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