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