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