ink_e2e/
sandbox_client.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{
16    backend::BuilderClient,
17    builders::{
18        constructor_exec_input,
19        CreateBuilderPartial,
20    },
21    client_utils::{
22        salt,
23        ContractsRegistry,
24    },
25    contract_results::{
26        BareInstantiationResult,
27        ContractExecResultFor,
28        ContractResult,
29    },
30    error::SandboxErr,
31    log_error,
32    CallBuilderFinal,
33    CallDryRunResult,
34    ChainBackend,
35    ContractsBackend,
36    E2EBackend,
37    InstantiateDryRunResult,
38    UploadResult,
39    H256,
40};
41use frame_support::{
42    dispatch::RawOrigin,
43    pallet_prelude::DispatchError,
44    traits::{
45        fungible::Inspect,
46        IsType,
47    },
48};
49use ink_env::Environment;
50use ink_primitives::{
51    reflect::{
52        AbiDecodeWith,
53        AbiEncodeWith,
54        ScaleEncoding,
55    },
56    DepositLimit,
57};
58use ink_sandbox::{
59    api::prelude::*,
60    frame_system,
61    frame_system::pallet_prelude::OriginFor,
62    pallet_balances,
63    pallet_revive,
64    AccountIdFor,
65    RuntimeCall,
66    Sandbox,
67    Weight,
68};
69use jsonrpsee::core::async_trait;
70use pallet_revive::{
71    evm::{
72        CallTrace,
73        TracerConfig,
74        U256,
75    },
76    CodeUploadReturnValue,
77    InstantiateReturnValue,
78    MomentOf,
79};
80use scale::Decode;
81use sp_core::{
82    sr25519::Pair,
83    Pair as _,
84};
85use sp_runtime::traits::Bounded;
86use std::{
87    marker::PhantomData,
88    path::PathBuf,
89};
90use subxt::{
91    dynamic::Value,
92    tx::Payload,
93};
94use subxt_signer::sr25519::Keypair;
95
96type BalanceOf<R> = <R as pallet_balances::Config>::Balance;
97type ContractsBalanceOf<R> =
98    <<R as pallet_revive::Config>::Currency as Inspect<AccountIdFor<R>>>::Balance;
99
100pub struct Client<AccountId, S: Sandbox> {
101    sandbox: S,
102    contracts: ContractsRegistry,
103    _phantom: PhantomData<AccountId>,
104}
105
106// While it is not necessary true that `Client` is `Send`, it will not be used in a way
107// that would violate this bound. In particular, all `Client` instances will be operating
108// synchronously.
109unsafe impl<AccountId, S: Sandbox> Send for Client<AccountId, S> {}
110impl<AccountId, S: Sandbox> Client<AccountId, S>
111where
112    S: Default,
113    S::Runtime: pallet_balances::Config + pallet_revive::Config,
114    AccountIdFor<S::Runtime>: From<[u8; 32]>,
115    BalanceOf<S::Runtime>: From<u128>,
116{
117    pub fn new<P: Into<PathBuf>>(contracts: impl IntoIterator<Item = P>) -> Self {
118        let mut sandbox = S::default();
119        Self::fund_accounts(&mut sandbox);
120
121        Self {
122            sandbox,
123            contracts: ContractsRegistry::new(contracts),
124            _phantom: Default::default(),
125        }
126    }
127
128    fn fund_accounts(sandbox: &mut S) {
129        const TOKENS: u128 = 1_000_000_000_000_000;
130
131        let accounts = [
132            crate::alice(),
133            crate::bob(),
134            crate::charlie(),
135            crate::dave(),
136            crate::eve(),
137            crate::ferdie(),
138            crate::one(),
139            crate::two(),
140        ]
141        .map(|kp| kp.public_key().0)
142        .map(From::from);
143        for account in accounts.iter() {
144            sandbox
145                .mint_into(account, TOKENS.into())
146                .unwrap_or_else(|_| panic!("Failed to mint {} tokens", TOKENS));
147        }
148    }
149}
150
151#[async_trait]
152impl<AccountId: AsRef<[u8; 32]> + Send, S: Sandbox> ChainBackend for Client<AccountId, S>
153where
154    S::Runtime: pallet_balances::Config,
155    AccountIdFor<S::Runtime>: From<[u8; 32]>,
156{
157    type AccountId = AccountId;
158    type Balance = BalanceOf<S::Runtime>;
159    type Error = SandboxErr;
160    type EventLog = ();
161
162    async fn create_and_fund_account(
163        &mut self,
164        _origin: &Keypair,
165        amount: Self::Balance,
166    ) -> Keypair {
167        let (pair, seed) = Pair::generate();
168
169        self.sandbox
170            .mint_into(&pair.public().0.into(), amount)
171            .expect("Failed to mint tokens");
172
173        Keypair::from_secret_key(seed).expect("Failed to create keypair")
174    }
175
176    async fn free_balance(
177        &mut self,
178        account: Self::AccountId,
179    ) -> Result<Self::Balance, Self::Error> {
180        let account = AccountIdFor::<S::Runtime>::from(*account.as_ref());
181        Ok(self.sandbox.free_balance(&account))
182    }
183
184    async fn runtime_call<'a>(
185        &mut self,
186        origin: &Keypair,
187        pallet_name: &'a str,
188        call_name: &'a str,
189        call_data: Vec<Value>,
190    ) -> Result<Self::EventLog, Self::Error> {
191        // Since in general, `ChainBackend::runtime_call` must be dynamic, we have to
192        // perform some translation here in order to invoke strongly-typed
193        // [`ink_sandbox::Sandbox`] API.
194
195        // Get metadata of the Sandbox runtime, so that we can encode the call object.
196        // Panic on error - metadata of the static im-memory runtime should always be
197        // available.
198        let raw_metadata: Vec<u8> = S::get_metadata().into();
199        let metadata = subxt_metadata::Metadata::decode(&mut raw_metadata.as_slice())
200            .expect("Failed to decode metadata");
201
202        // Encode the call object.
203        let call = subxt::dynamic::tx(pallet_name, call_name, call_data);
204        let encoded_call = call.encode_call_data(&metadata.into()).map_err(|err| {
205            SandboxErr::new(format!("runtime_call: Error encoding call: {err:?}"))
206        })?;
207
208        // Decode the call object.
209        // Panic on error - we just encoded a validated call object, so it should be
210        // decodable.
211        let decoded_call =
212            RuntimeCall::<S::Runtime>::decode(&mut encoded_call.as_slice())
213                .expect("Failed to decode runtime call");
214
215        // Execute the call.
216        self.sandbox
217            .runtime_call(
218                decoded_call,
219                S::convert_account_to_origin(keypair_to_account(origin)),
220            )
221            .map_err(|err| {
222                SandboxErr::new(format!("runtime_call: execution error {:?}", err.error))
223            })?;
224
225        Ok(())
226    }
227}
228
229#[async_trait]
230impl<
231        AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
232        S: Sandbox,
233        E: Environment<AccountId = AccountId, Balance = ContractsBalanceOf<S::Runtime>>
234            + 'static,
235    > BuilderClient<E> for Client<AccountId, S>
236where
237    S::Runtime: pallet_balances::Config + pallet_revive::Config,
238    AccountIdFor<S::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
239    ContractsBalanceOf<S::Runtime>: Send + Sync,
240
241    ContractsBalanceOf<S::Runtime>: Into<U256> + TryFrom<U256> + Bounded,
242    MomentOf<S::Runtime>: Into<U256>,
243
244    // todo
245    <<S as Sandbox>::Runtime as frame_system::Config>::Hash:
246        frame_support::traits::IsType<sp_core::H256>,
247{
248    async fn bare_instantiate<
249        Contract: Clone,
250        Args: Send + Sync + AbiEncodeWith<ScaleEncoding> + Clone,
251        R,
252    >(
253        &mut self,
254        contract_name: &str,
255        caller: &Keypair,
256        constructor: &mut CreateBuilderPartial<E, Contract, Args, R>,
257        value: E::Balance,
258        gas_limit: Weight,
259        storage_deposit_limit: DepositLimit<E::Balance>,
260    ) -> Result<BareInstantiationResult<Self::EventLog>, Self::Error> {
261        let _ =
262            <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
263
264        // get the trace
265        let _ = self.sandbox.build_block();
266
267        let tracer_config = TracerConfig::CallTracer { with_logs: true };
268
269        let mut tracer =
270            tracer_config.build(pallet_revive::Pallet::<S::Runtime>::evm_gas_from_weight);
271        let mut code_hash: Option<H256> = None;
272        let result = pallet_revive::tracing::trace(&mut tracer, || {
273            let code = self.contracts.load_code(contract_name);
274            code_hash = Some(H256(crate::client_utils::code_hash(&code[..])));
275            let data = constructor_exec_input(constructor.clone());
276            self.sandbox.deploy_contract(
277                code,
278                value,
279                data,
280                salt(),
281                caller_to_origin::<S>(caller),
282                gas_limit,
283                storage_deposit_limit,
284            )
285        });
286
287        let addr_raw = match &result.result {
288            Err(err) => {
289                log_error(&format!("Instantiation failed: {err:?}"));
290                return Err(SandboxErr::new(format!("bare_instantiate: {err:?}")));
291            }
292            Ok(res) => res.addr,
293        };
294
295        let mut traces = tracer.collect_traces();
296        assert_eq!(traces.len(), 1);
297        let trace = traces.pop();
298
299        Ok(BareInstantiationResult {
300            addr: addr_raw,
301            events: (), // todo: https://github.com/Cardinal-Cryptography/drink/issues/32
302            trace,
303            code_hash: code_hash.expect("code_hash must have been calculated"),
304        })
305    }
306
307    async fn bare_instantiate_dry_run<
308        Contract: Clone,
309        Args: Send + Sync + AbiEncodeWith<ScaleEncoding> + Clone,
310        R,
311    >(
312        &mut self,
313        contract_name: &str,
314        caller: &Keypair,
315        constructor: &mut CreateBuilderPartial<E, Contract, Args, R>,
316        value: E::Balance,
317        storage_deposit_limit: DepositLimit<E::Balance>,
318    ) -> Result<InstantiateDryRunResult<E>, Self::Error> {
319        // todo has to be: let _ = <Client<AccountId, S> as
320        // BuilderClient<E>>::map_account_dry_run(self, &caller).await;
321        let _ =
322            <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
323
324        let code = self.contracts.load_code(contract_name);
325        let data = constructor_exec_input(constructor.clone());
326        let result = self.sandbox.dry_run(|sandbox| {
327            sandbox.deploy_contract(
328                code,
329                value,
330                data,
331                salt(),
332                caller_to_origin::<S>(caller),
333                S::default_gas_limit(),
334                storage_deposit_limit,
335            )
336        });
337
338        let addr_id_raw = match &result.result {
339            Err(err) => {
340                panic!("Instantiate dry-run failed: {err:?}!")
341            }
342            Ok(res) => res.addr,
343        };
344
345        let result = ContractResult::<InstantiateReturnValue, E::Balance> {
346            gas_consumed: result.gas_consumed,
347            gas_required: result.gas_required,
348            storage_deposit: result.storage_deposit,
349            result: result.result.map(|r| {
350                InstantiateReturnValue {
351                    result: r.result,
352                    addr: addr_id_raw, // todo
353                }
354            }),
355        };
356        Ok(result.into())
357    }
358
359    async fn bare_upload(
360        &mut self,
361        contract_name: &str,
362        caller: &Keypair,
363        storage_deposit_limit: E::Balance,
364    ) -> Result<UploadResult<E, Self::EventLog>, Self::Error> {
365        let code = self.contracts.load_code(contract_name);
366
367        let result = match self.sandbox.upload_contract(
368            code,
369            caller_to_origin::<S>(caller),
370            storage_deposit_limit,
371        ) {
372            Ok(result) => result,
373            Err(err) => {
374                log_error(&format!("Upload failed: {err:?}"));
375                return Err(SandboxErr::new(format!("bare_upload: {err:?}")))
376            }
377        };
378
379        Ok(UploadResult {
380            code_hash: result.code_hash,
381            dry_run: Ok(CodeUploadReturnValue {
382                code_hash: result.code_hash,
383                deposit: result.deposit,
384            }),
385            events: (),
386        })
387    }
388
389    async fn bare_remove_code(
390        &mut self,
391        _caller: &Keypair,
392        _code_hash: H256,
393    ) -> Result<Self::EventLog, Self::Error> {
394        unimplemented!("sandbox does not yet support remove_code")
395    }
396
397    async fn bare_call<
398        Args: Sync + AbiEncodeWith<Abi> + Clone,
399        RetType: Send + AbiDecodeWith<Abi>,
400        Abi: Sync + Clone,
401    >(
402        &mut self,
403        caller: &Keypair,
404        message: &CallBuilderFinal<E, Args, RetType, Abi>,
405        value: E::Balance,
406        gas_limit: Weight,
407        storage_deposit_limit: DepositLimit<E::Balance>,
408    ) -> Result<(Self::EventLog, Option<CallTrace>), Self::Error>
409    where
410        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
411    {
412        let _ =
413            <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
414
415        // todo rename any account_id coming back from callee
416        let addr = *message.clone().params().callee();
417        let exec_input = message.clone().params().exec_input().encode();
418
419        // todo
420        let tracer_config = TracerConfig::CallTracer { with_logs: true };
421        let mut tracer =
422            tracer_config.build(pallet_revive::Pallet::<S::Runtime>::evm_gas_from_weight);
423        let _result = pallet_revive::tracing::trace(&mut tracer, || {
424            self.sandbox
425                .call_contract(
426                    addr,
427                    value,
428                    exec_input,
429                    caller_to_origin::<S>(caller),
430                    gas_limit,
431                    storage_deposit_limit,
432                )
433                .result
434                .map_err(|err| SandboxErr::new(format!("bare_call: {err:?}")))
435        })?;
436        let mut traces = tracer.collect_traces();
437        assert_eq!(traces.len(), 1);
438        let trace = traces.pop();
439
440        Ok(((), trace))
441    }
442
443    async fn bare_call_dry_run<
444        Args: Sync + AbiEncodeWith<Abi> + Clone,
445        RetType: Send + AbiDecodeWith<Abi>,
446        Abi: Sync + Clone,
447    >(
448        &mut self,
449        caller: &Keypair,
450        message: &CallBuilderFinal<E, Args, RetType, Abi>,
451        value: E::Balance,
452        storage_deposit_limit: DepositLimit<E::Balance>,
453    ) -> Result<CallDryRunResult<E, RetType>, Self::Error>
454    where
455        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
456    {
457        // todo there's side effects here
458        let _ =
459            <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
460
461        let addr = *message.clone().params().callee();
462        let exec_input = message.clone().params().exec_input().encode();
463
464        let result = self.sandbox.dry_run(|sandbox| {
465            sandbox.call_contract(
466                addr,
467                value,
468                exec_input,
469                caller_to_origin::<S>(caller),
470                S::default_gas_limit(),
471                storage_deposit_limit,
472            )
473        });
474        if result.result.is_err() {
475            let res = result.result.clone().unwrap_err();
476            if let DispatchError::Module(m) = res {
477                let msg = m.message;
478                if msg.is_some() {
479                    let s = msg.unwrap();
480                    if s.contains("AccountUnmapped") {
481                        panic!("something is wrong, we mapped the account before")
482                    }
483                }
484            }
485        }
486        // todo error when `AccountUnmapped`
487        Ok(CallDryRunResult {
488            exec_result: ContractExecResultFor::<E> {
489                gas_consumed: result.gas_consumed,
490                gas_required: result.gas_required,
491                storage_deposit: result.storage_deposit,
492                result: result.result,
493            },
494            _marker: Default::default(),
495            trace: None, // todo
496        })
497    }
498
499    async fn map_account_dry_run(&mut self, caller: &Keypair) -> Result<(), Self::Error> {
500        let caller = keypair_to_account(caller);
501        let origin = RawOrigin::Signed(caller);
502        let origin = OriginFor::<S::Runtime>::from(origin);
503
504        self.sandbox
505            .dry_run(|sandbox| sandbox.map_account(origin))
506            .map_err(|err| {
507                SandboxErr::new(format!("map_account_dry_run: execution error {:?}", err))
508            })
509    }
510
511    async fn map_account(&mut self, caller: &Keypair) -> Result<(), Self::Error> {
512        let caller = keypair_to_account(caller);
513        let origin = RawOrigin::Signed(caller);
514        let origin = OriginFor::<S::Runtime>::from(origin);
515
516        self.sandbox.map_account(origin).map_err(|err| {
517            SandboxErr::new(format!("map_account: execution error {:?}", err))
518        })
519    }
520}
521
522impl<
523        AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
524        Config: Sandbox,
525        E: Environment<
526                AccountId = AccountId,
527                Balance = ContractsBalanceOf<Config::Runtime>,
528            > + 'static,
529    > E2EBackend<E> for Client<AccountId, Config>
530where
531    Config::Runtime: pallet_balances::Config + pallet_revive::Config,
532    AccountIdFor<Config::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
533    ContractsBalanceOf<Config::Runtime>: Send + Sync,
534    ContractsBalanceOf<Config::Runtime>: Into<U256> + TryFrom<U256> + Bounded,
535    MomentOf<Config::Runtime>: Into<U256>,
536
537    // todo
538    <Config::Runtime as frame_system::Config>::Hash: IsType<sp_core::H256>,
539{
540}
541
542fn keypair_to_account<AccountId: From<[u8; 32]>>(keypair: &Keypair) -> AccountId {
543    AccountId::from(keypair.public_key().0)
544}
545
546fn caller_to_origin<S>(caller: &Keypair) -> OriginFor<S::Runtime>
547where
548    S: Sandbox,
549    S::Runtime: pallet_balances::Config + pallet_revive::Config,
550    AccountIdFor<S::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
551{
552    let caller = keypair_to_account(caller);
553    let origin = RawOrigin::Signed(caller);
554    OriginFor::<S::Runtime>::from(origin)
555}
556
557#[async_trait]
558impl<
559        AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
560        S: Sandbox,
561        E: Environment<AccountId = AccountId, Balance = ContractsBalanceOf<S::Runtime>>
562            + 'static,
563    > ContractsBackend<E> for Client<AccountId, S>
564where
565    S::Runtime: pallet_balances::Config + pallet_revive::Config,
566    AccountIdFor<S::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
567{
568    type Error = SandboxErr;
569    type EventLog = ();
570}
571
572/// Exposes preset sandbox configurations to be used in tests.
573pub mod preset {
574    pub mod mock_network {
575        use ink_sandbox::{
576            frame_system,
577            AccountIdFor,
578            BlockBuilder,
579            Extension,
580            RuntimeMetadataPrefixed,
581            Sandbox,
582            Snapshot,
583        };
584        pub use pallet_revive_mock_network::*;
585        use sp_runtime::traits::Dispatchable;
586
587        /// A [`ink_sandbox::Sandbox`] that can be used to test contracts
588        /// with a mock network of relay chain and parachains.
589        ///
590        /// ```no_compile
591        /// #[ink_e2e::test(backend(runtime_only(sandbox = MockNetworkSandbox)))]
592        /// async fn my_test<Client: E2EBackend>(mut client: Client) -> E2EResult<()> {
593        ///   // ...
594        /// }
595        /// ```
596        #[derive(Default)]
597        pub struct MockNetworkSandbox {
598            dry_run: bool,
599        }
600
601        impl Sandbox for MockNetworkSandbox {
602            type Runtime = parachain::Runtime;
603
604            fn execute_with<T>(&mut self, execute: impl FnOnce() -> T) -> T {
605                if self.dry_run {
606                    ParaA::execute_with(execute)
607                } else {
608                    ParaA::execute_without_dispatch(execute)
609                }
610            }
611
612            fn dry_run<T>(&mut self, action: impl FnOnce(&mut Self) -> T) -> T {
613                EXT_PARAA.with(|v| {
614                    let backend_backup = v.borrow_mut().as_backend();
615                    self.dry_run = true;
616                    let result = action(self);
617                    self.dry_run = false;
618
619                    let mut v = v.borrow_mut();
620                    v.commit_all().expect("Failed to commit changes");
621                    v.backend = backend_backup;
622                    result
623                })
624            }
625
626            fn register_extension<E: ::core::any::Any + Extension>(&mut self, ext: E) {
627                EXT_PARAA.with(|v| v.borrow_mut().register_extension(ext));
628            }
629
630            fn initialize_block(
631                height: frame_system::pallet_prelude::BlockNumberFor<Self::Runtime>,
632                parent_hash: <Self::Runtime as frame_system::Config>::Hash,
633            ) {
634                BlockBuilder::<Self::Runtime>::initialize_block(height, parent_hash)
635            }
636
637            fn finalize_block(
638                height: frame_system::pallet_prelude::BlockNumberFor<Self::Runtime>,
639            ) -> <Self::Runtime as frame_system::Config>::Hash {
640                BlockBuilder::<Self::Runtime>::finalize_block(height)
641            }
642
643            fn default_actor() -> AccountIdFor<Self::Runtime> {
644                ALICE
645            }
646
647            fn get_metadata() -> RuntimeMetadataPrefixed {
648                parachain::Runtime::metadata()
649            }
650
651            fn convert_account_to_origin(
652                account: AccountIdFor<Self::Runtime>,
653            ) -> <<Self::Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin
654            {
655                Some(account).into()
656            }
657
658            fn take_snapshot(&mut self) -> Snapshot {
659                EXT_PARAA.with(|v| {
660                    let mut v = v.borrow_mut();
661                    let mut backend = v.as_backend().clone();
662                    let raw_key_values = backend
663                        .backend_storage_mut()
664                        .drain()
665                        .into_iter()
666                        .filter(|(_, (_, r))| *r > 0)
667                        .collect::<Vec<(Vec<u8>, (Vec<u8>, i32))>>();
668                    let root = backend.root().to_owned();
669
670                    Snapshot {
671                        storage: raw_key_values,
672                        storage_root: root,
673                    }
674                })
675            }
676
677            fn restore_snapshot(&mut self, snapshot: ink_sandbox::Snapshot) {
678                EXT_PARAA.with(|v| {
679                    let mut v = v.borrow_mut();
680
681                    *v = ink_sandbox::TestExternalities::from_raw_snapshot(
682                        snapshot.storage,
683                        snapshot.storage_root,
684                        Default::default(),
685                    );
686                })
687            }
688        }
689    }
690}