1use 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
113unsafe 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 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 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 let decoded_call =
225 RuntimeCall::<S::Runtime>::decode(&mut encoded_call.as_slice())
226 .expect("Failed to decode runtime call");
227
228 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 <<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 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 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: (), trace,
385 code_hash: code_hash.expect("code_hash must have been calculated"),
386 })
387 }
388
389 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 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 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 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")) .map(|res| {
465 InstantiateReturnValue {
466 result: ExecReturnValue {
467 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 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 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 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 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 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 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 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 Ok(CallDryRunResult {
654 exec_result: ContractExecResultFor::<E> {
655 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")) .map(|res| {
669 ExecReturnValue {
670 flags: ink_env::ReturnFlags::from_bits_truncate(res.flags.bits()),
672 data: res.data,
673 }
674 }),
675 },
676 trace: None, _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 <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
738pub mod preset {
740 }
860
861pub 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}