ink_e2e/
xts.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    Keypair,
17    log_info,
18    sr25519,
19};
20use ink_env::Environment;
21
22use crate::contract_results::{
23    ContractExecResultFor,
24    ContractInstantiateResultFor,
25};
26use core::marker::PhantomData;
27use funty::Fundamental;
28use ink_primitives::{
29    Address,
30    DepositLimit,
31};
32use pallet_revive::{
33    CodeUploadResult,
34    evm::{
35        CallTrace,
36        CallTracerConfig,
37        Trace,
38        TracerType,
39    },
40};
41use sp_core::H256;
42use sp_runtime::OpaqueExtrinsic;
43use subxt::{
44    OnlineClient,
45    backend::{
46        legacy::LegacyRpcMethods,
47        rpc::RpcClient,
48    },
49    blocks::ExtrinsicEvents,
50    config::{
51        DefaultExtrinsicParams,
52        DefaultExtrinsicParamsBuilder,
53        ExtrinsicParams,
54        HashFor,
55        Header,
56    },
57    ext::{
58        scale_encode,
59        subxt_core::tx::Transaction,
60    },
61    tx::{
62        Signer,
63        SubmittableTransaction,
64        TxStatus,
65    },
66};
67
68/// Copied from `sp_weight` to additionally implement `scale_encode::EncodeAsType`.
69#[derive(
70    Copy,
71    Clone,
72    Eq,
73    PartialEq,
74    Debug,
75    Default,
76    scale::Encode,
77    scale::Decode,
78    scale::MaxEncodedLen,
79    scale_encode::EncodeAsType,
80)]
81#[encode_as_type(crate_path = "subxt::ext::scale_encode")]
82pub struct Weight {
83    #[codec(compact)]
84    /// The weight of computational time used based on some reference hardware.
85    ref_time: u64,
86    #[codec(compact)]
87    /// The weight of storage space used by proof of validity.
88    proof_size: u64,
89}
90
91impl From<sp_weights::Weight> for Weight {
92    fn from(weight: sp_weights::Weight) -> Self {
93        Self {
94            ref_time: weight.ref_time(),
95            proof_size: weight.proof_size(),
96        }
97    }
98}
99
100impl From<Weight> for sp_weights::Weight {
101    fn from(weight: Weight) -> Self {
102        sp_weights::Weight::from_parts(weight.ref_time, weight.proof_size)
103    }
104}
105
106/// A raw call to `pallet-revive`'s `instantiate_with_code`.
107///
108/// See <https://github.com/use-ink/polkadot-sdk/blob/c40b36c3a7c208f9a6837b80812473af3d9ba7f7/substrate/frame/revive/src/lib.rs>.
109#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
110#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
111pub struct InstantiateWithCode<E: Environment> {
112    #[codec(compact)]
113    value: E::Balance,
114    gas_limit: Weight,
115    #[codec(compact)]
116    storage_deposit_limit: E::Balance,
117    code: Vec<u8>,
118    data: Vec<u8>,
119    salt: Option<[u8; 32]>,
120}
121
122/// A raw call to `pallet-revive`'s `call`.
123///
124/// See <https://github.com/use-ink/polkadot-sdk/blob/c40b36c3a7c208f9a6837b80812473af3d9ba7f7/substrate/frame/revive/src/lib.rs>.
125#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
126#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
127pub struct Call<E: Environment> {
128    dest: Address,
129    #[codec(compact)]
130    value: E::Balance,
131    gas_limit: Weight,
132    #[codec(compact)]
133    storage_deposit_limit: E::Balance,
134    data: Vec<u8>,
135}
136
137/// A raw call to `pallet-revive`'s `map_account`.
138///
139/// See <https://github.com/use-ink/polkadot-sdk/blob/c40b36c3a7c208f9a6837b80812473af3d9ba7f7/substrate/frame/revive/src/lib.rs>.
140#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
141#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
142pub struct MapAccount {}
143
144/// A raw call to `pallet-balances`'s `transfer`.
145#[derive(Debug, scale::Decode, scale::Encode, scale_encode::EncodeAsType)]
146#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
147pub struct Transfer<E: Environment, C: subxt::Config> {
148    dest: subxt::utils::Static<C::Address>,
149    #[codec(compact)]
150    value: E::Balance,
151}
152
153/// A raw call to `pallet-revive`'s `remove_code`.
154///
155/// See <https://github.com/use-ink/polkadot-sdk/blob/c40b36c3a7c208f9a6837b80812473af3d9ba7f7/substrate/frame/revive/src/lib.rs>.
156#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
157#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
158pub struct RemoveCode {
159    code_hash: H256,
160}
161
162/// A raw call to `pallet-revive`'s `upload_code`.
163///
164/// See <https://github.com/use-ink/polkadot-sdk/blob/c40b36c3a7c208f9a6837b80812473af3d9ba7f7/substrate/frame/revive/src/lib.rs>.
165#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
166#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
167pub struct UploadCode<E: Environment> {
168    code: Vec<u8>,
169    #[codec(compact)]
170    storage_deposit_limit: E::Balance,
171}
172
173/// A struct that encodes RPC parameters required to instantiate a new smart contract.
174#[derive(scale::Encode)]
175struct RpcInstantiateRequest<C: subxt::Config, E: Environment> {
176    origin: C::AccountId,
177    value: E::Balance,
178    gas_limit: Option<Weight>,
179    storage_deposit_limit: Option<E::Balance>,
180    code: Code,
181    data: Vec<u8>,
182    salt: Option<[u8; 32]>,
183}
184
185/// A struct that encodes RPC parameters required to upload a new smart contract.
186#[derive(scale::Encode)]
187struct RpcCodeUploadRequest<C: subxt::Config, E: Environment>
188where
189    E::Balance: serde::Serialize,
190{
191    origin: C::AccountId,
192    code: Vec<u8>,
193    storage_deposit_limit: Option<E::Balance>,
194}
195
196/// A struct that encodes RPC parameters required for a call to a smart contract.
197#[derive(scale::Encode)]
198struct RpcCallRequest<C: subxt::Config, E: Environment> {
199    origin: C::AccountId,
200    dest: Address,
201    value: E::Balance,
202    gas_limit: Option<Weight>,
203    storage_deposit_limit: DepositLimit<E::Balance>,
204    input_data: Vec<u8>,
205}
206
207/// Reference to an existing code hash or a new contract binary.
208#[derive(scale::Encode)]
209enum Code {
210    /// A contract binary as raw bytes.
211    Upload(Vec<u8>),
212    #[allow(unused)]
213    /// The code hash of an on-chain contract blob.
214    Existing(H256),
215}
216
217/// Provides functions for interacting with the `pallet-revive` API.
218pub struct ReviveApi<C: subxt::Config, E: Environment> {
219    pub rpc: LegacyRpcMethods<C>,
220    pub client: OnlineClient<C>,
221    _phantom: PhantomData<fn() -> (C, E)>,
222}
223
224impl<C, E> ReviveApi<C, E>
225where
226    C: subxt::Config,
227    C::AccountId: From<sr25519::PublicKey> + serde::de::DeserializeOwned + scale::Codec,
228    C::Address: From<sr25519::PublicKey>,
229    C::Signature: From<sr25519::Signature>,
230    <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
231        From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
232    E: Environment,
233    E::Balance: scale::HasCompact + serde::Serialize + std::fmt::Debug,
234{
235    /// Creates a new [`ReviveApi`] instance.
236    pub async fn new(rpc: RpcClient) -> Result<Self, subxt::Error> {
237        let client = OnlineClient::<C>::from_rpc_client(rpc.clone()).await?;
238        let rpc = LegacyRpcMethods::<C>::new(rpc);
239        Ok(Self {
240            rpc,
241            client,
242            _phantom: Default::default(),
243        })
244    }
245
246    /// Attempt to transfer the `value` from `origin` to `dest`.
247    ///
248    /// Returns `Ok` on success, and a [`subxt::Error`] if the extrinsic is
249    /// invalid (e.g. out of date nonce)
250    pub async fn transfer_allow_death(
251        &self,
252        origin: &Keypair,
253        dest: C::AccountId,
254        value: E::Balance,
255    ) -> Result<(), subxt::Error> {
256        let call = subxt::tx::DefaultPayload::new(
257            "Balances",
258            "transfer_allow_death",
259            Transfer::<E, C> {
260                dest: subxt::utils::Static(dest.into()),
261                value,
262            },
263        )
264        .unvalidated();
265
266        let _ = self.submit_extrinsic(&call, origin).await;
267
268        Ok(())
269    }
270
271    /// Dry runs the instantiation of the given `code`.
272    pub async fn instantiate_with_code_dry_run(
273        &self,
274        value: E::Balance,
275        storage_deposit_limit: DepositLimit<E::Balance>,
276        code: Vec<u8>,
277        data: Vec<u8>,
278        salt: Option<[u8; 32]>,
279        signer: &Keypair,
280    ) -> ContractInstantiateResultFor<E> {
281        let code = Code::Upload(code);
282        let storage_deposit_limit = match storage_deposit_limit {
283            DepositLimit::Balance(v) => Some(v),
284            DepositLimit::UnsafeOnlyForDryRun => None,
285        };
286        let call_request = RpcInstantiateRequest::<C, E> {
287            origin: Signer::<C>::account_id(signer),
288            value,
289            gas_limit: None,
290            storage_deposit_limit,
291            code,
292            data,
293            salt,
294        };
295        let func = "ReviveApi_instantiate";
296        let params = scale::Encode::encode(&call_request);
297        let bytes = self
298            .rpc
299            .state_call(func, Some(&params), None)
300            .await
301            .unwrap_or_else(|err| {
302                panic!("error on ws request `revive_instantiate`: {err:?}");
303            });
304        scale::Decode::decode(&mut bytes.as_ref()).unwrap_or_else(|err| {
305            panic!("decoding `ContractInstantiateResult` failed: {err}")
306        })
307    }
308
309    /// todo
310    pub async fn create_extrinsic<Call>(
311        &self,
312        call: &Call,
313        signer: &Keypair,
314    ) -> SubmittableTransaction<C, OnlineClient<C>>
315    where
316        Call: subxt::tx::Payload,
317    {
318        let account_id = <Keypair as Signer<C>>::account_id(signer);
319        let account_nonce =
320            self.get_account_nonce(&account_id)
321                .await
322                .unwrap_or_else(|err| {
323                    panic!("error calling `get_account_nonce`: {err:?}");
324                });
325
326        let params = DefaultExtrinsicParamsBuilder::new()
327            .nonce(account_nonce)
328            .build();
329        self.client
330            .tx()
331            .create_partial_offline(call, params.into())
332            .unwrap_or_else(|err| {
333                panic!("error on call `create_signed_with_nonce`: {err:?}");
334            })
335            .sign(signer)
336    }
337
338    /// Sign and submit an extrinsic with the given call payload.
339    pub async fn submit_extrinsic<Call>(
340        &self,
341        call: &Call,
342        signer: &Keypair,
343    ) -> (ExtrinsicEvents<C>, Option<CallTrace>)
344    where
345        Call: subxt::tx::Payload,
346    {
347        // we have to retrieve the current block hash here. we use it later in this
348        // function when retrieving the log. the extrinsic is dry-run for tracing
349        // the log. if we were to use the latest block the extrinsic would already
350        // have been executed and we would get an error.
351        let parent_hash = self.best_block().await;
352
353        let mut tx = self
354            .create_extrinsic(call, signer)
355            .await
356            .submit_and_watch()
357            .await
358            .inspect(|tx_progress| {
359                log_info(&format!(
360                    "signed and submitted tx with hash {:?}",
361                    tx_progress.extrinsic_hash()
362                ));
363            })
364            .unwrap_or_else(|err| {
365                panic!("error on call `submit_and_watch`: {err:?}");
366            });
367
368        // Below we use the low level API to replicate the `wait_for_in_block` behaviour
369        // which was removed in subxt 0.33.0. See https://github.com/paritytech/subxt/pull/1237.
370        //
371        // We require this because we use `ink-node` as our development
372        // node, which does not currently support finality, so we just want to
373        // wait until it is included in a block.
374        while let Some(status) = tx.next().await {
375            match status.unwrap_or_else(|err| {
376                panic!("error subscribing to tx status: {err:?}");
377            }) {
378                TxStatus::InBestBlock(tx_in_block)
379                | TxStatus::InFinalizedBlock(tx_in_block) => {
380                    let events = tx_in_block.fetch_events().await.unwrap_or_else(|err| {
381                        panic!("error on call `fetch_events`: {err:?}");
382                    });
383                    let trace = self
384                        .trace(
385                            tx_in_block.block_hash(),
386                            Some(tx_in_block.extrinsic_hash()),
387                            parent_hash,
388                            None,
389                        )
390                        .await;
391                    return (events, trace)
392                }
393                TxStatus::Error { message } => {
394                    panic!("TxStatus::Error: {message:?}");
395                }
396                TxStatus::Invalid { message } => {
397                    panic!("TxStatus::Invalid: {message:?}");
398                }
399                TxStatus::Dropped { message } => {
400                    panic!("TxStatus::Dropped: {message:?}");
401                }
402                _ => continue,
403            }
404        }
405        panic!("Error waiting for tx status")
406    }
407
408    /// todo
409    pub async fn trace(
410        &self,
411        block_hash: HashFor<C>,
412        extrinsic_hash: Option<HashFor<C>>,
413        parent_hash: HashFor<C>,
414        extrinsic: Option<Vec<u8>>,
415    ) -> Option<CallTrace> {
416        // todo move below to its own function
417        let block_details = self
418            .rpc
419            .chain_get_block(Some(block_hash))
420            .await
421            .expect("no block found")
422            .expect("no block details found");
423        let header = block_details.block.header;
424        let mut exts: Vec<OpaqueExtrinsic> = block_details
425            .block
426            .extrinsics
427            .clone()
428            .into_iter()
429            .filter_map(|e| scale::Decode::decode(&mut &e[..]).ok())
430            .collect::<Vec<_>>();
431
432        // todo
433        let tx_index: usize = match (extrinsic_hash, extrinsic) {
434            (Some(hash), None) => {
435                block_details
436                    .block
437                    .extrinsics
438                    .iter()
439                    .cloned()
440                    .enumerate()
441                    .find_map(|(index, ext)| {
442                        let hash_ext = Transaction::<C>::from_bytes(ext.0)
443                            .hash_with(self.client.hasher());
444                        if hash_ext == hash {
445                            return Some(index);
446                        }
447                        None
448                    })
449                    .expect("the extrinsic hash was not found in the block")
450            }
451            (None, Some(extrinsic)) => {
452                exts.push(
453                    OpaqueExtrinsic::from_bytes(&extrinsic[..])
454                        .expect("OpaqueExtrinsic cannot be created"),
455                );
456                exts.len() - 1
457            }
458            _ => panic!("pattern error"),
459        };
460
461        let tracer_type = TracerType::CallTracer(Some(CallTracerConfig::default()));
462        let func = "ReviveApi_trace_tx";
463
464        let params =
465            scale::Encode::encode(&((header, exts), tx_index.as_u32(), tracer_type));
466
467        let bytes = self
468            .rpc
469            .state_call(func, Some(&params), Some(parent_hash))
470            .await
471            .unwrap_or_else(|err| {
472                panic!(
473                    "error on ws request `trace_tx`: {err:?}\n\n{:#}",
474                    format!("{err}").trim_start_matches("RPC error: ")
475                );
476            });
477
478        let trace: Option<Trace> = scale::Decode::decode(&mut bytes.as_ref())
479            .unwrap_or_else(|err| panic!("decoding `trace_tx` result failed: {err}"));
480
481        match trace {
482            Some(Trace::Call(trace)) => Some(trace),
483            _ => None,
484        }
485    }
486
487    /// Return the hash of the *best* block
488    pub async fn best_block(&self) -> HashFor<C> {
489        self.rpc
490            .chain_get_block_hash(None)
491            .await
492            .unwrap_or_else(|err| {
493                panic!("error on call `chain_get_block_hash`: {err:?}");
494            })
495            .unwrap_or_else(|| {
496                panic!("error on call `chain_get_block_hash`: no best block found");
497            })
498    }
499
500    /// Return the account nonce at the *best* block for an account ID.
501    async fn get_account_nonce(
502        &self,
503        account_id: &C::AccountId,
504    ) -> Result<u64, subxt::Error> {
505        let best_block = self.best_block().await;
506        let account_nonce = self
507            .client
508            .blocks()
509            .at(best_block)
510            .await?
511            .account_nonce(account_id)
512            .await?;
513        Ok(account_nonce)
514    }
515
516    /// Submits an extrinsic to instantiate a contract with the given code.
517    ///
518    /// Returns when the transaction is included in a block. The return value
519    /// contains all events that are associated with this transaction.
520    #[allow(clippy::too_many_arguments)]
521    pub async fn instantiate_with_code(
522        &self,
523        value: E::Balance,
524        gas_limit: Weight,
525        storage_deposit_limit: E::Balance,
526        code: Vec<u8>,
527        data: Vec<u8>,
528        salt: Option<[u8; 32]>,
529        signer: &Keypair,
530    ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
531        let call = subxt::tx::DefaultPayload::new(
532            "Revive",
533            "instantiate_with_code",
534            InstantiateWithCode::<E> {
535                value,
536                gas_limit,
537                storage_deposit_limit,
538                code,
539                data,
540                salt,
541            },
542        )
543        .unvalidated();
544
545        self.submit_extrinsic(&call, signer).await
546    }
547
548    /// Dry runs the upload of the given `code`.
549    pub async fn upload_dry_run(
550        &self,
551        signer: &Keypair,
552        code: Vec<u8>,
553        storage_deposit_limit: Option<E::Balance>,
554    ) -> CodeUploadResult<E::Balance> {
555        let call_request = RpcCodeUploadRequest::<C, E> {
556            origin: Signer::<C>::account_id(signer),
557            code,
558            storage_deposit_limit,
559        };
560        let func = "ReviveApi_upload_code";
561        let params = scale::Encode::encode(&call_request);
562        let bytes = self
563            .rpc
564            .state_call(func, Some(&params), None)
565            .await
566            .unwrap_or_else(|err| {
567                panic!("error on ws request `upload_code`: {err:?}");
568            });
569        scale::Decode::decode(&mut bytes.as_ref())
570            .unwrap_or_else(|err| panic!("decoding CodeUploadResult failed: {err}"))
571    }
572
573    /// Submits an extrinsic to upload a given code.
574    ///
575    /// Returns when the transaction is included in a block. The return value
576    /// contains all events that are associated with this transaction.
577    pub async fn upload(
578        &self,
579        signer: &Keypair,
580        code: Vec<u8>,
581        storage_deposit_limit: E::Balance,
582    ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
583        let call = subxt::tx::DefaultPayload::new(
584            "Revive",
585            "upload_code",
586            UploadCode::<E> {
587                code,
588                storage_deposit_limit,
589            },
590        )
591        .unvalidated();
592
593        self.submit_extrinsic(&call, signer).await
594    }
595
596    /// Submits an extrinsic to remove the code at the given hash.
597    ///
598    /// Returns when the transaction is included in a block. The return value
599    /// contains all events that are associated with this transaction.
600    pub async fn remove_code(
601        &self,
602        signer: &Keypair,
603        code_hash: H256,
604    ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
605        let call = subxt::tx::DefaultPayload::new(
606            "Revive",
607            "remove_code",
608            RemoveCode { code_hash },
609        )
610        .unvalidated();
611
612        self.submit_extrinsic(&call, signer).await
613    }
614
615    /// Dry runs a call of the contract at `contract` with the given parameters.
616    pub async fn call_dry_run(
617        &self,
618        dest: Address,
619        data: Vec<u8>,
620        value: E::Balance,
621        storage_deposit_limit: DepositLimit<E::Balance>,
622        signer: &Keypair,
623    ) -> (ContractExecResultFor<E>, Option<CallTrace>) {
624        let origin = Signer::<C>::account_id(signer);
625        let call_request = RpcCallRequest::<C, E> {
626            origin,
627            dest,
628            value,
629            gas_limit: Some(Weight {
630                ref_time: u64::MAX,
631                proof_size: u64::MAX,
632            }),
633            storage_deposit_limit: storage_deposit_limit.clone(),
634            input_data: data.clone(),
635        };
636        let func = "ReviveApi_call";
637        let params = scale::Encode::encode(&call_request);
638        let bytes = self
639            .rpc
640            .state_call(func, Some(&params), None)
641            .await
642            .unwrap_or_else(|err| {
643                panic!("error on ws request `ReviveApi_call`: {err:?}");
644            });
645        let dry_run_result: ContractExecResultFor<E> =
646            scale::Decode::decode(&mut bytes.as_ref()).unwrap_or_else(|err| {
647                panic!("decoding `ContractExecResult` failed: {err}")
648            });
649
650        // Even if the `storage_deposit_limit` to this function was set as `Unchecked`,
651        // we still take the return value of the dry run for submitting the extrinsic
652        // that will take effect.
653        let storage_deposit_limit = match storage_deposit_limit {
654            DepositLimit::UnsafeOnlyForDryRun => {
655                dry_run_result.storage_deposit.charge_or_zero()
656            }
657            DepositLimit::Balance(limit) => limit,
658        };
659
660        let call = subxt::tx::DefaultPayload::new(
661            "Revive",
662            "call",
663            crate::xts::Call::<E> {
664                dest,
665                value,
666                gas_limit: Weight {
667                    ref_time: dry_run_result.gas_required.ref_time(),
668                    proof_size: dry_run_result.gas_required.proof_size(),
669                },
670                storage_deposit_limit,
671                data,
672            },
673        )
674        .unvalidated();
675        let xt = self.create_extrinsic(&call, signer).await;
676        let block_hash = self.best_block().await;
677        let block_details = self
678            .rpc
679            .chain_get_block(Some(block_hash))
680            .await
681            .expect("no block found")
682            .expect("no block details found");
683        let block_number: u64 = block_details.block.header.number().into();
684        let parent_hash = self
685            .rpc
686            .chain_get_block_hash(Some((block_number - 1u64).into()))
687            .await
688            .expect("no block hash found")
689            .expect("no block details found");
690
691        let trace = self
692            .trace(block_hash, None, parent_hash, Some(xt.into_encoded()))
693            .await;
694
695        (dry_run_result, trace)
696    }
697
698    /// Submits an extrinsic to call a contract with the given parameters.
699    ///
700    /// Returns when the transaction is included in a block. The return value
701    /// contains all events that are associated with this transaction.
702    /// todo the API for `call_dry_run` should mirror that of `call`
703    pub async fn call(
704        &self,
705        contract: Address,
706        value: E::Balance,
707        gas_limit: Weight,
708        storage_deposit_limit: E::Balance,
709        data: Vec<u8>,
710        signer: &Keypair,
711    ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
712        let call = subxt::tx::DefaultPayload::new(
713            "Revive",
714            "call",
715            Call::<E> {
716                dest: contract,
717                value,
718                gas_limit,
719                storage_deposit_limit,
720                data,
721            },
722        )
723        .unvalidated();
724        self.submit_extrinsic(&call, signer).await
725    }
726
727    /// Maps the `signer` to an `H160` account.
728    ///
729    /// This is a `pallet-revive` concept, whereby a stroage entry is created on-chain.
730    /// The entry maps the account id from `signer` to an `H160` account. This is
731    /// a necessity for any operation interacting with the contracts part of
732    /// `pallet-revive`.
733    pub async fn map_account(
734        &self,
735        signer: &Keypair,
736    ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
737        // todo check if the account is unmapped! otherwise
738        // we submit a costly extrinisc which is guaranteed to fail.
739        let call = subxt::tx::DefaultPayload::new("Revive", "map_account", MapAccount {})
740            .unvalidated();
741
742        self.submit_extrinsic(&call, signer).await
743    }
744
745    /// Submit an extrinsic `call_name` for the `pallet_name`.
746    /// The `call_data` is a `Vec<subxt::dynamic::Value>` that holds
747    /// a representation of some value.
748    ///
749    /// Returns when the transaction is included in a block. The return value
750    /// contains all events that are associated with this transaction.
751    pub async fn runtime_call<'a>(
752        &self,
753        signer: &Keypair,
754        pallet_name: &'a str,
755        call_name: &'a str,
756        call_data: Vec<subxt::dynamic::Value>,
757    ) -> (ExtrinsicEvents<C>, Option<CallTrace>) {
758        let call = subxt::dynamic::tx(pallet_name, call_name, call_data);
759        self.submit_extrinsic(&call, signer).await
760    }
761}