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