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