Skip to main content

subxt_core/config/
transaction_extensions.rs

1// Copyright 2019-2024 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! This module contains implementations for common transaction extensions, each
6//! of which implements [`TransactionExtension`], and can be used in conjunction with
7//! [`AnyOf`] to configure the set of transaction extensions which are known about
8//! when interacting with a chain.
9
10use super::extrinsic_params::ExtrinsicParams;
11use crate::client::ClientState;
12use crate::config::ExtrinsicParamsEncoder;
13use crate::config::{Config, HashFor};
14use crate::error::ExtrinsicParamsError;
15use crate::utils::{Era, Static};
16use alloc::borrow::ToOwned;
17use alloc::boxed::Box;
18use alloc::vec::Vec;
19use codec::{Compact, Encode};
20use core::any::Any;
21use core::fmt::Debug;
22use derive_where::derive_where;
23use hashbrown::HashMap;
24use scale_decode::DecodeAsType;
25use scale_info::PortableRegistry;
26
27// Re-export this here; it's a bit generically named to be re-exported from ::config.
28pub use super::extrinsic_params::Params;
29
30/// A single [`TransactionExtension`] has a unique name, but is otherwise the
31/// same as [`ExtrinsicParams`] in describing how to encode the extra and
32/// additional data.
33pub trait TransactionExtension<T: Config>: ExtrinsicParams<T> {
34    /// The type representing the `extra` / value bytes of a transaction extension.
35    /// Decoding from this type should be symmetrical to the respective
36    /// `ExtrinsicParamsEncoder::encode_value_to()` implementation of this transaction extension.
37    type Decoded: DecodeAsType;
38
39    /// This should return true if the transaction extension matches the details given.
40    /// Often, this will involve just checking that the identifier given matches that of the
41    /// extension in question.
42    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool;
43}
44
45/// The [`VerifySignature`] extension. For V5 General transactions, this is how a signature
46/// is provided. The signature is constructed by signing a payload which contains the
47/// transaction call data as well as the encoded "additional" bytes for any extensions _after_
48/// this one in the list.
49pub struct VerifySignature<T: Config>(VerifySignatureDetails<T>);
50
51impl<T: Config> ExtrinsicParams<T> for VerifySignature<T> {
52    type Params = ();
53
54    fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
55        Ok(VerifySignature(VerifySignatureDetails::Disabled))
56    }
57}
58
59impl<T: Config> ExtrinsicParamsEncoder for VerifySignature<T> {
60    fn encode_value_to(&self, v: &mut Vec<u8>) {
61        self.0.encode_to(v);
62    }
63    fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
64        // This extension is never encoded to the signer payload, and extensions
65        // prior to this are ignored when creating said payload, so clear anything
66        // we've seen so far.
67        v.clear();
68    }
69    fn encode_implicit_to(&self, v: &mut Vec<u8>) {
70        // We only use the "implicit" data for extensions _after_ this one
71        // in the pipeline to form the signer payload. Thus, clear anything
72        // we've seen so far.
73        v.clear();
74    }
75
76    fn inject_signature(&mut self, account: &dyn Any, signature: &dyn Any) {
77        // Downcast refs back to concrete types (we use `&dyn Any`` so that the trait remains object safe)
78        let account = account
79            .downcast_ref::<T::AccountId>()
80            .expect("A T::AccountId should have been provided")
81            .clone();
82        let signature = signature
83            .downcast_ref::<T::Signature>()
84            .expect("A T::Signature should have been provided")
85            .clone();
86
87        // The signature is not set through params, only here, once given by a user:
88        self.0 = VerifySignatureDetails::Signed { signature, account }
89    }
90}
91
92impl<T: Config> TransactionExtension<T> for VerifySignature<T> {
93    type Decoded = Static<VerifySignatureDetails<T>>;
94    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
95        // The name is expected to be VerifyMultiSignature, but to ensure 100% backward
96        // compatibility, also accept VerifySignature.
97        identifier == "VerifyMultiSignature" || identifier == "verifySignature"
98    }
99}
100
101/// This allows a signature to be provided to the [`VerifySignature`] transaction extension.
102// Dev note: this must encode identically to https://github.com/paritytech/polkadot-sdk/blob/fd72d58313c297a10600037ce1bb88ec958d722e/substrate/frame/verify-signature/src/extension.rs#L43
103#[derive(codec::Encode, codec::Decode)]
104pub enum VerifySignatureDetails<T: Config> {
105    /// A signature has been provided.
106    Signed {
107        /// The signature.
108        signature: T::Signature,
109        /// The account that generated the signature.
110        account: T::AccountId,
111    },
112    /// No signature was provided.
113    Disabled,
114}
115
116/// The [`CheckMetadataHash`] transaction extension.
117pub struct CheckMetadataHash {
118    // Eventually we might provide or calculate the metadata hash here,
119    // but for now we never provide a hash and so this is empty.
120}
121
122impl<T: Config> ExtrinsicParams<T> for CheckMetadataHash {
123    type Params = ();
124
125    fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
126        Ok(CheckMetadataHash {})
127    }
128}
129
130impl ExtrinsicParamsEncoder for CheckMetadataHash {
131    fn encode_value_to(&self, v: &mut Vec<u8>) {
132        // A single 0 byte in the TX payload indicates that the chain should
133        // _not_ expect any metadata hash to exist in the signer payload.
134        0u8.encode_to(v);
135    }
136    fn encode_implicit_to(&self, v: &mut Vec<u8>) {
137        // We provide no metadata hash in the signer payload to align with the above.
138        None::<()>.encode_to(v);
139    }
140}
141
142impl<T: Config> TransactionExtension<T> for CheckMetadataHash {
143    type Decoded = CheckMetadataHashMode;
144    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
145        identifier == "CheckMetadataHash"
146    }
147}
148
149/// Is metadata checking enabled or disabled?
150// Dev note: The "Disabled" and "Enabled" variant names match those that the
151// transaction extension will be encoded with, in order that DecodeAsType will work
152// properly.
153#[derive(Copy, Clone, Debug, DecodeAsType)]
154pub enum CheckMetadataHashMode {
155    /// No hash was provided in the signer payload.
156    Disabled,
157    /// A hash was provided in the signer payload.
158    Enabled,
159}
160
161impl CheckMetadataHashMode {
162    /// Is metadata checking enabled or disabled for this transaction?
163    pub fn is_enabled(&self) -> bool {
164        match self {
165            CheckMetadataHashMode::Disabled => false,
166            CheckMetadataHashMode::Enabled => true,
167        }
168    }
169}
170
171/// The [`CheckSpecVersion`] transaction extension.
172pub struct CheckSpecVersion(u32);
173
174impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
175    type Params = ();
176
177    fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
178        Ok(CheckSpecVersion(client.runtime_version.spec_version))
179    }
180}
181
182impl ExtrinsicParamsEncoder for CheckSpecVersion {
183    fn encode_implicit_to(&self, v: &mut Vec<u8>) {
184        self.0.encode_to(v);
185    }
186}
187
188impl<T: Config> TransactionExtension<T> for CheckSpecVersion {
189    type Decoded = ();
190    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
191        identifier == "CheckSpecVersion"
192    }
193}
194
195/// The [`CheckNonce`] transaction extension.
196pub struct CheckNonce(u64);
197
198impl<T: Config> ExtrinsicParams<T> for CheckNonce {
199    type Params = CheckNonceParams;
200
201    fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
202        Ok(CheckNonce(params.0.unwrap_or(0)))
203    }
204}
205
206impl ExtrinsicParamsEncoder for CheckNonce {
207    fn encode_value_to(&self, v: &mut Vec<u8>) {
208        Compact(self.0).encode_to(v);
209    }
210}
211
212impl<T: Config> TransactionExtension<T> for CheckNonce {
213    type Decoded = u64;
214    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
215        identifier == "CheckNonce"
216    }
217}
218
219/// Configure the nonce used.
220#[derive(Debug, Clone, Default)]
221pub struct CheckNonceParams(Option<u64>);
222
223impl CheckNonceParams {
224    /// Retrieve the nonce from the chain and use that.
225    pub fn from_chain() -> Self {
226        Self(None)
227    }
228    /// Manually set an account nonce to use.
229    pub fn with_nonce(nonce: u64) -> Self {
230        Self(Some(nonce))
231    }
232}
233
234impl<T: Config> Params<T> for CheckNonceParams {
235    fn inject_account_nonce(&mut self, nonce: u64) {
236        if self.0.is_none() {
237            self.0 = Some(nonce)
238        }
239    }
240}
241
242/// The [`CheckTxVersion`] transaction extension.
243pub struct CheckTxVersion(u32);
244
245impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
246    type Params = ();
247
248    fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
249        Ok(CheckTxVersion(client.runtime_version.transaction_version))
250    }
251}
252
253impl ExtrinsicParamsEncoder for CheckTxVersion {
254    fn encode_implicit_to(&self, v: &mut Vec<u8>) {
255        self.0.encode_to(v);
256    }
257}
258
259impl<T: Config> TransactionExtension<T> for CheckTxVersion {
260    type Decoded = ();
261    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
262        identifier == "CheckTxVersion"
263    }
264}
265
266/// The [`CheckGenesis`] transaction extension.
267pub struct CheckGenesis<T: Config>(HashFor<T>);
268
269impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
270    type Params = ();
271
272    fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
273        Ok(CheckGenesis(client.genesis_hash))
274    }
275}
276
277impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
278    fn encode_implicit_to(&self, v: &mut Vec<u8>) {
279        self.0.encode_to(v);
280    }
281}
282
283impl<T: Config> TransactionExtension<T> for CheckGenesis<T> {
284    type Decoded = ();
285    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
286        identifier == "CheckGenesis"
287    }
288}
289
290/// The [`CheckMortality`] transaction extension.
291pub struct CheckMortality<T: Config> {
292    params: CheckMortalityParamsInner<T>,
293    genesis_hash: HashFor<T>,
294}
295
296impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
297    type Params = CheckMortalityParams<T>;
298
299    fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
300        // If a user has explicitly configured the transaction to be mortal for n blocks, but we get
301        // to this stage and no injected information was able to turn this into MortalFromBlock{..},
302        // then we hit an error as we are unable to construct a mortal transaction here.
303        if matches!(&params.0, CheckMortalityParamsInner::MortalForBlocks(_)) {
304            return Err(ExtrinsicParamsError::custom(
305                "CheckMortality: We cannot construct an offline extrinsic with only the number of blocks it is mortal for. Use mortal_from_unchecked instead.",
306            ));
307        }
308
309        Ok(CheckMortality {
310            // if nothing has been explicitly configured, we will have a mortal transaction
311            // valid for 32 blocks if block info is available.
312            params: params.0,
313            genesis_hash: client.genesis_hash,
314        })
315    }
316}
317
318impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
319    fn encode_value_to(&self, v: &mut Vec<u8>) {
320        match &self.params {
321            CheckMortalityParamsInner::MortalFromBlock {
322                for_n_blocks,
323                from_block_n,
324                ..
325            } => {
326                Era::mortal(*for_n_blocks, *from_block_n).encode_to(v);
327            }
328            _ => {
329                // Note: if we see `CheckMortalityInner::MortalForBlocks`, then it means the user has
330                // configured a block to be mortal for N blocks, but the current block was never injected,
331                // so we don't know where to start from and default back to building an immortal tx.
332                Era::Immortal.encode_to(v);
333            }
334        }
335    }
336    fn encode_implicit_to(&self, v: &mut Vec<u8>) {
337        match &self.params {
338            CheckMortalityParamsInner::MortalFromBlock {
339                from_block_hash, ..
340            } => {
341                from_block_hash.encode_to(v);
342            }
343            _ => {
344                self.genesis_hash.encode_to(v);
345            }
346        }
347    }
348}
349
350impl<T: Config> TransactionExtension<T> for CheckMortality<T> {
351    type Decoded = Era;
352    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
353        identifier == "CheckMortality"
354    }
355}
356
357/// Parameters to configure the [`CheckMortality`] transaction extension.
358pub struct CheckMortalityParams<T: Config>(CheckMortalityParamsInner<T>);
359
360enum CheckMortalityParamsInner<T: Config> {
361    /// The transaction will be immortal.
362    Immortal,
363    /// The transaction is mortal for N blocks. This must be "upgraded" into
364    /// [`CheckMortalityParamsInner::MortalFromBlock`] to ultimately work.
365    MortalForBlocks(u64),
366    /// The transaction is mortal for N blocks, but if it cannot be "upgraded",
367    /// then it will be set to immortal instead. This is the default if unset.
368    MortalForBlocksOrImmortalIfNotPossible(u64),
369    /// The transaction is mortal and all of the relevant information is provided.
370    MortalFromBlock {
371        for_n_blocks: u64,
372        from_block_n: u64,
373        from_block_hash: HashFor<T>,
374    },
375}
376
377impl<T: Config> Default for CheckMortalityParams<T> {
378    fn default() -> Self {
379        // default to being mortal for 32 blocks if possible, else immortal:
380        CheckMortalityParams(CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(32))
381    }
382}
383
384impl<T: Config> CheckMortalityParams<T> {
385    /// Configure a transaction that will be mortal for the number of blocks given.
386    pub fn mortal(for_n_blocks: u64) -> Self {
387        Self(CheckMortalityParamsInner::MortalForBlocks(for_n_blocks))
388    }
389
390    /// Configure a transaction that will be mortal for the number of blocks given,
391    /// and from the block details provided. Prefer to use [`CheckMortalityParams::mortal()`]
392    /// where possible, which prevents the block number and hash from being misaligned.
393    pub fn mortal_from_unchecked(
394        for_n_blocks: u64,
395        from_block_n: u64,
396        from_block_hash: HashFor<T>,
397    ) -> Self {
398        Self(CheckMortalityParamsInner::MortalFromBlock {
399            for_n_blocks,
400            from_block_n,
401            from_block_hash,
402        })
403    }
404    /// An immortal transaction.
405    pub fn immortal() -> Self {
406        Self(CheckMortalityParamsInner::Immortal)
407    }
408}
409
410impl<T: Config> Params<T> for CheckMortalityParams<T> {
411    fn inject_block(&mut self, from_block_n: u64, from_block_hash: HashFor<T>) {
412        match &self.0 {
413            CheckMortalityParamsInner::MortalForBlocks(n)
414            | CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(n) => {
415                self.0 = CheckMortalityParamsInner::MortalFromBlock {
416                    for_n_blocks: *n,
417                    from_block_n,
418                    from_block_hash,
419                }
420            }
421            _ => {
422                // Don't change anything if explicit Immortal or explicit block set.
423            }
424        }
425    }
426}
427
428/// The [`ChargeAssetTxPayment`] transaction extension.
429#[derive(DecodeAsType)]
430#[derive_where(Clone, Debug; T::AssetId)]
431#[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")]
432pub struct ChargeAssetTxPayment<T: Config> {
433    tip: Compact<u128>,
434    asset_id: Option<T::AssetId>,
435}
436
437impl<T: Config> ChargeAssetTxPayment<T> {
438    /// Tip to the extrinsic author in the native chain token.
439    pub fn tip(&self) -> u128 {
440        self.tip.0
441    }
442
443    /// Tip to the extrinsic author using the asset ID given.
444    pub fn asset_id(&self) -> Option<&T::AssetId> {
445        self.asset_id.as_ref()
446    }
447}
448
449impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment<T> {
450    type Params = ChargeAssetTxPaymentParams<T>;
451
452    fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
453        Ok(ChargeAssetTxPayment {
454            tip: Compact(params.tip),
455            asset_id: params.asset_id,
456        })
457    }
458}
459
460impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
461    fn encode_value_to(&self, v: &mut Vec<u8>) {
462        (self.tip, &self.asset_id).encode_to(v);
463    }
464}
465
466impl<T: Config> TransactionExtension<T> for ChargeAssetTxPayment<T> {
467    type Decoded = Self;
468    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
469        identifier == "ChargeAssetTxPayment"
470    }
471}
472
473/// Parameters to configure the [`ChargeAssetTxPayment`] transaction extension.
474pub struct ChargeAssetTxPaymentParams<T: Config> {
475    tip: u128,
476    asset_id: Option<T::AssetId>,
477}
478
479impl<T: Config> Default for ChargeAssetTxPaymentParams<T> {
480    fn default() -> Self {
481        ChargeAssetTxPaymentParams {
482            tip: Default::default(),
483            asset_id: Default::default(),
484        }
485    }
486}
487
488impl<T: Config> ChargeAssetTxPaymentParams<T> {
489    /// Don't provide a tip to the extrinsic author.
490    pub fn no_tip() -> Self {
491        ChargeAssetTxPaymentParams {
492            tip: 0,
493            asset_id: None,
494        }
495    }
496    /// Tip the extrinsic author in the native chain token.
497    pub fn tip(tip: u128) -> Self {
498        ChargeAssetTxPaymentParams {
499            tip,
500            asset_id: None,
501        }
502    }
503    /// Tip the extrinsic author using the asset ID given.
504    pub fn tip_of(tip: u128, asset_id: T::AssetId) -> Self {
505        ChargeAssetTxPaymentParams {
506            tip,
507            asset_id: Some(asset_id),
508        }
509    }
510}
511
512impl<T: Config> Params<T> for ChargeAssetTxPaymentParams<T> {}
513
514/// The [`ChargeTransactionPayment`] transaction extension.
515#[derive(Clone, Debug, DecodeAsType)]
516pub struct ChargeTransactionPayment {
517    tip: Compact<u128>,
518}
519
520impl ChargeTransactionPayment {
521    /// Tip to the extrinsic author in the native chain token.
522    pub fn tip(&self) -> u128 {
523        self.tip.0
524    }
525}
526
527impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
528    type Params = ChargeTransactionPaymentParams;
529
530    fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
531        Ok(ChargeTransactionPayment {
532            tip: Compact(params.tip),
533        })
534    }
535}
536
537impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
538    fn encode_value_to(&self, v: &mut Vec<u8>) {
539        self.tip.encode_to(v);
540    }
541}
542
543impl<T: Config> TransactionExtension<T> for ChargeTransactionPayment {
544    type Decoded = Self;
545    fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
546        identifier == "ChargeTransactionPayment"
547    }
548}
549
550/// Parameters to configure the [`ChargeTransactionPayment`] transaction extension.
551#[derive(Default)]
552pub struct ChargeTransactionPaymentParams {
553    tip: u128,
554}
555
556impl ChargeTransactionPaymentParams {
557    /// Don't provide a tip to the extrinsic author.
558    pub fn no_tip() -> Self {
559        ChargeTransactionPaymentParams { tip: 0 }
560    }
561    /// Tip the extrinsic author in the native chain token.
562    pub fn tip(tip: u128) -> Self {
563        ChargeTransactionPaymentParams { tip }
564    }
565}
566
567impl<T: Config> Params<T> for ChargeTransactionPaymentParams {}
568
569/// This accepts a tuple of [`TransactionExtension`]s, and will dynamically make use of whichever
570/// ones are actually required for the chain in the correct order, ignoring the rest. This
571/// is a sensible default, and allows for a single configuration to work across multiple chains.
572pub struct AnyOf<T, Params> {
573    params: Vec<Box<dyn ExtrinsicParamsEncoder + Send + 'static>>,
574    _marker: core::marker::PhantomData<(T, Params)>,
575}
576
577macro_rules! impl_tuples {
578    ($($ident:ident $index:tt),+) => {
579        // We do some magic when the tuple is wrapped in AnyOf. We
580        // look at the metadata, and use this to select and make use of only the extensions
581        // that we actually need for the chain we're dealing with.
582        impl <T, $($ident),+> ExtrinsicParams<T> for AnyOf<T, ($($ident,)+)>
583        where
584            T: Config,
585            $($ident: TransactionExtension<T>,)+
586        {
587            type Params = ($($ident::Params,)+);
588
589            fn new(
590                client: &ClientState<T>,
591                params: Self::Params,
592            ) -> Result<Self, ExtrinsicParamsError> {
593                let metadata = &client.metadata;
594                let types = metadata.types();
595
596                // For each transaction extension in the tuple, find the matching index in the metadata, if
597                // there is one, and add it to a map with that index as the key.
598                let mut exts_by_index = HashMap::new();
599                $({
600                    for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
601                        // Skip over any exts that have a match already:
602                        if exts_by_index.contains_key(&idx) {
603                            continue
604                        }
605                        // Break and record as soon as we find a match:
606                        if $ident::matches(e.identifier(), e.extra_ty(), types) {
607                            let ext = $ident::new(client, params.$index)?;
608                            let boxed_ext: Box<dyn ExtrinsicParamsEncoder + Send + 'static> = Box::new(ext);
609                            exts_by_index.insert(idx, boxed_ext);
610                            break
611                        }
612                    }
613                })+
614
615                // Next, turn these into an ordered vec, erroring if we haven't matched on any exts yet.
616                let mut params = Vec::new();
617                for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
618                    let Some(ext) = exts_by_index.remove(&idx) else {
619                        if is_type_empty(e.extra_ty(), types) {
620                            continue
621                        } else {
622                            return Err(ExtrinsicParamsError::UnknownTransactionExtension(e.identifier().to_owned()));
623                        }
624                    };
625                    params.push(ext);
626                }
627
628                Ok(AnyOf {
629                    params,
630                    _marker: core::marker::PhantomData
631                })
632            }
633        }
634
635        impl <T, $($ident),+> ExtrinsicParamsEncoder for AnyOf<T, ($($ident,)+)>
636        where
637            T: Config,
638            $($ident: TransactionExtension<T>,)+
639        {
640            fn encode_value_to(&self, v: &mut Vec<u8>) {
641                for ext in &self.params {
642                    ext.encode_value_to(v);
643                }
644            }
645            fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
646                for ext in &self.params {
647                    ext.encode_signer_payload_value_to(v);
648                }
649            }
650            fn encode_implicit_to(&self, v: &mut Vec<u8>) {
651                for ext in &self.params {
652                    ext.encode_implicit_to(v);
653                }
654            }
655            fn inject_signature(&mut self, account_id: &dyn Any, signature: &dyn Any) {
656                for ext in &mut self.params {
657                    ext.inject_signature(account_id, signature);
658                }
659            }
660        }
661    }
662}
663
664#[rustfmt::skip]
665const _: () = {
666    impl_tuples!(A 0);
667    impl_tuples!(A 0, B 1);
668    impl_tuples!(A 0, B 1, C 2);
669    impl_tuples!(A 0, B 1, C 2, D 3);
670    impl_tuples!(A 0, B 1, C 2, D 3, E 4);
671    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
672    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
673    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
674    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
675    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
676    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
677    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11);
678    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12);
679    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13);
680    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14);
681    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15);
682    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16);
683    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17);
684    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18);
685    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19);
686    impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19, V 20);
687};
688
689/// Checks to see whether the type being given is empty, ie would require
690/// 0 bytes to encode.
691fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool {
692    let Some(ty) = types.resolve(type_id) else {
693        // Can't resolve; type may not be empty. Not expected to hit this.
694        return false;
695    };
696
697    use scale_info::TypeDef;
698    match &ty.type_def {
699        TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)),
700        TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types),
701        TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)),
702        // Explicitly list these in case any additions are made in the future.
703        TypeDef::BitSequence(_)
704        | TypeDef::Variant(_)
705        | TypeDef::Sequence(_)
706        | TypeDef::Compact(_)
707        | TypeDef::Primitive(_) => false,
708    }
709}