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