mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 02:48:03 +00:00
77b6abccba
* Clarify mortality config * Fix EncodeAsType / DecodeAsType impls for Era to be proper ones * Fix test now that txs are mortal by default * clippy * missing imports * allow Era decoding from CheckMortality * tweak a comment * fmt * Add more explicit tests for mortality
708 lines
25 KiB
Rust
708 lines
25 KiB
Rust
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
|
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
|
// see LICENSE for license details.
|
|
|
|
//! This module contains implementations for common transaction extensions, each
|
|
//! of which implements [`TransactionExtension`], and can be used in conjunction with
|
|
//! [`AnyOf`] to configure the set of transaction extensions which are known about
|
|
//! when interacting with a chain.
|
|
|
|
use super::extrinsic_params::ExtrinsicParams;
|
|
use crate::client::ClientState;
|
|
use crate::config::ExtrinsicParamsEncoder;
|
|
use crate::config::{Config, HashFor};
|
|
use crate::error::ExtrinsicParamsError;
|
|
use crate::utils::{Era, Static};
|
|
use alloc::borrow::ToOwned;
|
|
use alloc::boxed::Box;
|
|
use alloc::vec::Vec;
|
|
use codec::{Compact, Encode};
|
|
use core::any::Any;
|
|
use core::fmt::Debug;
|
|
use derive_where::derive_where;
|
|
use hashbrown::HashMap;
|
|
use scale_decode::DecodeAsType;
|
|
use scale_info::PortableRegistry;
|
|
|
|
// Re-export this here; it's a bit generically named to be re-exported from ::config.
|
|
pub use super::extrinsic_params::Params;
|
|
|
|
/// A single [`TransactionExtension`] has a unique name, but is otherwise the
|
|
/// same as [`ExtrinsicParams`] in describing how to encode the extra and
|
|
/// additional data.
|
|
pub trait TransactionExtension<T: Config>: ExtrinsicParams<T> {
|
|
/// The type representing the `extra` / value bytes of a transaction extension.
|
|
/// Decoding from this type should be symmetrical to the respective
|
|
/// `ExtrinsicParamsEncoder::encode_value_to()` implementation of this transaction extension.
|
|
type Decoded: DecodeAsType;
|
|
|
|
/// This should return true if the transaction extension matches the details given.
|
|
/// Often, this will involve just checking that the identifier given matches that of the
|
|
/// extension in question.
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool;
|
|
}
|
|
|
|
/// The [`VerifySignature`] extension. For V5 General transactions, this is how a signature
|
|
/// is provided. The signature is constructed by signing a payload which contains the
|
|
/// transaction call data as well as the encoded "additional" bytes for any extensions _after_
|
|
/// this one in the list.
|
|
pub struct VerifySignature<T: Config>(VerifySignatureDetails<T>);
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for VerifySignature<T> {
|
|
type Params = ();
|
|
|
|
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(VerifySignature(VerifySignatureDetails::Disabled))
|
|
}
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParamsEncoder for VerifySignature<T> {
|
|
fn encode_value_to(&self, v: &mut Vec<u8>) {
|
|
self.0.encode_to(v);
|
|
}
|
|
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
|
|
// This extension is never encoded to the signer payload, and extensions
|
|
// prior to this are ignored when creating said payload, so clear anything
|
|
// we've seen so far.
|
|
v.clear();
|
|
}
|
|
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
|
|
// We only use the "implicit" data for extensions _after_ this one
|
|
// in the pipeline to form the signer payload. Thus, clear anything
|
|
// we've seen so far.
|
|
v.clear();
|
|
}
|
|
|
|
fn inject_signature(&mut self, account: &dyn Any, signature: &dyn Any) {
|
|
// Downcast refs back to concrete types (we use `&dyn Any`` so that the trait remains object safe)
|
|
let account = account
|
|
.downcast_ref::<T::AccountId>()
|
|
.expect("A T::AccountId should have been provided")
|
|
.clone();
|
|
let signature = signature
|
|
.downcast_ref::<T::Signature>()
|
|
.expect("A T::Signature should have been provided")
|
|
.clone();
|
|
|
|
// The signature is not set through params, only here, once given by a user:
|
|
self.0 = VerifySignatureDetails::Signed { signature, account }
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for VerifySignature<T> {
|
|
type Decoded = Static<VerifySignatureDetails<T>>;
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "VerifySignature"
|
|
}
|
|
}
|
|
|
|
/// This allows a signature to be provided to the [`VerifySignature`] transaction extension.
|
|
// Dev note: this must encode identically to https://github.com/paritytech/polkadot-sdk/blob/fd72d58313c297a10600037ce1bb88ec958d722e/substrate/frame/verify-signature/src/extension.rs#L43
|
|
#[derive(codec::Encode, codec::Decode)]
|
|
pub enum VerifySignatureDetails<T: Config> {
|
|
/// A signature has been provided.
|
|
Signed {
|
|
/// The signature.
|
|
signature: T::Signature,
|
|
/// The account that generated the signature.
|
|
account: T::AccountId,
|
|
},
|
|
/// No signature was provided.
|
|
Disabled,
|
|
}
|
|
|
|
/// The [`CheckMetadataHash`] transaction extension.
|
|
pub struct CheckMetadataHash {
|
|
// Eventually we might provide or calculate the metadata hash here,
|
|
// but for now we never provide a hash and so this is empty.
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for CheckMetadataHash {
|
|
type Params = ();
|
|
|
|
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(CheckMetadataHash {})
|
|
}
|
|
}
|
|
|
|
impl ExtrinsicParamsEncoder for CheckMetadataHash {
|
|
fn encode_value_to(&self, v: &mut Vec<u8>) {
|
|
// A single 0 byte in the TX payload indicates that the chain should
|
|
// _not_ expect any metadata hash to exist in the signer payload.
|
|
0u8.encode_to(v);
|
|
}
|
|
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
|
|
// We provide no metadata hash in the signer payload to align with the above.
|
|
None::<()>.encode_to(v);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for CheckMetadataHash {
|
|
type Decoded = CheckMetadataHashMode;
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "CheckMetadataHash"
|
|
}
|
|
}
|
|
|
|
/// Is metadata checking enabled or disabled?
|
|
// Dev note: The "Disabled" and "Enabled" variant names match those that the
|
|
// transaction extension will be encoded with, in order that DecodeAsType will work
|
|
// properly.
|
|
#[derive(Copy, Clone, Debug, DecodeAsType)]
|
|
pub enum CheckMetadataHashMode {
|
|
/// No hash was provided in the signer payload.
|
|
Disabled,
|
|
/// A hash was provided in the signer payload.
|
|
Enabled,
|
|
}
|
|
|
|
impl CheckMetadataHashMode {
|
|
/// Is metadata checking enabled or disabled for this transaction?
|
|
pub fn is_enabled(&self) -> bool {
|
|
match self {
|
|
CheckMetadataHashMode::Disabled => false,
|
|
CheckMetadataHashMode::Enabled => true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The [`CheckSpecVersion`] transaction extension.
|
|
pub struct CheckSpecVersion(u32);
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
|
|
type Params = ();
|
|
|
|
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(CheckSpecVersion(client.runtime_version.spec_version))
|
|
}
|
|
}
|
|
|
|
impl ExtrinsicParamsEncoder for CheckSpecVersion {
|
|
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
|
|
self.0.encode_to(v);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for CheckSpecVersion {
|
|
type Decoded = ();
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "CheckSpecVersion"
|
|
}
|
|
}
|
|
|
|
/// The [`CheckNonce`] transaction extension.
|
|
pub struct CheckNonce(u64);
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
|
|
type Params = CheckNonceParams;
|
|
|
|
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(CheckNonce(params.0.unwrap_or(0)))
|
|
}
|
|
}
|
|
|
|
impl ExtrinsicParamsEncoder for CheckNonce {
|
|
fn encode_value_to(&self, v: &mut Vec<u8>) {
|
|
Compact(self.0).encode_to(v);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for CheckNonce {
|
|
type Decoded = u64;
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "CheckNonce"
|
|
}
|
|
}
|
|
|
|
/// Configure the nonce used.
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct CheckNonceParams(Option<u64>);
|
|
|
|
impl CheckNonceParams {
|
|
/// Retrieve the nonce from the chain and use that.
|
|
pub fn from_chain() -> Self {
|
|
Self(None)
|
|
}
|
|
/// Manually set an account nonce to use.
|
|
pub fn with_nonce(nonce: u64) -> Self {
|
|
Self(Some(nonce))
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Params<T> for CheckNonceParams {
|
|
fn inject_account_nonce(&mut self, nonce: u64) {
|
|
if self.0.is_none() {
|
|
self.0 = Some(nonce)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The [`CheckTxVersion`] transaction extension.
|
|
pub struct CheckTxVersion(u32);
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
|
|
type Params = ();
|
|
|
|
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(CheckTxVersion(client.runtime_version.transaction_version))
|
|
}
|
|
}
|
|
|
|
impl ExtrinsicParamsEncoder for CheckTxVersion {
|
|
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
|
|
self.0.encode_to(v);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for CheckTxVersion {
|
|
type Decoded = ();
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "CheckTxVersion"
|
|
}
|
|
}
|
|
|
|
/// The [`CheckGenesis`] transaction extension.
|
|
pub struct CheckGenesis<T: Config>(HashFor<T>);
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
|
|
type Params = ();
|
|
|
|
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(CheckGenesis(client.genesis_hash))
|
|
}
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
|
|
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
|
|
self.0.encode_to(v);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for CheckGenesis<T> {
|
|
type Decoded = ();
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "CheckGenesis"
|
|
}
|
|
}
|
|
|
|
/// The [`CheckMortality`] transaction extension.
|
|
pub struct CheckMortality<T: Config> {
|
|
params: CheckMortalityParamsInner<T>,
|
|
genesis_hash: HashFor<T>,
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
|
|
type Params = CheckMortalityParams<T>;
|
|
|
|
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
// If a user has explicitly configured the transaction to be mortal for n blocks, but we get
|
|
// to this stage and no injected information was able to turn this into MortalFromBlock{..},
|
|
// then we hit an error as we are unable to construct a mortal transaction here.
|
|
if matches!(¶ms.0, CheckMortalityParamsInner::MortalForBlocks(_)) {
|
|
return Err(ExtrinsicParamsError::custom(
|
|
"CheckMortality: We cannot construct an offline extrinsic with only the number of blocks it is mortal for. Use mortal_from_unchecked instead.",
|
|
));
|
|
}
|
|
|
|
Ok(CheckMortality {
|
|
// if nothing has been explicitly configured, we will have a mortal transaction
|
|
// valid for 32 blocks if block info is available.
|
|
params: params.0,
|
|
genesis_hash: client.genesis_hash,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
|
|
fn encode_value_to(&self, v: &mut Vec<u8>) {
|
|
match &self.params {
|
|
CheckMortalityParamsInner::MortalFromBlock {
|
|
for_n_blocks,
|
|
from_block_n,
|
|
..
|
|
} => {
|
|
Era::mortal(*for_n_blocks, *from_block_n).encode_to(v);
|
|
}
|
|
_ => {
|
|
// Note: if we see `CheckMortalityInner::MortalForBlocks`, then it means the user has
|
|
// configured a block to be mortal for N blocks, but the current block was never injected,
|
|
// so we don't know where to start from and default back to building an immortal tx.
|
|
Era::Immortal.encode_to(v);
|
|
}
|
|
}
|
|
}
|
|
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
|
|
match &self.params {
|
|
CheckMortalityParamsInner::MortalFromBlock {
|
|
from_block_hash, ..
|
|
} => {
|
|
from_block_hash.encode_to(v);
|
|
}
|
|
_ => {
|
|
self.genesis_hash.encode_to(v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for CheckMortality<T> {
|
|
type Decoded = Era;
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "CheckMortality"
|
|
}
|
|
}
|
|
|
|
/// Parameters to configure the [`CheckMortality`] transaction extension.
|
|
pub struct CheckMortalityParams<T: Config>(CheckMortalityParamsInner<T>);
|
|
|
|
enum CheckMortalityParamsInner<T: Config> {
|
|
/// The transaction will be immortal.
|
|
Immortal,
|
|
/// The transaction is mortal for N blocks. This must be "upgraded" into
|
|
/// [`CheckMortalityParamsInner::MortalFromBlock`] to ultimately work.
|
|
MortalForBlocks(u64),
|
|
/// The transaction is mortal for N blocks, but if it cannot be "upgraded",
|
|
/// then it will be set to immortal instead. This is the default if unset.
|
|
MortalForBlocksOrImmortalIfNotPossible(u64),
|
|
/// The transaction is mortal and all of the relevant information is provided.
|
|
MortalFromBlock {
|
|
for_n_blocks: u64,
|
|
from_block_n: u64,
|
|
from_block_hash: HashFor<T>,
|
|
},
|
|
}
|
|
|
|
impl<T: Config> Default for CheckMortalityParams<T> {
|
|
fn default() -> Self {
|
|
// default to being mortal for 32 blocks if possible, else immortal:
|
|
CheckMortalityParams(CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(32))
|
|
}
|
|
}
|
|
|
|
impl<T: Config> CheckMortalityParams<T> {
|
|
/// Configure a transaction that will be mortal for the number of blocks given.
|
|
pub fn mortal(for_n_blocks: u64) -> Self {
|
|
Self(CheckMortalityParamsInner::MortalForBlocks(for_n_blocks))
|
|
}
|
|
|
|
/// Configure a transaction that will be mortal for the number of blocks given,
|
|
/// and from the block details provided. Prefer to use [`CheckMortalityParams::mortal()`]
|
|
/// where possible, which prevents the block number and hash from being misaligned.
|
|
pub fn mortal_from_unchecked(
|
|
for_n_blocks: u64,
|
|
from_block_n: u64,
|
|
from_block_hash: HashFor<T>,
|
|
) -> Self {
|
|
Self(CheckMortalityParamsInner::MortalFromBlock {
|
|
for_n_blocks,
|
|
from_block_n,
|
|
from_block_hash,
|
|
})
|
|
}
|
|
/// An immortal transaction.
|
|
pub fn immortal() -> Self {
|
|
Self(CheckMortalityParamsInner::Immortal)
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Params<T> for CheckMortalityParams<T> {
|
|
fn inject_block(&mut self, from_block_n: u64, from_block_hash: HashFor<T>) {
|
|
match &self.0 {
|
|
CheckMortalityParamsInner::MortalForBlocks(n)
|
|
| CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(n) => {
|
|
self.0 = CheckMortalityParamsInner::MortalFromBlock {
|
|
for_n_blocks: *n,
|
|
from_block_n,
|
|
from_block_hash,
|
|
}
|
|
}
|
|
_ => {
|
|
// Don't change anything if explicit Immortal or explicit block set.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The [`ChargeAssetTxPayment`] transaction extension.
|
|
#[derive(DecodeAsType)]
|
|
#[derive_where(Clone, Debug; T::AssetId)]
|
|
#[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")]
|
|
pub struct ChargeAssetTxPayment<T: Config> {
|
|
tip: Compact<u128>,
|
|
asset_id: Option<T::AssetId>,
|
|
}
|
|
|
|
impl<T: Config> ChargeAssetTxPayment<T> {
|
|
/// Tip to the extrinsic author in the native chain token.
|
|
pub fn tip(&self) -> u128 {
|
|
self.tip.0
|
|
}
|
|
|
|
/// Tip to the extrinsic author using the asset ID given.
|
|
pub fn asset_id(&self) -> Option<&T::AssetId> {
|
|
self.asset_id.as_ref()
|
|
}
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment<T> {
|
|
type Params = ChargeAssetTxPaymentParams<T>;
|
|
|
|
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(ChargeAssetTxPayment {
|
|
tip: Compact(params.tip),
|
|
asset_id: params.asset_id,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
|
|
fn encode_value_to(&self, v: &mut Vec<u8>) {
|
|
(self.tip, &self.asset_id).encode_to(v);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for ChargeAssetTxPayment<T> {
|
|
type Decoded = Self;
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "ChargeAssetTxPayment"
|
|
}
|
|
}
|
|
|
|
/// Parameters to configure the [`ChargeAssetTxPayment`] transaction extension.
|
|
pub struct ChargeAssetTxPaymentParams<T: Config> {
|
|
tip: u128,
|
|
asset_id: Option<T::AssetId>,
|
|
}
|
|
|
|
impl<T: Config> Default for ChargeAssetTxPaymentParams<T> {
|
|
fn default() -> Self {
|
|
ChargeAssetTxPaymentParams {
|
|
tip: Default::default(),
|
|
asset_id: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> ChargeAssetTxPaymentParams<T> {
|
|
/// Don't provide a tip to the extrinsic author.
|
|
pub fn no_tip() -> Self {
|
|
ChargeAssetTxPaymentParams {
|
|
tip: 0,
|
|
asset_id: None,
|
|
}
|
|
}
|
|
/// Tip the extrinsic author in the native chain token.
|
|
pub fn tip(tip: u128) -> Self {
|
|
ChargeAssetTxPaymentParams {
|
|
tip,
|
|
asset_id: None,
|
|
}
|
|
}
|
|
/// Tip the extrinsic author using the asset ID given.
|
|
pub fn tip_of(tip: u128, asset_id: T::AssetId) -> Self {
|
|
ChargeAssetTxPaymentParams {
|
|
tip,
|
|
asset_id: Some(asset_id),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Params<T> for ChargeAssetTxPaymentParams<T> {}
|
|
|
|
/// The [`ChargeTransactionPayment`] transaction extension.
|
|
#[derive(Clone, Debug, DecodeAsType)]
|
|
pub struct ChargeTransactionPayment {
|
|
tip: Compact<u128>,
|
|
}
|
|
|
|
impl ChargeTransactionPayment {
|
|
/// Tip to the extrinsic author in the native chain token.
|
|
pub fn tip(&self) -> u128 {
|
|
self.tip.0
|
|
}
|
|
}
|
|
|
|
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
|
|
type Params = ChargeTransactionPaymentParams;
|
|
|
|
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
|
|
Ok(ChargeTransactionPayment {
|
|
tip: Compact(params.tip),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
|
|
fn encode_value_to(&self, v: &mut Vec<u8>) {
|
|
self.tip.encode_to(v);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> TransactionExtension<T> for ChargeTransactionPayment {
|
|
type Decoded = Self;
|
|
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
|
|
identifier == "ChargeTransactionPayment"
|
|
}
|
|
}
|
|
|
|
/// Parameters to configure the [`ChargeTransactionPayment`] transaction extension.
|
|
#[derive(Default)]
|
|
pub struct ChargeTransactionPaymentParams {
|
|
tip: u128,
|
|
}
|
|
|
|
impl ChargeTransactionPaymentParams {
|
|
/// Don't provide a tip to the extrinsic author.
|
|
pub fn no_tip() -> Self {
|
|
ChargeTransactionPaymentParams { tip: 0 }
|
|
}
|
|
/// Tip the extrinsic author in the native chain token.
|
|
pub fn tip(tip: u128) -> Self {
|
|
ChargeTransactionPaymentParams { tip }
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Params<T> for ChargeTransactionPaymentParams {}
|
|
|
|
/// This accepts a tuple of [`TransactionExtension`]s, and will dynamically make use of whichever
|
|
/// ones are actually required for the chain in the correct order, ignoring the rest. This
|
|
/// is a sensible default, and allows for a single configuration to work across multiple chains.
|
|
pub struct AnyOf<T, Params> {
|
|
params: Vec<Box<dyn ExtrinsicParamsEncoder + Send + 'static>>,
|
|
_marker: core::marker::PhantomData<(T, Params)>,
|
|
}
|
|
|
|
macro_rules! impl_tuples {
|
|
($($ident:ident $index:tt),+) => {
|
|
// We do some magic when the tuple is wrapped in AnyOf. We
|
|
// look at the metadata, and use this to select and make use of only the extensions
|
|
// that we actually need for the chain we're dealing with.
|
|
impl <T, $($ident),+> ExtrinsicParams<T> for AnyOf<T, ($($ident,)+)>
|
|
where
|
|
T: Config,
|
|
$($ident: TransactionExtension<T>,)+
|
|
{
|
|
type Params = ($($ident::Params,)+);
|
|
|
|
fn new(
|
|
client: &ClientState<T>,
|
|
params: Self::Params,
|
|
) -> Result<Self, ExtrinsicParamsError> {
|
|
let metadata = &client.metadata;
|
|
let types = metadata.types();
|
|
|
|
// For each transaction extension in the tuple, find the matching index in the metadata, if
|
|
// there is one, and add it to a map with that index as the key.
|
|
let mut exts_by_index = HashMap::new();
|
|
$({
|
|
for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
|
|
// Skip over any exts that have a match already:
|
|
if exts_by_index.contains_key(&idx) {
|
|
continue
|
|
}
|
|
// Break and record as soon as we find a match:
|
|
if $ident::matches(e.identifier(), e.extra_ty(), types) {
|
|
let ext = $ident::new(client, params.$index)?;
|
|
let boxed_ext: Box<dyn ExtrinsicParamsEncoder + Send + 'static> = Box::new(ext);
|
|
exts_by_index.insert(idx, boxed_ext);
|
|
break
|
|
}
|
|
}
|
|
})+
|
|
|
|
// Next, turn these into an ordered vec, erroring if we haven't matched on any exts yet.
|
|
let mut params = Vec::new();
|
|
for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
|
|
let Some(ext) = exts_by_index.remove(&idx) else {
|
|
if is_type_empty(e.extra_ty(), types) {
|
|
continue
|
|
} else {
|
|
return Err(ExtrinsicParamsError::UnknownTransactionExtension(e.identifier().to_owned()));
|
|
}
|
|
};
|
|
params.push(ext);
|
|
}
|
|
|
|
Ok(AnyOf {
|
|
params,
|
|
_marker: core::marker::PhantomData
|
|
})
|
|
}
|
|
}
|
|
|
|
impl <T, $($ident),+> ExtrinsicParamsEncoder for AnyOf<T, ($($ident,)+)>
|
|
where
|
|
T: Config,
|
|
$($ident: TransactionExtension<T>,)+
|
|
{
|
|
fn encode_value_to(&self, v: &mut Vec<u8>) {
|
|
for ext in &self.params {
|
|
ext.encode_value_to(v);
|
|
}
|
|
}
|
|
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
|
|
for ext in &self.params {
|
|
ext.encode_signer_payload_value_to(v);
|
|
}
|
|
}
|
|
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
|
|
for ext in &self.params {
|
|
ext.encode_implicit_to(v);
|
|
}
|
|
}
|
|
fn inject_signature(&mut self, account_id: &dyn Any, signature: &dyn Any) {
|
|
for ext in &mut self.params {
|
|
ext.inject_signature(account_id, signature);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
const _: () = {
|
|
impl_tuples!(A 0);
|
|
impl_tuples!(A 0, B 1);
|
|
impl_tuples!(A 0, B 1, C 2);
|
|
impl_tuples!(A 0, B 1, C 2, D 3);
|
|
impl_tuples!(A 0, B 1, C 2, D 3, E 4);
|
|
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
|
|
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
|
|
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
|
|
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
|
|
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
|
|
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
};
|
|
|
|
/// Checks to see whether the type being given is empty, ie would require
|
|
/// 0 bytes to encode.
|
|
fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool {
|
|
let Some(ty) = types.resolve(type_id) else {
|
|
// Can't resolve; type may not be empty. Not expected to hit this.
|
|
return false;
|
|
};
|
|
|
|
use scale_info::TypeDef;
|
|
match &ty.type_def {
|
|
TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)),
|
|
TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types),
|
|
TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)),
|
|
// Explicitly list these in case any additions are made in the future.
|
|
TypeDef::BitSequence(_)
|
|
| TypeDef::Variant(_)
|
|
| TypeDef::Sequence(_)
|
|
| TypeDef::Compact(_)
|
|
| TypeDef::Primitive(_) => false,
|
|
}
|
|
}
|