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