ink_e2e/
subxt_client.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::{
16    builders::{
17        constructor_exec_input,
18        CreateBuilderPartial,
19    },
20    deposit_limit_to_balance,
21    events::{
22        CodeStoredEvent,
23        EventWithTopics,
24    },
25    log_error,
26    log_info,
27    sr25519,
28    InstantiateDryRunResult,
29    Keypair,
30    ReviveApi,
31    H256,
32};
33use crate::{
34    backend::{
35        BuilderClient,
36        ChainBackend,
37    },
38    client_utils::{
39        salt,
40        ContractsRegistry,
41    },
42    contract_results::{
43        BareInstantiationResult,
44        CallDryRunResult,
45        CallResult,
46        ContractResult,
47        UploadResult,
48    },
49    error::DryRunError,
50    events,
51    ContractsBackend,
52    E2EBackend,
53};
54use ink::H160;
55use ink_env::{
56    call::{
57        utils::{
58            ReturnType,
59            Set,
60        },
61        Call,
62        ExecutionInput,
63    },
64    Environment,
65};
66use ink_primitives::{
67    reflect::{
68        AbiDecodeWith,
69        AbiEncodeWith,
70        ScaleEncoding,
71    },
72    types::AccountIdMapper,
73    DepositLimit,
74};
75use jsonrpsee::core::async_trait;
76use pallet_revive::evm::CallTrace;
77use scale::{
78    Decode,
79    Encode,
80};
81use sp_weights::Weight;
82#[cfg(feature = "std")]
83use std::fmt::Debug;
84use std::path::PathBuf;
85use subxt::{
86    blocks::ExtrinsicEvents,
87    config::{
88        DefaultExtrinsicParams,
89        ExtrinsicParams,
90    },
91    error::DispatchError,
92    events::EventDetails,
93    ext::scale_value::{
94        Composite,
95        Value,
96        ValueDef,
97    },
98    tx::Signer,
99};
100
101pub type Error = crate::error::Error<DispatchError>;
102
103/// Represents an initialized contract message builder.
104pub type CallBuilderFinal<E, Args, RetType, Abi> = ink_env::call::CallBuilder<
105    E,
106    Set<Call>,
107    Set<ExecutionInput<Args, Abi>>,
108    Set<ReturnType<RetType>>,
109>;
110
111/// The `Client` takes care of communicating with the node.
112///
113/// This node's RPC interface will be used for instantiating the contract
114/// and interacting with it .
115pub struct Client<C, E>
116where
117    C: subxt::Config,
118    E: Environment,
119{
120    // TODO (@peterwht): make private once call builder supports RLP
121    pub api: ReviveApi<C, E>,
122    pub contracts: ContractsRegistry,
123    url: String,
124}
125
126impl<C, E> Client<C, E>
127where
128    C: subxt::Config,
129    C::AccountId:
130        From<sr25519::PublicKey> + scale::Codec + serde::de::DeserializeOwned + Debug,
131    C::Address: From<sr25519::PublicKey>,
132    C::Signature: From<sr25519::Signature>,
133    <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
134        From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
135
136    E: Environment,
137    E::AccountId: Debug,
138    E::EventRecord: Debug,
139    E::Balance: Debug + scale::HasCompact + serde::Serialize,
140    H256: Debug + scale::Encode,
141{
142    /// Creates a new [`Client`] instance using a `subxt` client.
143    pub async fn new<P: Into<PathBuf>>(
144        client: subxt::backend::rpc::RpcClient,
145        contracts: impl IntoIterator<Item = P>,
146        url: String,
147    ) -> Result<Self, subxt::Error> {
148        Ok(Self {
149            api: ReviveApi::new(client).await?,
150            contracts: ContractsRegistry::new(contracts),
151            url,
152        })
153    }
154
155    // TODO (@peterwht): private after call builder supports RLP
156    /// Executes an `instantiate_with_code` call and captures the resulting events.
157    pub async fn exec_instantiate(
158        &mut self,
159        signer: &Keypair,
160        code: Vec<u8>,
161        data: Vec<u8>,
162        value: E::Balance,
163        gas_limit: Weight,
164        storage_deposit_limit: E::Balance,
165    ) -> Result<BareInstantiationResult<ExtrinsicEvents<C>>, Error> {
166        let salt = salt();
167        // todo remove assert once salt() returns no more option
168        assert!(salt.is_some());
169        let (events, trace) = self
170            .api
171            .instantiate_with_code(
172                value,
173                gas_limit.into(),
174                storage_deposit_limit,
175                code.clone(),
176                data.clone(),
177                salt,
178                signer,
179            )
180            .await;
181
182        for evt in events.iter() {
183            let evt = evt.unwrap_or_else(|err| {
184                panic!("unable to unwrap event: {err:?}");
185            });
186            if is_extrinsic_failed_event(&evt) {
187                let metadata = self.api.client.metadata();
188                let dispatch_error =
189                    subxt::error::DispatchError::decode_from(evt.field_bytes(), metadata)
190                        .map_err(|e| Error::Decoding(e.to_string()))?;
191                log_error(&format!(
192                    "extrinsic for instantiate failed: {dispatch_error}"
193                ));
194                return Err(Error::InstantiateExtrinsic(dispatch_error))
195            }
196        }
197
198        let deployer = self.derive_keypair_address(signer);
199        let addr = pallet_revive::create2(
200            &deployer,
201            &code[..],
202            &data[..],
203            &salt.expect("todo make salt() return no option, but value"),
204        );
205
206        Ok(BareInstantiationResult {
207            // The `account_id` must exist at this point. If the instantiation fails
208            // the dry-run must already return that.
209            addr,
210            events,
211            trace,
212            code_hash: H256(crate::client_utils::code_hash(&code[..])),
213        })
214    }
215
216    /// Executes an `upload` call and captures the resulting events.
217    async fn exec_upload(
218        &mut self,
219        signer: &Keypair,
220        code: Vec<u8>,
221        _storage_deposit_limit: E::Balance,
222    ) -> Result<UploadResult<E, ExtrinsicEvents<C>>, Error> {
223        // todo
224        let storage_deposit_limit: E::Balance = unsafe {
225            core::mem::transmute_copy::<u128, <E as Environment>::Balance>(&u128::MAX)
226        };
227        let dry_run = self
228            .api
229            .upload_dry_run(signer, code.clone(), storage_deposit_limit)
230            .await;
231        log_info(&format!("upload dry run: {dry_run:?}"));
232        if let Err(err) = dry_run {
233            let dispatch_err = self.runtime_dispatch_error_to_subxt_dispatch_error(&err);
234            return Err(Error::UploadDryRun(dispatch_err))
235        }
236
237        let tx_events = self.api.upload(signer, code, storage_deposit_limit).await;
238
239        let mut hash = None;
240        for evt in tx_events.iter() {
241            let evt = evt.unwrap_or_else(|err| {
242                panic!("unable to unwrap event: {err:?}");
243            });
244
245            if let Some(uploaded) =
246                evt.as_event::<CodeStoredEvent>().unwrap_or_else(|err| {
247                    panic!("event conversion to `Uploaded` failed: {err:?}");
248                })
249            {
250                log_info(&format!(
251                    "contract was uploaded with hash {:?}",
252                    uploaded.code_hash
253                ));
254                hash = Some(uploaded.code_hash);
255                break
256            } else if is_extrinsic_failed_event(&evt) {
257                let metadata = self.api.client.metadata();
258                let dispatch_error =
259                    DispatchError::decode_from(evt.field_bytes(), metadata)
260                        .map_err(|e| Error::Decoding(e.to_string()))?;
261
262                log_error(&format!("extrinsic for upload failed: {dispatch_error}"));
263                return Err(Error::UploadExtrinsic(dispatch_error))
264            }
265        }
266
267        // todo still up to date?
268        // The `pallet-revive` behavior is that if the code was already stored on the
269        // chain we won't get an event with the hash, but the extrinsic will still
270        // succeed. We then don't error (`cargo-contract` would), but instead
271        // return the hash from the dry-run.
272        let code_hash = match hash {
273            Some(hash) => hash,
274            None => {
275                dry_run
276                    .as_ref()
277                    .unwrap_or_else(|err| panic!("must have worked: {err:?}"))
278                    .code_hash
279            }
280        };
281
282        Ok(UploadResult {
283            dry_run,
284            code_hash,
285            events: tx_events,
286        })
287    }
288
289    /// todo check if comment still holds
290    /// Transforms a [`ContractResult`] from a dry run into a [`Result`] type, containing
291    /// details of the [`DispatchError`] if the dry run failed.
292    #[allow(clippy::type_complexity)]
293    fn contract_result_to_result<V>(
294        &self,
295        contract_result: ContractResult<V, E::Balance>,
296    ) -> Result<ContractResult<V, E::Balance>, DryRunError<DispatchError>> {
297        if let Err(error) = contract_result.result {
298            let subxt_dispatch_err =
299                self.runtime_dispatch_error_to_subxt_dispatch_error(&error);
300            Err(DryRunError::<DispatchError> {
301                error: subxt_dispatch_err,
302            })
303        } else {
304            Ok(contract_result)
305        }
306    }
307
308    /// Converts a `sp_runtime::DispatchError` into a `DispatchError` which contains error
309    /// details.
310    fn runtime_dispatch_error_to_subxt_dispatch_error(
311        &self,
312        dispatch_error: &sp_runtime::DispatchError,
313    ) -> DispatchError {
314        let dispatch_err_encoded = Encode::encode(&dispatch_error);
315        DispatchError::decode_from(dispatch_err_encoded, self.api.client.metadata())
316            .expect("failed to decode valid dispatch error")
317    }
318
319    /// Returns the URL of the running node.
320    pub fn url(&self) -> &str {
321        &self.url
322    }
323
324    /// Derives the Ethereum address from a keypair.
325    // copied from `pallet-revive`
326    fn derive_keypair_address(&self, signer: &Keypair) -> H160 {
327        let account_id = <Keypair as subxt::tx::Signer<C>>::account_id(signer);
328        let account_bytes = account_id.encode();
329        AccountIdMapper::to_address(account_bytes.as_ref())
330    }
331
332    /// Returns the original mapped `AccountId32` for a `H160`.
333    async fn fetch_original_account(
334        &self,
335        addr: &H160,
336    ) -> Result<Option<C::AccountId>, Error> {
337        let original_account_entry = subxt::dynamic::storage(
338            "Revive",
339            "OriginalAccount",
340            vec![Value::from_bytes(addr)],
341        );
342        let best_block = self.api.best_block().await;
343        let raw_value = self
344            .api
345            .client
346            .storage()
347            .at(best_block)
348            .fetch(&original_account_entry)
349            .await
350            .map_err(|err| {
351                Error::Other(format!("Unable to fetch original account: {err:?}"))
352            })?;
353        Ok(match raw_value {
354            Some(value) => {
355                let raw_account_id = value.as_type::<[u8; 32]>().map_err(|err| {
356                    Error::Decoding(format!("unable to deserialize AccountId: {}", err))
357                })?;
358                let account: C::AccountId = Decode::decode(&mut &raw_account_id[..])
359                    .map_err(|err| {
360                        Error::Decoding(format!("unable to decode AccountId: {}", err))
361                    })?;
362                Some(account)
363            }
364            None => None,
365        })
366    }
367}
368
369#[async_trait]
370impl<C, E> ChainBackend for Client<C, E>
371where
372    C: subxt::Config + Send + Sync,
373    C::AccountId: Clone
374        + Debug
375        + Send
376        + Sync
377        + core::fmt::Display
378        + scale::Codec
379        + From<sr25519::PublicKey>
380        + serde::de::DeserializeOwned,
381    C::Address: From<sr25519::PublicKey>,
382    C::Signature: From<sr25519::Signature>,
383    C::Address: Send + Sync,
384    <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
385        From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
386
387    E: Environment,
388    E::AccountId: Debug + Send + Sync,
389    E::Balance: Clone
390        + Debug
391        + Send
392        + Sync
393        + TryFrom<u128>
394        + scale::HasCompact
395        + serde::Serialize,
396    E::EventRecord: Debug,
397{
398    type AccountId = E::AccountId;
399    type Balance = E::Balance;
400    type Error = Error;
401    type EventLog = ExtrinsicEvents<C>;
402
403    async fn create_and_fund_account(
404        &mut self,
405        origin: &Keypair,
406        amount: Self::Balance,
407    ) -> Keypair {
408        let (_, phrase, _) =
409            <sp_core::sr25519::Pair as sp_core::Pair>::generate_with_phrase(None);
410        let phrase =
411            subxt_signer::bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
412        let keypair = Keypair::from_phrase(&phrase, None).expect("valid phrase expected");
413        let account_id = <Keypair as Signer<C>>::account_id(&keypair);
414        let origin_account_id = origin.public_key().to_account_id();
415
416        self.api
417            .try_transfer_balance(origin, account_id.clone(), amount)
418            .await
419            .unwrap_or_else(|err| {
420                panic!(
421                    "transfer from {} to {} failed with {:?}",
422                    origin_account_id, account_id, err
423                )
424            });
425
426        log_info(&format!(
427            "transfer from {} to {} succeeded",
428            origin_account_id, account_id,
429        ));
430
431        keypair
432    }
433
434    async fn free_balance(
435        &mut self,
436        account: Self::AccountId,
437    ) -> Result<Self::Balance, Self::Error> {
438        let account_addr = subxt::dynamic::storage(
439            "System",
440            "Account",
441            vec![
442                // Something that encodes to an AccountId32 is what we need for the map
443                // key here:
444                Value::from_bytes(&account),
445            ],
446        );
447
448        let best_block = self.api.best_block().await;
449
450        let account = self
451            .api
452            .client
453            .storage()
454            .at(best_block)
455            .fetch_or_default(&account_addr)
456            .await
457            .unwrap_or_else(|err| {
458                panic!("unable to fetch balance: {err:?}");
459            })
460            .to_value()
461            .unwrap_or_else(|err| {
462                panic!("unable to decode account info: {err:?}");
463            });
464
465        let account_data = get_composite_field_value(&account, "data")?;
466        let balance = get_composite_field_value(account_data, "free")?;
467        let balance = balance.as_u128().ok_or_else(|| {
468            Error::Balance(format!("{balance:?} should convert to u128"))
469        })?;
470        let balance = E::Balance::try_from(balance).map_err(|_| {
471            Error::Balance(format!("{balance:?} failed to convert from u128"))
472        })?;
473
474        log_info(&format!("balance of contract {account:?} is {balance:?}"));
475        Ok(balance)
476    }
477
478    async fn runtime_call<'a>(
479        &mut self,
480        origin: &Keypair,
481        pallet_name: &'a str,
482        call_name: &'a str,
483        call_data: Vec<Value>,
484    ) -> Result<Self::EventLog, Self::Error> {
485        let tx_events = self
486            .api
487            .runtime_call(origin, pallet_name, call_name, call_data)
488            .await;
489
490        for evt in tx_events.iter() {
491            let evt = evt.unwrap_or_else(|err| {
492                panic!("unable to unwrap event: {err:?}");
493            });
494
495            if is_extrinsic_failed_event(&evt) {
496                let metadata = self.api.client.metadata();
497                let dispatch_error =
498                    subxt::error::DispatchError::decode_from(evt.field_bytes(), metadata)
499                        .map_err(|e| Error::Decoding(e.to_string()))?;
500
501                log_error(&format!("extrinsic for call failed: {dispatch_error}"));
502                return Err(Error::CallExtrinsic(dispatch_error))
503            }
504        }
505
506        Ok(tx_events)
507    }
508}
509
510#[async_trait]
511impl<C, E> BuilderClient<E> for Client<C, E>
512where
513    C: subxt::Config + Send + Sync,
514    C::AccountId: Clone
515        + Debug
516        + Send
517        + Sync
518        + core::fmt::Display
519        + scale::Codec
520        + From<sr25519::PublicKey>
521        + serde::de::DeserializeOwned,
522    C::Address: From<sr25519::PublicKey>,
523    C::Signature: From<sr25519::Signature>,
524    C::Address: Send + Sync,
525    <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
526        From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
527
528    E: Environment,
529    E::AccountId: Debug + Send + Sync,
530    E::EventRecord: Debug,
531    E::Balance:
532        Clone + Debug + Send + Sync + From<u128> + scale::HasCompact + serde::Serialize,
533    H256: Debug + Send + Sync + scale::Encode,
534{
535    async fn bare_instantiate<
536        Contract: Clone,
537        Args: Send + Sync + AbiEncodeWith<ScaleEncoding> + Clone,
538        R,
539    >(
540        &mut self,
541        contract_name: &str,
542        caller: &Keypair,
543        constructor: &mut CreateBuilderPartial<E, Contract, Args, R>,
544        value: E::Balance,
545        gas_limit: Weight,
546        storage_deposit_limit: DepositLimit<E::Balance>,
547    ) -> Result<BareInstantiationResult<Self::EventLog>, Self::Error> {
548        let code = self.contracts.load_code(contract_name);
549        let data = constructor_exec_input(constructor.clone());
550        let storage_deposit_limit = deposit_limit_to_balance::<E>(storage_deposit_limit);
551        let ret = self
552            .exec_instantiate(caller, code, data, value, gas_limit, storage_deposit_limit)
553            .await?;
554        log_info(&format!("instantiated contract at {:?}", ret.addr));
555        Ok(ret)
556    }
557
558    async fn bare_instantiate_dry_run<
559        Contract: Clone,
560        Args: Send + Sync + AbiEncodeWith<ScaleEncoding> + Clone,
561        R,
562    >(
563        &mut self,
564        contract_name: &str,
565        caller: &Keypair,
566        constructor: &mut CreateBuilderPartial<E, Contract, Args, R>,
567        value: E::Balance,
568        storage_deposit_limit: DepositLimit<E::Balance>,
569    ) -> Result<InstantiateDryRunResult<E>, Self::Error> {
570        // todo beware side effect! this is wrong, we have to batch up the `map_account`
571        // into the RPC dry run instead
572        let _ = self.map_account(caller).await;
573
574        let code = self.contracts.load_code(contract_name);
575        let data = constructor_exec_input(constructor.clone());
576
577        let result = self
578            .api
579            .instantiate_with_code_dry_run(
580                value,
581                storage_deposit_limit,
582                code,
583                data,
584                salt(),
585                caller,
586            )
587            .await;
588
589        log_info(&format!("instantiate dry run: {:?}", &result.result));
590        let result = self
591            .contract_result_to_result(result)
592            .map_err(Error::InstantiateDryRun)?;
593
594        /*
595        if let Ok(res) = result.result.clone() {
596            if res.result.did_revert() {
597                return Err(Self::Error::InstantiateDryRunReverted(DryRunRevert {
598                    error: res.result.data,
599                }));
600            }
601        }
602         */
603
604        Ok(result.into())
605    }
606
607    async fn bare_upload(
608        &mut self,
609        contract_name: &str,
610        caller: &Keypair,
611        storage_deposit_limit: E::Balance,
612    ) -> Result<UploadResult<E, Self::EventLog>, Self::Error> {
613        let code = self.contracts.load_code(contract_name);
614        let ret = self
615            .exec_upload(caller, code, storage_deposit_limit)
616            .await?;
617        log_info(&format!("contract stored with hash {:?}", ret.code_hash));
618        Ok(ret)
619    }
620
621    async fn bare_remove_code(
622        &mut self,
623        caller: &Keypair,
624        code_hash: H256,
625    ) -> Result<Self::EventLog, Self::Error> {
626        let tx_events = self.api.remove_code(caller, code_hash).await;
627
628        for evt in tx_events.iter() {
629            let evt = evt.unwrap_or_else(|err| {
630                panic!("unable to unwrap event: {err:?}");
631            });
632
633            if is_extrinsic_failed_event(&evt) {
634                let metadata = self.api.client.metadata();
635                let dispatch_error =
636                    DispatchError::decode_from(evt.field_bytes(), metadata)
637                        .map_err(|e| Error::Decoding(e.to_string()))?;
638                return Err(Error::RemoveCodeExtrinsic(dispatch_error))
639            }
640        }
641
642        Ok(tx_events)
643    }
644
645    async fn bare_call<
646        Args: Sync + AbiEncodeWith<Abi> + Clone,
647        RetType: Send + AbiDecodeWith<Abi>,
648        Abi: Sync + Clone,
649    >(
650        &mut self,
651        caller: &Keypair,
652        message: &CallBuilderFinal<E, Args, RetType, Abi>,
653        value: E::Balance,
654        gas_limit: Weight,
655        storage_deposit_limit: DepositLimit<E::Balance>,
656    ) -> Result<(Self::EventLog, Option<CallTrace>), Self::Error>
657    where
658        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
659    {
660        let addr = *message.clone().params().callee();
661        let exec_input = message.clone().params().exec_input().encode();
662        log_info(&format!("call: {:02X?}", exec_input));
663
664        let (tx_events, trace) = self
665            .api
666            .call(
667                addr,
668                value,
669                gas_limit.into(),
670                deposit_limit_to_balance::<E>(storage_deposit_limit),
671                exec_input,
672                caller,
673            )
674            .await;
675
676        for evt in tx_events.iter() {
677            let evt = evt.unwrap_or_else(|err| {
678                panic!("unable to unwrap event: {err:?}");
679            });
680
681            if is_extrinsic_failed_event(&evt) {
682                let metadata = self.api.client.metadata();
683                let dispatch_error =
684                    DispatchError::decode_from(evt.field_bytes(), metadata)
685                        .map_err(|e| Error::Decoding(e.to_string()))?;
686                log_error(&format!("extrinsic for call failed: {dispatch_error}"));
687                return Err(Error::CallExtrinsic(dispatch_error))
688            }
689        }
690
691        Ok((tx_events, trace))
692    }
693
694    // todo is not really a `bare_call`
695    async fn bare_call_dry_run<
696        Args: Sync + AbiEncodeWith<Abi> + Clone,
697        RetType: Send + AbiDecodeWith<Abi>,
698        Abi: Sync + Clone,
699    >(
700        &mut self,
701        caller: &Keypair,
702        message: &CallBuilderFinal<E, Args, RetType, Abi>,
703        value: E::Balance,
704        storage_deposit_limit: DepositLimit<E::Balance>,
705    ) -> Result<CallDryRunResult<E, RetType>, Self::Error>
706    where
707        CallBuilderFinal<E, Args, RetType, Abi>: Clone,
708    {
709        // todo beware side effect! this is wrong, we have to batch up the `map_account`
710        // into the RPC dry run instead
711        let _ = self.map_account(caller).await;
712
713        let dest = *message.clone().params().callee();
714        let exec_input = message.clone().params().exec_input().encode();
715
716        let (exec_result, trace) = self
717            .api
718            .call_dry_run(
719                Signer::<C>::account_id(caller), /* todo this param is not necessary,
720                                                  * because the last argument is the
721                                                  * caller and this value can be
722                                                  * created in the function */
723                dest,
724                exec_input,
725                value,
726                deposit_limit_to_balance::<E>(storage_deposit_limit),
727                caller,
728            )
729            .await;
730        log_info(&format!("call dry run result: {:?}", &exec_result.result));
731
732        let exec_result = self
733            .contract_result_to_result(exec_result)
734            .map_err(Error::CallDryRun)?;
735
736        Ok(CallDryRunResult {
737            exec_result,
738            trace,
739            _marker: Default::default(),
740        })
741    }
742
743    async fn map_account(&mut self, caller: &Keypair) -> Result<(), Self::Error> {
744        let addr = self.derive_keypair_address(caller);
745        if self.fetch_original_account(&addr).await?.is_some() {
746            return Ok(());
747        }
748        let tx_events = self.api.map_account(caller).await;
749
750        for evt in tx_events.iter() {
751            let evt = evt.unwrap_or_else(|err| {
752                panic!("unable to unwrap event: {err:?}");
753            });
754
755            if is_extrinsic_failed_event(&evt) {
756                let metadata = self.api.client.metadata();
757                let dispatch_error =
758                    DispatchError::decode_from(evt.field_bytes(), metadata)
759                        .map_err(|e| Error::Decoding(e.to_string()))?;
760                log_error(&format!("extrinsic for call failed: {dispatch_error}"));
761                return Err(Error::CallExtrinsic(dispatch_error))
762            }
763        }
764
765        // todo: Ok(tx_events)
766        Ok(())
767    }
768
769    // todo not used anywhere
770    // code is also not dry
771    async fn map_account_dry_run(&mut self, caller: &Keypair) -> Result<(), Self::Error> {
772        let tx_events = self.api.map_account(caller).await;
773
774        for evt in tx_events.iter() {
775            let evt = evt.unwrap_or_else(|err| {
776                panic!("unable to unwrap event: {err:?}");
777            });
778
779            if is_extrinsic_failed_event(&evt) {
780                let metadata = self.api.client.metadata();
781                let dispatch_error =
782                    DispatchError::decode_from(evt.field_bytes(), metadata)
783                        .map_err(|e| Error::Decoding(e.to_string()))?;
784                log_error(&format!("extrinsic for call failed: {dispatch_error}"));
785                return Err(Error::CallExtrinsic(dispatch_error))
786            }
787        }
788
789        Ok(())
790    }
791}
792
793impl<C, E> ContractsBackend<E> for Client<C, E>
794where
795    C: subxt::Config + Send + Sync,
796    C::AccountId: Clone
797        + Debug
798        + Send
799        + Sync
800        + core::fmt::Display
801        + scale::Codec
802        + From<sr25519::PublicKey>
803        + serde::de::DeserializeOwned,
804    C::Address: From<sr25519::PublicKey>,
805    C::Signature: From<sr25519::Signature>,
806    C::Address: Send + Sync,
807
808    E: Environment,
809    E::AccountId: Debug + Send + Sync,
810    E::Balance:
811        Clone + Debug + Send + Sync + From<u128> + scale::HasCompact + serde::Serialize,
812    H256: Debug + Send + scale::Encode,
813{
814    type Error = Error;
815    type EventLog = ExtrinsicEvents<C>;
816}
817
818impl<C, E> E2EBackend<E> for Client<C, E>
819where
820    C: subxt::Config + Send + Sync,
821    C::AccountId: Clone
822        + Debug
823        + Send
824        + Sync
825        + core::fmt::Display
826        + scale::Codec
827        + From<sr25519::PublicKey>
828        + serde::de::DeserializeOwned,
829    C::Address: From<sr25519::PublicKey>,
830    C::Signature: From<sr25519::Signature>,
831    C::Address: Send + Sync,
832    <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
833        From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
834
835    E: Environment,
836    E::AccountId: Debug + Send + Sync,
837    E::EventRecord: Debug,
838    E::Balance:
839        Clone + Debug + Send + Sync + From<u128> + scale::HasCompact + serde::Serialize,
840    H256: Debug + Send + Sync + scale::Encode,
841{
842}
843
844/// Try to extract the given field from a dynamic [`Value`].
845///
846/// Returns `Err` if:
847///   - The value is not a [`Value::Composite`] with [`Composite::Named`] fields
848///   - The value does not contain a field with the given name.
849fn get_composite_field_value<'a, T>(
850    value: &'a Value<T>,
851    field_name: &str,
852) -> Result<&'a Value<T>, Error> {
853    if let ValueDef::Composite(Composite::Named(fields)) = &value.value {
854        let (_, field) = fields
855            .iter()
856            .find(|(name, _)| name == field_name)
857            .ok_or_else(|| {
858                Error::Balance(format!("No field named '{field_name}' found"))
859            })?;
860        Ok(field)
861    } else {
862        Err(Error::Balance(
863            "Expected a composite type with named fields".into(),
864        ))
865    }
866}
867
868/// Returns true if the give event is System::Extrinsic failed.
869fn is_extrinsic_failed_event<C: subxt::Config>(event: &EventDetails<C>) -> bool {
870    event.pallet_name() == "System" && event.variant_name() == "ExtrinsicFailed"
871}
872
873impl<E: Environment, V, C: subxt::Config> CallResult<E, V, ExtrinsicEvents<C>> {
874    /// Returns true if the specified event was triggered by the call.
875    pub fn contains_event(&self, pallet_name: &str, variant_name: &str) -> bool {
876        self.events.iter().any(|event| {
877            let event = event.unwrap();
878            eprintln!(
879                "pallet: {:?}, variant: {:?}",
880                event.pallet_name(),
881                event.variant_name()
882            );
883            event.pallet_name() == pallet_name && event.variant_name() == variant_name
884        })
885    }
886
887    /// Returns all the `ContractEmitted` events emitted by the contract.
888    #[allow(clippy::result_large_err)] // todo
889    pub fn contract_emitted_events(
890        &self,
891    ) -> Result<Vec<EventWithTopics<events::ContractEmitted>>, subxt::Error>
892    where
893        C::Hash: Into<sp_core::H256>,
894    {
895        let mut events_with_topics = Vec::new();
896        for event in self.events.iter() {
897            let event = event?;
898            if let Some(decoded_event) = event.as_event::<events::ContractEmitted>()? {
899                let topics = decoded_event.topics.clone();
900                let event_with_topics = EventWithTopics {
901                    event: decoded_event,
902                    topics,
903                };
904                events_with_topics.push(event_with_topics);
905            }
906        }
907        Ok(events_with_topics)
908    }
909}