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