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