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