1use std::{
16 marker::PhantomData,
17 path::PathBuf,
18};
19
20use frame_support::{
21 dispatch::RawOrigin,
22 pallet_prelude::DispatchError,
23 traits::{
24 IsType,
25 fungible::Inspect,
26 },
27};
28use ink_env::{
29 Environment,
30 call::utils::DecodeMessageResult,
31};
32use ink_primitives::{
33 DepositLimit,
34 abi::AbiEncodeWith,
35};
36use ink_sandbox::{
37 AccountIdFor,
38 RuntimeCall,
39 Sandbox,
40 Weight,
41 api::prelude::*,
42 frame_system,
43 frame_system::pallet_prelude::OriginFor,
44 pallet_balances,
45 pallet_revive,
46};
47use jsonrpsee::core::async_trait;
48use pallet_revive::{
49 CodeUploadReturnValue,
50 InstantiateReturnValue,
51 MomentOf,
52 evm::{
53 CallTrace,
54 CallTracerConfig,
55 Trace,
56 TracerType,
57 U256,
58 },
59};
60use scale::Decode;
61use sp_core::{
62 Pair as _,
63 sr25519::Pair,
64};
65use sp_runtime::traits::Bounded;
66use subxt::{
67 dynamic::Value,
68 tx::Payload,
69};
70use subxt_signer::sr25519::Keypair;
71
72use crate::{
73 CallBuilderFinal,
74 CallDryRunResult,
75 ChainBackend,
76 ContractsBackend,
77 E2EBackend,
78 H256,
79 InstantiateDryRunResult,
80 UploadResult,
81 backend::BuilderClient,
82 builders::{
83 CreateBuilderPartial,
84 constructor_exec_input,
85 },
86 client_utils::{
87 ContractsRegistry,
88 salt,
89 },
90 contract_results::{
91 BareInstantiationResult,
92 ContractExecResultFor,
93 ContractResult,
94 },
95 error::SandboxErr,
96 log_error,
97};
98
99type BalanceOf<R> = <R as pallet_balances::Config>::Balance;
100type ContractsBalanceOf<R> =
101 <<R as pallet_revive::Config>::Currency as Inspect<AccountIdFor<R>>>::Balance;
102
103pub struct Client<AccountId, S: Sandbox> {
104 sandbox: S,
105 contracts: ContractsRegistry,
106 _phantom: PhantomData<AccountId>,
107}
108
109unsafe impl<AccountId, S: Sandbox> Send for Client<AccountId, S> {}
113impl<AccountId, S: Sandbox> Client<AccountId, S>
114where
115 S: Default,
116 S::Runtime: pallet_balances::Config + pallet_revive::Config,
117 AccountIdFor<S::Runtime>: From<[u8; 32]>,
118 BalanceOf<S::Runtime>: From<u128>,
119{
120 pub fn new<P: Into<PathBuf>>(contracts: impl IntoIterator<Item = P>) -> Self {
121 let mut sandbox = S::default();
122 Self::fund_accounts(&mut sandbox);
123
124 Self {
125 sandbox,
126 contracts: ContractsRegistry::new(contracts),
127 _phantom: Default::default(),
128 }
129 }
130
131 fn fund_accounts(sandbox: &mut S) {
132 const TOKENS: u128 = 1_000_000_000_000_000;
133
134 let accounts = [
135 crate::alice(),
136 crate::bob(),
137 crate::charlie(),
138 crate::dave(),
139 crate::eve(),
140 crate::ferdie(),
141 crate::one(),
142 crate::two(),
143 ]
144 .map(|kp| kp.public_key().0)
145 .map(From::from);
146 for account in accounts.iter() {
147 sandbox
148 .mint_into(account, TOKENS.into())
149 .unwrap_or_else(|_| panic!("Failed to mint {TOKENS} tokens"));
150 }
151
152 let acc = pallet_revive::Pallet::<S::Runtime>::account_id();
153 let ed = pallet_balances::Pallet::<S::Runtime>::minimum_balance();
154 sandbox.mint_into(&acc, ed).unwrap_or_else(|_| {
155 panic!("Failed to mint existential deposit into `pallet-revive` account")
156 });
157 }
158}
159
160#[async_trait]
161impl<AccountId: AsRef<[u8; 32]> + Send, S: Sandbox> ChainBackend for Client<AccountId, S>
162where
163 S::Runtime: pallet_balances::Config,
164 AccountIdFor<S::Runtime>: From<[u8; 32]>,
165{
166 type AccountId = AccountId;
167 type Balance = BalanceOf<S::Runtime>;
168 type Error = SandboxErr;
169 type EventLog = ();
170
171 async fn create_and_fund_account(
172 &mut self,
173 _origin: &Keypair,
174 amount: Self::Balance,
175 ) -> Keypair {
176 let (pair, seed) = Pair::generate();
177
178 self.sandbox
179 .mint_into(&pair.public().0.into(), amount)
180 .expect("Failed to mint tokens");
181
182 Keypair::from_secret_key(seed).expect("Failed to create keypair")
183 }
184
185 async fn free_balance(
186 &mut self,
187 account: Self::AccountId,
188 ) -> Result<Self::Balance, Self::Error> {
189 let account = AccountIdFor::<S::Runtime>::from(*account.as_ref());
190 Ok(self.sandbox.free_balance(&account))
191 }
192
193 async fn runtime_call<'a>(
194 &mut self,
195 origin: &Keypair,
196 pallet_name: &'a str,
197 call_name: &'a str,
198 call_data: Vec<Value>,
199 ) -> Result<Self::EventLog, Self::Error> {
200 let raw_metadata: Vec<u8> = S::get_metadata().into();
208 let metadata = subxt_metadata::Metadata::decode(&mut raw_metadata.as_slice())
209 .expect("Failed to decode metadata");
210
211 let call = subxt::dynamic::tx(pallet_name, call_name, call_data);
213 let encoded_call = call.encode_call_data(&metadata.into()).map_err(|err| {
214 SandboxErr::new(format!("runtime_call: Error encoding call: {err:?}"))
215 })?;
216
217 let decoded_call =
221 RuntimeCall::<S::Runtime>::decode(&mut encoded_call.as_slice())
222 .expect("Failed to decode runtime call");
223
224 self.sandbox
226 .runtime_call(
227 decoded_call,
228 S::convert_account_to_origin(keypair_to_account(origin)),
229 )
230 .map_err(|err| {
231 SandboxErr::new(format!("runtime_call: execution error {:?}", err.error))
232 })?;
233
234 Ok(())
235 }
236}
237
238#[async_trait]
239impl<
240 AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
241 S: Sandbox,
242 E: Environment<AccountId = AccountId, Balance = ContractsBalanceOf<S::Runtime>>
243 + 'static,
244> BuilderClient<E> for Client<AccountId, S>
245where
246 S::Runtime: pallet_balances::Config + pallet_revive::Config,
247 AccountIdFor<S::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
248 ContractsBalanceOf<S::Runtime>: Send + Sync,
249 ContractsBalanceOf<S::Runtime>: Into<U256> + TryFrom<U256> + Bounded,
250 MomentOf<S::Runtime>: Into<U256>,
251 <<S as Sandbox>::Runtime as frame_system::Config>::Nonce: Into<u32>,
252 <<S as Sandbox>::Runtime as frame_system::Config>::Hash:
254 frame_support::traits::IsType<sp_core::H256>,
255 {
257 async fn bare_instantiate<
258 Contract: Clone,
259 Args: Send + Sync + AbiEncodeWith<Abi> + Clone,
260 R,
261 Abi: Send + Sync + Clone,
262 >(
263 &mut self,
264 contract_name: &str,
265 caller: &Keypair,
266 constructor: &mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
267 value: E::Balance,
268 gas_limit: Weight,
269 storage_deposit_limit: DepositLimit<E::Balance>,
270 ) -> Result<BareInstantiationResult<Self::EventLog>, Self::Error> {
271 let _ =
272 <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
273
274 let _ = self.sandbox.build_block();
276
277 let tracer_type = TracerType::CallTracer(Some(CallTracerConfig::default()));
278 let mut tracer = self.sandbox.evm_tracer(tracer_type);
279
280 let mut code_hash: Option<H256> = None;
281 let result = pallet_revive::tracing::trace(tracer.as_tracing(), || {
282 let code = self.contracts.load_code(contract_name);
283 code_hash = Some(H256(crate::client_utils::code_hash(&code[..])));
284 let data = constructor_exec_input(constructor.clone());
285 self.sandbox.deploy_contract(
286 code,
287 value,
288 data,
289 salt(),
290 caller_to_origin::<S>(caller),
291 gas_limit,
292 storage_deposit_limit,
293 )
294 });
295
296 let addr_raw = match &result.result {
297 Err(err) => {
298 log_error(&format!("Instantiation failed: {err:?}"));
299 return Err(SandboxErr::new(format!("bare_instantiate: {err:?}")));
300 }
301 Ok(res) => res.addr,
302 };
303
304 let trace = match tracer.collect_trace() {
305 Some(Trace::Call(call_trace)) => Some(call_trace),
306 _ => None,
307 };
308
309 Ok(BareInstantiationResult {
310 addr: addr_raw,
311 events: (), trace,
313 code_hash: code_hash.expect("code_hash must have been calculated"),
314 })
315 }
316
317 async fn bare_instantiate_dry_run<
318 Contract: Clone,
319 Args: Send + Sync + AbiEncodeWith<Abi> + Clone,
320 R,
321 Abi: Send + Sync + Clone,
322 >(
323 &mut self,
324 contract_name: &str,
325 caller: &Keypair,
326 constructor: &mut CreateBuilderPartial<E, Contract, Args, R, Abi>,
327 value: E::Balance,
328 storage_deposit_limit: DepositLimit<E::Balance>,
329 ) -> Result<InstantiateDryRunResult<E, Abi>, Self::Error> {
330 let _ =
333 <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
334
335 let code = self.contracts.load_code(contract_name);
336 let data = constructor_exec_input(constructor.clone());
337 let result = self.sandbox.dry_run(|sandbox| {
338 sandbox.deploy_contract(
339 code,
340 value,
341 data,
342 salt(),
343 caller_to_origin::<S>(caller),
344 S::default_gas_limit(),
345 storage_deposit_limit,
346 )
347 });
348
349 let addr_id_raw = match &result.result {
350 Err(err) => {
351 panic!("Instantiate dry-run failed: {err:?}!")
352 }
353 Ok(res) => res.addr,
354 };
355
356 let result = ContractResult::<InstantiateReturnValue, E::Balance> {
357 gas_consumed: result.gas_consumed,
358 gas_required: result.gas_required,
359 storage_deposit: result.storage_deposit,
360 result: result.result.map(|r| {
361 InstantiateReturnValue {
362 result: r.result,
363 addr: addr_id_raw, }
365 }),
366 };
367 Ok(result.into())
368 }
369
370 async fn bare_upload(
371 &mut self,
372 contract_name: &str,
373 caller: &Keypair,
374 storage_deposit_limit: E::Balance,
375 ) -> Result<UploadResult<E, Self::EventLog>, Self::Error> {
376 let code = self.contracts.load_code(contract_name);
377
378 let result = match self.sandbox.upload_contract(
379 code,
380 caller_to_origin::<S>(caller),
381 storage_deposit_limit,
382 ) {
383 Ok(result) => result,
384 Err(err) => {
385 log_error(&format!("Upload failed: {err:?}"));
386 return Err(SandboxErr::new(format!("bare_upload: {err:?}")))
387 }
388 };
389
390 Ok(UploadResult {
391 code_hash: result.code_hash,
392 dry_run: Ok(CodeUploadReturnValue {
393 code_hash: result.code_hash,
394 deposit: result.deposit,
395 }),
396 events: (),
397 })
398 }
399
400 async fn bare_remove_code(
401 &mut self,
402 _caller: &Keypair,
403 _code_hash: H256,
404 ) -> Result<Self::EventLog, Self::Error> {
405 unimplemented!("sandbox does not yet support remove_code")
406 }
407
408 async fn bare_call<
409 Args: Sync + AbiEncodeWith<Abi> + Clone,
410 RetType: Send + DecodeMessageResult<Abi>,
411 Abi: Sync + Clone,
412 >(
413 &mut self,
414 caller: &Keypair,
415 message: &CallBuilderFinal<E, Args, RetType, Abi>,
416 value: E::Balance,
417 gas_limit: Weight,
418 storage_deposit_limit: DepositLimit<E::Balance>,
419 ) -> Result<(Self::EventLog, Option<CallTrace>), Self::Error>
420 where
421 CallBuilderFinal<E, Args, RetType, Abi>: Clone,
422 {
423 let _ =
424 <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
425
426 let addr = *message.clone().params().callee();
428 let exec_input = message.clone().params().exec_input().encode();
429
430 let tracer_type = TracerType::CallTracer(Some(CallTracerConfig::default()));
432 let mut tracer = self.sandbox.evm_tracer(tracer_type);
433 let _result = pallet_revive::tracing::trace(tracer.as_tracing(), || {
434 self.sandbox
435 .call_contract(
436 addr,
437 value,
438 exec_input,
439 caller_to_origin::<S>(caller),
440 gas_limit,
441 storage_deposit_limit,
442 )
443 .result
444 .map_err(|err| SandboxErr::new(format!("bare_call: {err:?}")))
445 })?;
446 let trace = match tracer.collect_trace() {
447 Some(Trace::Call(call_trace)) => Some(call_trace),
448 _ => None,
449 };
450
451 Ok(((), trace))
452 }
453
454 async fn bare_call_dry_run<
455 Args: Sync + AbiEncodeWith<Abi> + Clone,
456 RetType: Send + DecodeMessageResult<Abi>,
457 Abi: Sync + Clone,
458 >(
459 &mut self,
460 caller: &Keypair,
461 message: &CallBuilderFinal<E, Args, RetType, Abi>,
462 value: E::Balance,
463 storage_deposit_limit: DepositLimit<E::Balance>,
464 ) -> Result<CallDryRunResult<E, RetType, Abi>, Self::Error>
465 where
466 CallBuilderFinal<E, Args, RetType, Abi>: Clone,
467 {
468 let _ =
470 <Client<AccountId, S> as BuilderClient<E>>::map_account(self, caller).await;
471
472 let addr = *message.clone().params().callee();
473 let exec_input = message.clone().params().exec_input().encode();
474
475 let result = self.sandbox.dry_run(|sandbox| {
476 sandbox.call_contract(
477 addr,
478 value,
479 exec_input,
480 caller_to_origin::<S>(caller),
481 S::default_gas_limit(),
482 storage_deposit_limit,
483 )
484 });
485 if result.result.is_err() {
486 let res = result.result.clone().unwrap_err();
487 if let DispatchError::Module(m) = res
488 && let Some(s) = m.message
489 && s.contains("AccountUnmapped")
490 {
491 panic!("something is wrong, we mapped the account before")
492 }
493 }
494 Ok(CallDryRunResult {
496 exec_result: ContractExecResultFor::<E> {
497 gas_consumed: result.gas_consumed,
498 gas_required: result.gas_required,
499 storage_deposit: result.storage_deposit,
500 result: result.result,
501 },
502 trace: None, _marker: Default::default(),
504 })
505 }
506
507 async fn map_account_dry_run(&mut self, caller: &Keypair) -> Result<(), Self::Error> {
508 let caller = keypair_to_account(caller);
509 let origin = RawOrigin::Signed(caller);
510 let origin = OriginFor::<S::Runtime>::from(origin);
511
512 self.sandbox
513 .dry_run(|sandbox| sandbox.map_account(origin))
514 .map_err(|err| {
515 SandboxErr::new(format!("map_account_dry_run: execution error {err:?}"))
516 })
517 }
518
519 async fn map_account(&mut self, caller: &Keypair) -> Result<(), Self::Error> {
520 let caller = keypair_to_account(caller);
521 let origin = RawOrigin::Signed(caller);
522 let origin = OriginFor::<S::Runtime>::from(origin);
523
524 self.sandbox.map_account(origin).map_err(|err| {
525 SandboxErr::new(format!("map_account: execution error {err:?}"))
526 })
527 }
528}
529
530impl<
531 AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
532 Config: Sandbox,
533 E: Environment<AccountId = AccountId, Balance = ContractsBalanceOf<Config::Runtime>>
534 + 'static,
535> E2EBackend<E> for Client<AccountId, Config>
536where
537 Config::Runtime: pallet_balances::Config + pallet_revive::Config,
538 AccountIdFor<Config::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
539 ContractsBalanceOf<Config::Runtime>: Send + Sync,
540 ContractsBalanceOf<Config::Runtime>: Into<U256> + TryFrom<U256> + Bounded,
541 MomentOf<Config::Runtime>: Into<U256>,
542 <Config::Runtime as frame_system::Config>::Nonce: Into<u32>,
543 <Config::Runtime as frame_system::Config>::Hash: IsType<sp_core::H256>,
545{
546}
547
548fn keypair_to_account<AccountId: From<[u8; 32]>>(keypair: &Keypair) -> AccountId {
549 AccountId::from(keypair.public_key().0)
550}
551
552fn caller_to_origin<S>(caller: &Keypair) -> OriginFor<S::Runtime>
553where
554 S: Sandbox,
555 S::Runtime: pallet_balances::Config + pallet_revive::Config,
556 AccountIdFor<S::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
557{
558 let caller = keypair_to_account(caller);
559 let origin = RawOrigin::Signed(caller);
560 OriginFor::<S::Runtime>::from(origin)
561}
562
563#[async_trait]
564impl<
565 AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
566 S: Sandbox,
567 E: Environment<AccountId = AccountId, Balance = ContractsBalanceOf<S::Runtime>>
568 + 'static,
569> ContractsBackend<E> for Client<AccountId, S>
570where
571 S::Runtime: pallet_balances::Config + pallet_revive::Config,
572 AccountIdFor<S::Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
573{
574 type Error = SandboxErr;
575 type EventLog = ();
576}
577
578pub mod preset {
580 }