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