1use 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
111unsafe 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 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 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 let decoded_call =
223 RuntimeCall::<S::Runtime>::decode(&mut encoded_call.as_slice())
224 .expect("Failed to decode runtime call");
225
226 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 <<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 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: (), trace,
379 code_hash: code_hash.expect("code_hash must have been calculated"),
380 })
381 }
382
383 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 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 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 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 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 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 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 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 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 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, _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 <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
698pub mod preset {
700 }
820
821pub 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}