ink_sandbox/api/
revive_api.rs

1use crate::{
2    AccountIdFor,
3    ContractExecResultFor,
4    ContractResultInstantiate,
5    H256,
6    Sandbox,
7    balance_to_evm_value,
8};
9use frame_support::{
10    pallet_prelude::DispatchError,
11    sp_runtime::traits::Bounded,
12    traits::{
13        Time,
14        fungible::{
15            Inspect,
16            Mutate,
17        },
18    },
19    weights::Weight,
20};
21use frame_system::pallet_prelude::OriginFor;
22use ink_primitives::Address;
23use pallet_revive::{
24    Code,
25    CodeUploadResult,
26    ExecConfig,
27    evm::{
28        Tracer,
29        TracerType,
30    },
31};
32use sp_core::U256;
33use std::ops::Not;
34
35type BalanceOf<R> =
36    <<R as pallet_revive::Config>::Currency as Inspect<AccountIdFor<R>>>::Balance;
37
38type MomentOf<T> = <<T as pallet_revive::Config>::Time as Time>::Moment;
39
40/// Contract API used to interact with `pallet-revive`.
41pub trait ContractAPI {
42    /// The runtime contract config.
43    type T: pallet_revive::Config;
44
45    /// Interface for `bare_instantiate` contract call with a simultaneous upload.
46    ///
47    /// # Arguments
48    ///
49    /// * `contract_bytes` - The contract code.
50    /// * `value` - The number of tokens to be transferred to the contract.
51    /// * `data` - The input data to be passed to the contract (including constructor
52    ///   name).
53    /// * `salt` - The salt to be used for contract address derivation.
54    /// * `origin` - The sender of the contract call.
55    /// * `gas_limit` - The gas limit for the contract call.
56    /// * `storage_deposit_limit` - The storage deposit limit for the contract call.
57    #[allow(clippy::type_complexity, clippy::too_many_arguments)]
58    fn map_account(
59        &mut self,
60        account: impl crate::IntoAccountId<AccountIdFor<Self::T>>,
61    ) -> Result<(), DispatchError>;
62
63    /// `pallet-revive` uses a dedicated "pallet" account for tracking
64    /// storage deposits. The static account is returned by the
65    /// `pallet_revive::Pallet::account_id()` function.
66    ///
67    /// This function funds the account with the existential deposit
68    /// (i.e. minimum balance).
69    fn warm_up(&mut self);
70
71    /// Interface for `bare_instantiate` contract call with a simultaneous upload.
72    ///
73    /// # Arguments
74    ///
75    /// * `contract_bytes` - The contract code.
76    /// * `value` - The number of tokens to be transferred to the contract.
77    /// * `data` - The input data to be passed to the contract (including constructor
78    ///   name).
79    /// * `salt` - The salt to be used for contract address derivation.
80    /// * `origin` - The sender of the contract call.
81    /// * `gas_limit` - The gas limit for the contract call.
82    /// * `storage_deposit_limit` - The storage deposit limit for the contract call.
83    #[allow(clippy::type_complexity, clippy::too_many_arguments)]
84    fn deploy_contract(
85        &mut self,
86        contract_bytes: Vec<u8>,
87        value: BalanceOf<Self::T>,
88        data: Vec<u8>,
89        salt: Option<[u8; 32]>,
90        origin: OriginFor<Self::T>,
91        gas_limit: Weight,
92        storage_deposit_limit: BalanceOf<Self::T>,
93    ) -> ContractResultInstantiate<Self::T>;
94
95    /// Interface for `bare_instantiate` contract call for a previously uploaded contract.
96    ///
97    /// # Arguments
98    ///
99    /// * `code_hash` - The code hash of the contract to instantiate.
100    /// * `value` - The number of tokens to be transferred to the contract.
101    /// * `data` - The input data to be passed to the contract (including constructor
102    ///   name).
103    /// * `salt` - The salt to be used for contract address derivation.
104    /// * `origin` - The sender of the contract call.
105    /// * `gas_limit` - The gas limit for the contract call.
106    /// * `storage_deposit_limit` - The storage deposit limit for the contract call.
107    #[allow(clippy::type_complexity, clippy::too_many_arguments)]
108    fn instantiate_contract(
109        &mut self,
110        code_hash: H256,
111        value: BalanceOf<Self::T>,
112        data: Vec<u8>,
113        salt: Option<[u8; 32]>,
114        origin: OriginFor<Self::T>,
115        gas_limit: Weight,
116        storage_deposit_limit: BalanceOf<Self::T>,
117    ) -> ContractResultInstantiate<Self::T>;
118
119    /// Interface for `bare_upload_code` contract call.
120    ///
121    /// # Arguments
122    ///
123    /// * `contract_bytes` - The contract code.
124    /// * `origin` - The sender of the contract call.
125    /// * `storage_deposit_limit` - The storage deposit limit for the contract call.
126    fn upload_contract(
127        &mut self,
128        contract_bytes: Vec<u8>,
129        origin: OriginFor<Self::T>,
130        storage_deposit_limit: BalanceOf<Self::T>,
131    ) -> CodeUploadResult<BalanceOf<Self::T>>;
132
133    /// Interface for `bare_call` contract call.
134    ///
135    /// # Arguments
136    ///
137    /// * `address` - The address of the contract to be called.
138    /// * `value` - The number of tokens to be transferred to the contract.
139    /// * `data` - The input data to be passed to the contract (including message name).
140    /// * `origin` - The sender of the contract call.
141    /// * `gas_limit` - The gas limit for the contract call.
142    /// * `storage_deposit_limit` - The storage deposit limit for the contract call.
143    #[allow(clippy::type_complexity, clippy::too_many_arguments)]
144    fn call_contract(
145        &mut self,
146        address: Address,
147        value: BalanceOf<Self::T>,
148        data: Vec<u8>,
149        origin: OriginFor<Self::T>,
150        gas_limit: Weight,
151        storage_deposit_limit: BalanceOf<Self::T>,
152    ) -> ContractExecResultFor<Self::T>;
153
154    fn evm_tracer(&mut self, tracer_type: TracerType) -> Tracer<Self::T>;
155}
156
157impl<T> ContractAPI for T
158where
159    T: Sandbox,
160    T::Runtime: pallet_balances::Config + pallet_revive::Config,
161    BalanceOf<T::Runtime>: Into<U256> + TryFrom<U256> + Bounded,
162    MomentOf<T::Runtime>: Into<U256>,
163    <<T as Sandbox>::Runtime as frame_system::Config>::Nonce: Into<u32>,
164    // todo
165    <<T as Sandbox>::Runtime as frame_system::Config>::Hash:
166        frame_support::traits::IsType<sp_core::H256>,
167{
168    type T = T::Runtime;
169
170    fn map_account(
171        &mut self,
172        account: impl crate::IntoAccountId<AccountIdFor<Self::T>>,
173    ) -> Result<(), DispatchError> {
174        let account_id = account.into_account_id();
175        let origin = Self::convert_account_to_origin(account_id);
176        self.execute_with(|| pallet_revive::Pallet::<Self::T>::map_account(origin))
177    }
178
179    /// `pallet-revive` uses a dedicated "pallet" account for tracking
180    /// storage deposits. The static account is returned by the
181    /// `pallet_revive::Pallet::account_id()` function.
182    ///
183    /// This function funds the account with the existential deposit
184    /// (i.e. minimum balance).
185    fn warm_up(&mut self) {
186        self.execute_with(|| {
187            let acc = pallet_revive::Pallet::<Self::T>::account_id();
188            let ed = pallet_balances::Pallet::<Self::T>::minimum_balance();
189
190            // we only fund the account if need be
191            if pallet_balances::Pallet::<Self::T>::free_balance(&acc) < ed {
192                let _ = pallet_balances::Pallet::<Self::T>::mint_into(&acc, ed);
193            }
194        });
195    }
196
197    fn deploy_contract(
198        &mut self,
199        contract_bytes: Vec<u8>,
200        value: BalanceOf<Self::T>,
201        data: Vec<u8>,
202        salt: Option<[u8; 32]>,
203        origin: OriginFor<Self::T>,
204        gas_limit: Weight,
205        storage_deposit_limit: BalanceOf<Self::T>,
206    ) -> ContractResultInstantiate<Self::T> {
207        self.warm_up();
208        self.execute_with(|| {
209            pallet_revive::Pallet::<Self::T>::bare_instantiate(
210                origin,
211                balance_to_evm_value::<Self::T>(value),
212                gas_limit,
213                storage_deposit_limit,
214                Code::Upload(contract_bytes),
215                data,
216                salt,
217                ExecConfig {
218                    bump_nonce: true,
219                    collect_deposit_from_hold: None,
220                    effective_gas_price: None,
221                },
222            )
223        })
224    }
225
226    fn instantiate_contract(
227        &mut self,
228        code_hash: H256,
229        value: BalanceOf<Self::T>,
230        data: Vec<u8>,
231        salt: Option<[u8; 32]>,
232        origin: OriginFor<Self::T>,
233        gas_limit: Weight,
234        storage_deposit_limit: BalanceOf<Self::T>,
235    ) -> ContractResultInstantiate<Self::T> {
236        self.execute_with(|| {
237            pallet_revive::Pallet::<Self::T>::bare_instantiate(
238                origin,
239                balance_to_evm_value::<Self::T>(value),
240                gas_limit,
241                storage_deposit_limit,
242                Code::Existing(code_hash),
243                data,
244                salt,
245                ExecConfig {
246                    bump_nonce: true,
247                    collect_deposit_from_hold: None,
248                    effective_gas_price: None,
249                },
250            )
251        })
252    }
253
254    fn upload_contract(
255        &mut self,
256        contract_bytes: Vec<u8>,
257        origin: OriginFor<Self::T>,
258        storage_deposit_limit: BalanceOf<Self::T>,
259    ) -> CodeUploadResult<BalanceOf<Self::T>> {
260        self.execute_with(|| {
261            pallet_revive::Pallet::<Self::T>::bare_upload_code(
262                origin,
263                contract_bytes,
264                storage_deposit_limit,
265            )
266        })
267    }
268
269    fn call_contract(
270        &mut self,
271        address: Address,
272        value: BalanceOf<Self::T>,
273        data: Vec<u8>,
274        origin: OriginFor<Self::T>,
275        gas_limit: Weight,
276        storage_deposit_limit: BalanceOf<Self::T>,
277    ) -> ContractExecResultFor<Self::T> {
278        self.execute_with(|| {
279            pallet_revive::Pallet::<Self::T>::bare_call(
280                origin,
281                address,
282                balance_to_evm_value::<Self::T>(value),
283                gas_limit,
284                storage_deposit_limit,
285                data,
286                ExecConfig {
287                    bump_nonce: true,
288                    collect_deposit_from_hold: None,
289                    effective_gas_price: None,
290                },
291            )
292        })
293    }
294
295    fn evm_tracer(&mut self, tracer_type: TracerType) -> Tracer<Self::T> {
296        self.execute_with(|| pallet_revive::Pallet::<Self::T>::evm_tracer(tracer_type))
297    }
298}
299
300/// todo
301/// Converts bytes to a '\n'-split string, ignoring empty lines.
302pub fn decode_debug_buffer(buffer: &[u8]) -> Vec<String> {
303    let decoded = buffer.iter().map(|b| *b as char).collect::<String>();
304    decoded
305        .split('\n')
306        .filter_map(|s| s.is_empty().not().then_some(s.to_string()))
307        .collect()
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use crate::{
314        DefaultSandbox,
315        RuntimeEventOf,
316        api::prelude::*,
317    };
318
319    const STORAGE_DEPOSIT_LIMIT: u128 = u128::MAX;
320
321    fn compile_module(contract_name: &str) -> Vec<u8> {
322        // todo compile the contract, instead of reading the binary
323        let path = [
324            std::env::var("CARGO_MANIFEST_DIR").as_deref().unwrap(),
325            "/test-resources/",
326            contract_name,
327            ".polkavm",
328        ]
329        .concat();
330        std::fs::read(std::path::Path::new(&path)).unwrap()
331    }
332
333    /// `pallet-revive` uses a dedicated "pallet" account for tracking
334    /// storage deposits. The static account is returned by the
335    /// `pallet_revive::Pallet::account_id()` function.
336    ///
337    /// This function funds the account with the existential deposit
338    /// (i.e. minimum balance).
339    fn warm_up<T>(sandbox: &mut T)
340    where
341        <T as Sandbox>::Runtime: pallet_revive::Config + pallet_balances::Config,
342        T: BalanceAPI<T> + Sandbox,
343    {
344        let acc = pallet_revive::Pallet::<<T as Sandbox>::Runtime>::account_id();
345        let ed = pallet_balances::Pallet::<<T as Sandbox>::Runtime>::minimum_balance();
346        sandbox.mint_into(&acc, ed).unwrap_or_else(|_| {
347            panic!("Failed to mint existential balance into `pallet-revive` account")
348        });
349    }
350
351    #[test]
352    fn can_upload_code() {
353        let mut sandbox = DefaultSandbox::default();
354        let contract_binary = compile_module("dummy");
355        warm_up::<DefaultSandbox>(&mut sandbox);
356
357        use sha3::{
358            Digest,
359            Keccak256,
360        };
361        let hash = Keccak256::digest(contract_binary.as_slice());
362        let hash = H256::from_slice(hash.as_slice());
363
364        let origin =
365            DefaultSandbox::convert_account_to_origin(DefaultSandbox::default_actor());
366        let result = sandbox.upload_contract(contract_binary, origin, 100000000000000);
367
368        assert!(result.is_ok());
369        assert_eq!(hash, result.unwrap().code_hash);
370    }
371
372    #[test]
373    fn can_deploy_contract() {
374        let mut sandbox = DefaultSandbox::default();
375        let contract_binary = compile_module("dummy");
376
377        let events_before = sandbox.events();
378        assert!(events_before.is_empty());
379
380        warm_up::<DefaultSandbox>(&mut sandbox);
381
382        let default_actor = DefaultSandbox::default_actor();
383        sandbox.map_account(&default_actor).expect("cannot map");
384        let origin = DefaultSandbox::convert_account_to_origin(default_actor);
385        let result = sandbox.deploy_contract(
386            contract_binary.clone(),
387            0,
388            vec![],
389            None,
390            origin.clone(),
391            DefaultSandbox::default_gas_limit(),
392            100000000000000,
393        );
394        assert!(result.result.is_ok());
395        assert!(!result.result.unwrap().result.did_revert());
396
397        // deploying again must fail due to `DuplicateContract`
398        let result = sandbox.deploy_contract(
399            contract_binary,
400            0,
401            vec![],
402            None,
403            origin,
404            DefaultSandbox::default_gas_limit(),
405            100000000000000,
406        );
407        assert!(result.result.is_err());
408        let dispatch_err = result.result.unwrap_err();
409        assert!(format!("{dispatch_err:?}").contains("DuplicateContract"));
410    }
411
412    #[test]
413    fn can_call_contract() {
414        let mut sandbox = DefaultSandbox::default();
415        let _actor = DefaultSandbox::default_actor();
416        let contract_binary = compile_module("dummy");
417        warm_up::<DefaultSandbox>(&mut sandbox);
418
419        let default_actor = DefaultSandbox::default_actor();
420        sandbox.map_account(&default_actor).expect("unable to map");
421        let origin = DefaultSandbox::convert_account_to_origin(default_actor);
422        let result = sandbox.deploy_contract(
423            contract_binary,
424            0,
425            vec![],
426            None,
427            origin.clone(),
428            DefaultSandbox::default_gas_limit(),
429            STORAGE_DEPOSIT_LIMIT,
430        );
431        assert!(!result.result.clone().unwrap().result.did_revert());
432
433        let contract_address = result.result.expect("Contract should be deployed").addr;
434
435        sandbox.reset_events();
436
437        let result = sandbox.call_contract(
438            contract_address,
439            0,
440            vec![],
441            origin.clone(),
442            DefaultSandbox::default_gas_limit(),
443            STORAGE_DEPOSIT_LIMIT,
444        );
445        assert!(result.result.is_ok());
446        assert!(!result.result.unwrap().did_revert());
447
448        let events = sandbox.events();
449        assert_eq!(events.len(), 1);
450        assert_eq!(
451            events[0].event,
452            RuntimeEventOf::<DefaultSandbox>::Revive(
453                pallet_revive::Event::ContractEmitted {
454                    contract: contract_address,
455                    topics: vec![H256::from([42u8; 32])],
456                    data: vec![1, 2, 3, 4],
457                }
458            )
459        );
460    }
461}