ink_sandbox/api/
revive_api.rs

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