Starting new subxt crate; configs implemented

This commit is contained in:
James Wilson
2025-11-25 12:29:13 +00:00
parent 0c6423bb48
commit 4c27bd8062
66 changed files with 6923 additions and 8 deletions
+2
View File
@@ -0,0 +1,2 @@
mod online_client;
mod offline_client;
View File
View File
+166
View File
@@ -0,0 +1,166 @@
// 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 provides a [`Config`] type, which is used to define various
//! types that are important in order to speak to a particular chain.
//! [`SubstrateConfig`] provides a default set of these types suitable for the
//! default Substrate node implementation, and [`PolkadotConfig`] for a
//! Polkadot node.
mod default_extrinsic_params;
mod extrinsic_params;
pub mod polkadot;
pub mod substrate;
pub mod transaction_extensions;
use codec::{Decode, Encode};
use core::fmt::Debug;
use scale_decode::DecodeAsType;
use scale_encode::EncodeAsType;
use serde::{Serialize, de::DeserializeOwned};
use subxt_metadata::Metadata;
use std::{marker::PhantomData, sync::Arc};
use scale_info_legacy::TypeRegistrySet;
use subxt_rpcs::RpcConfig;
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
pub use transaction_extensions::TransactionExtension;
/// Configuration for a given chain and the runtimes within. This consists of the
/// type information needed to work at the head of the chain (namely submitting
/// transactions), as well as functionality which we might wish to customize for a
/// given chain.
pub trait Config: Clone + Debug + Sized + Send + Sync + 'static {
/// The account ID type; required for constructing extrinsics.
type AccountId: Debug + Clone + Encode + Decode + Serialize + Send;
/// The address type; required for constructing extrinsics.
type Address: Debug + Encode + From<<Self as Config>::AccountId>;
/// The signature type.
type Signature: Debug + Clone + Encode + Decode + Send;
/// The block header.
type Header: Header;
/// This type defines the extrinsic extra and additional parameters.
type ExtrinsicParams: ExtrinsicParams<Self>;
/// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension.
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType + Send;
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
/// This is created on demand with the relevant metadata for a given block, and
/// can then be used to hash things at that block.
type Hasher: Hasher;
/// Return the spec version for a given block number, if available.
///
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here,
/// but the [`crate::client::OfflineClient`] will error if this is not available for the required block number.
fn spec_version_for_block_number(&self, block_number: u32) -> Option<u32>;
/// Return the metadata for a given spec version, if available.
///
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here, and then
/// call [`Config::set_metadata_for_spec_version`] to give the configuration the opportunity to cache it.
/// The [`crate::client::OfflineClient`] will error if this is not available for the required spec version.
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<Metadata>>;
/// Set some metadata for a given spec version. the [`crate::client::OnlineClient`] will call this if it has
/// to retrieve metadata from the chain, to give this the opportunity to cache it. The configuration can
/// do nothing if it prefers.
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<Metadata>,
);
/// Return legacy types (ie types to use with Runtimes that return pre-V14 metadata) for a given spec version.
/// If this returns `None`, [`subxt`] will return an error if type definitions are needed to access some older
/// block.
///
/// This doesn't need to live for long; it will be used to translate any older metadata returned from the node
/// into our [`Metadata`] type, which will then be used.
fn legacy_types_for_spec_version<'this>(
&'this self,
spec_version: u32,
) -> Option<TypeRegistrySet<'this>>;
}
/// `RpcConfigFor<Config>` can be used anywhere which requires an implementation of [`subxt_rpcs::RpcConfig`].
/// This is only needed at the type level, and so there is no way to construct this.
pub struct RpcConfigFor<T> {
marker: PhantomData<T>
}
impl <T: Config> RpcConfig for RpcConfigFor<T> {
type Hash = HashFor<T>;
type Header = T::Header;
type AccountId = T::AccountId;
}
/// Given some [`Config`], this returns the type of hash used.
pub type HashFor<T> = <<T as Config>::Hasher as Hasher>::Hash;
/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
pub type ParamsFor<T> = <<T as Config>::ExtrinsicParams as ExtrinsicParams<T>>::Params;
/// Block hashes must conform to a bunch of things to be used in Subxt.
pub trait Hash:
Debug
+ Copy
+ Send
+ Sync
+ Decode
+ AsRef<[u8]>
+ Serialize
+ DeserializeOwned
+ Encode
+ PartialEq
+ Eq
+ core::hash::Hash
{
}
impl<T> Hash for T where
T: Debug
+ Copy
+ Send
+ Sync
+ Decode
+ AsRef<[u8]>
+ Serialize
+ DeserializeOwned
+ Encode
+ PartialEq
+ Eq
+ core::hash::Hash
{
}
/// This represents the hasher used by a node to hash things like block headers
/// and extrinsics.
pub trait Hasher: Debug + Clone + Send + Sync + 'static {
/// The type of hash produced by this hasher.
type Hash: Hash;
/// Construct a new hasher.
fn new(metadata: &Metadata) -> Self;
/// Hash some bytes to the given output type.
fn hash(&self, s: &[u8]) -> Self::Hash;
}
/// This represents the block header type used by a node.
pub trait Header: Sized + Encode + Decode + Debug + Sync + Send + DeserializeOwned + Clone {
/// Return the block number of this header.
fn number(&self) -> u32;
}
+168
View File
@@ -0,0 +1,168 @@
// 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.
use crate::config::transaction_extensions::CheckMortalityParams;
use super::{Config, HashFor};
use super::{ExtrinsicParams, transaction_extensions};
/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions
/// and how to apply them to a given chain.
pub type DefaultExtrinsicParams<T> = transaction_extensions::AnyOf<
T,
(
transaction_extensions::VerifySignature<T>,
transaction_extensions::CheckSpecVersion,
transaction_extensions::CheckTxVersion,
transaction_extensions::CheckNonce,
transaction_extensions::CheckGenesis<T>,
transaction_extensions::CheckMortality<T>,
transaction_extensions::ChargeAssetTxPayment<T>,
transaction_extensions::ChargeTransactionPayment,
transaction_extensions::CheckMetadataHash,
),
>;
/// A builder that outputs the set of [`super::ExtrinsicParams::Params`] required for
/// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current
/// chain; such values will simply be ignored if so.
pub struct DefaultExtrinsicParamsBuilder<T: Config> {
/// `None` means the tx will be immortal, else it's mortality is described.
mortality: transaction_extensions::CheckMortalityParams<T>,
/// `None` means the nonce will be automatically set.
nonce: Option<u64>,
/// `None` means we'll use the native token.
tip_of_asset_id: Option<T::AssetId>,
tip: u128,
tip_of: u128,
}
impl<T: Config> Default for DefaultExtrinsicParamsBuilder<T> {
fn default() -> Self {
Self {
mortality: CheckMortalityParams::<T>::default(),
tip: 0,
tip_of: 0,
tip_of_asset_id: None,
nonce: None,
}
}
}
impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
/// Configure new extrinsic params. We default to providing no tip
/// and using an immortal transaction unless otherwise configured
pub fn new() -> Self {
Default::default()
}
/// Make the transaction immortal, meaning it will never expire. This means that it could, in
/// theory, be pending for a long time and only be included many blocks into the future.
pub fn immortal(mut self) -> Self {
self.mortality = transaction_extensions::CheckMortalityParams::<T>::immortal();
self
}
/// Make the transaction mortal, given a number of blocks it will be mortal for from
/// the current block at the time of submission.
///
/// # Warning
///
/// This will ultimately return an error if used for creating extrinsic offline, because we need
/// additional information in order to set the mortality properly.
///
/// When creating offline transactions, you must use [`Self::mortal_from_unchecked`] instead to set
/// the mortality. This provides all of the necessary information which we must otherwise be online
/// in order to obtain.
pub fn mortal(mut self, for_n_blocks: u64) -> Self {
self.mortality = transaction_extensions::CheckMortalityParams::<T>::mortal(for_n_blocks);
self
}
/// Configure a transaction that will be mortal for the number of blocks given, and from the
/// block details provided. Prefer to use [`Self::mortal()`] where possible, which prevents
/// the block number and hash from being misaligned.
pub fn mortal_from_unchecked(
mut self,
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: HashFor<T>,
) -> Self {
self.mortality = transaction_extensions::CheckMortalityParams::mortal_from_unchecked(
for_n_blocks,
from_block_n,
from_block_hash,
);
self
}
/// Provide a specific nonce for the submitter of the extrinsic
pub fn nonce(mut self, nonce: u64) -> Self {
self.nonce = Some(nonce);
self
}
/// Provide a tip to the block author in the chain's native token.
pub fn tip(mut self, tip: u128) -> Self {
self.tip = tip;
self.tip_of = tip;
self.tip_of_asset_id = None;
self
}
/// Provide a tip to the block author using the token denominated by the `asset_id` provided. This
/// is not applicable on chains which don't use the `ChargeAssetTxPayment` signed extension; in this
/// case, no tip will be given.
pub fn tip_of(mut self, tip: u128, asset_id: T::AssetId) -> Self {
self.tip = 0;
self.tip_of = tip;
self.tip_of_asset_id = Some(asset_id);
self
}
/// Build the extrinsic parameters.
pub fn build(self) -> <DefaultExtrinsicParams<T> as ExtrinsicParams<T>>::Params {
let check_mortality_params = self.mortality;
let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id {
transaction_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
} else {
transaction_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
};
let charge_transaction_params =
transaction_extensions::ChargeTransactionPaymentParams::tip(self.tip);
let check_nonce_params = if let Some(nonce) = self.nonce {
transaction_extensions::CheckNonceParams::with_nonce(nonce)
} else {
transaction_extensions::CheckNonceParams::from_chain()
};
(
(),
(),
(),
check_nonce_params,
(),
check_mortality_params,
charge_asset_tx_params,
charge_transaction_params,
(),
)
}
}
#[cfg(test)]
mod test {
use super::*;
fn assert_default<T: Default>(_t: T) {}
#[test]
fn params_are_default() {
let params = DefaultExtrinsicParamsBuilder::<crate::config::PolkadotConfig>::new().build();
assert_default(params)
}
}
+159
View File
@@ -0,0 +1,159 @@
// 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 a trait which controls the parameters that must
//! be provided in order to successfully construct an extrinsic.
//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose
//! implementation of this that will work in many cases.
use crate::{
config::{Config, HashFor},
error::ExtrinsicParamsError,
};
use subxt_metadata::Metadata;
use std::sync::Arc;
use core::any::Any;
/// This provides access to some relevant client state in transaction extensions,
/// and is just a combination of some of the available properties.
#[derive(Clone, Debug)]
pub struct ClientState<T: Config> {
/// Genesis hash.
pub genesis_hash: HashFor<T>,
/// Runtime version.
pub runtime_version: RuntimeVersion,
/// Metadata.
pub metadata: Arc<Metadata>,
}
/// Runtime version information needed to submit transactions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RuntimeVersion {
/// Version of the runtime specification. A full-node will not attempt to use its native
/// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`,
/// `spec_version` and `authoring_version` are the same between Wasm and native.
pub spec_version: u32,
/// All existing dispatches are fully compatible when this number doesn't change. If this
/// number changes, then `spec_version` must change, also.
///
/// This number must change when an existing dispatchable (module ID, dispatch ID) is changed,
/// either through an alteration in its user-level semantics, a parameter
/// added/removed/changed, a dispatchable being removed, a module being removed, or a
/// dispatchable/module changing its index.
///
/// It need *not* change when a new module is added or when a dispatchable is added.
pub transaction_version: u32,
}
/// This trait allows you to configure the "signed extra" and
/// "additional" parameters that are a part of the transaction payload
/// or the signer payload respectively.
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + Send + 'static {
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
type Params: Params<T>;
/// Construct a new instance of our [`ExtrinsicParams`].
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError>;
}
/// This trait is expected to be implemented for any [`ExtrinsicParams`], and
/// defines how to encode the "additional" and "extra" params. Both functions
/// are optional and will encode nothing by default.
pub trait ExtrinsicParamsEncoder: 'static {
/// This is expected to SCALE encode the transaction extension data to some
/// buffer that has been provided. This data is attached to the transaction
/// and also (by default) attached to the signer payload which is signed to
/// provide a signature for the transaction.
///
/// If [`ExtrinsicParamsEncoder::encode_signer_payload_value_to`] is implemented,
/// then that will be used instead when generating a signer payload. Useful for
/// eg the `VerifySignature` extension, which is send with the transaction but
/// is not a part of the signer payload.
fn encode_value_to(&self, _v: &mut Vec<u8>) {}
/// See [`ExtrinsicParamsEncoder::encode_value_to`]. This defaults to calling that
/// method, but if implemented will dictate what is encoded to the signer payload.
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
self.encode_value_to(v);
}
/// This is expected to SCALE encode the "implicit" (formally "additional")
/// parameters to some buffer that has been provided. These parameters are
/// _not_ sent along with the transaction, but are taken into account when
/// signing it, meaning the client and node must agree on their values.
fn encode_implicit_to(&self, _v: &mut Vec<u8>) {}
/// Set the signature. This happens after we have constructed the extrinsic params,
/// and so is defined here rather than on the params, below. We need to use `&dyn Any`
/// to keep this trait object safe, but can downcast in the impls.
///
/// # Panics
///
/// Implementations of this will likely try to downcast the provided `account_id`
/// and `signature` into `T::AccountId` and `T::Signature` (where `T: Config`), and are
/// free to panic if this downcasting does not succeed.
///
/// In typical usage, this is not a problem, since this method is only called internally
/// and provided values which line up with the relevant `Config`. In theory though, this
/// method can be called manually with any types, hence this warning.
fn inject_signature(&mut self, _account_id: &dyn Any, _signature: &dyn Any) {}
}
/// The parameters (ie [`ExtrinsicParams::Params`]) can also have data injected into them,
/// allowing Subxt to retrieve data from the chain and amend the parameters with it when
/// online.
pub trait Params<T: Config> {
/// Set the account nonce.
fn inject_account_nonce(&mut self, _nonce: u64) {}
/// Set the current block.
fn inject_block(&mut self, _number: u64, _hash: HashFor<T>) {}
}
impl<T: Config> Params<T> for () {}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
impl <Conf: Config, $($ident : Params<Conf>),+> Params<Conf> for ($($ident,)+){
fn inject_account_nonce(&mut self, nonce: u64) {
$(self.$index.inject_account_nonce(nonce);)+
}
fn inject_block(&mut self, number: u64, hash: HashFor<Conf>) {
$(self.$index.inject_block(number, hash);)+
}
}
}
}
#[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, T 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, T 19, U 20);
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, T 19, U 20, V 21);
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, T 19, U 20, V 21, W 22);
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, T 19, U 20, V 21, W 22, X 23);
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, T 19, U 20, V 21, W 22, X 23, Y 24);
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, T 19, U 20, V 21, W 22, X 23, Y 24, Z 25);
};
+104
View File
@@ -0,0 +1,104 @@
// 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.
//! Polkadot specific configuration
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
use crate::config::substrate::{SubstrateConfig, SubstrateConfigBuilder};
use std::sync::Arc;
use scale_info_legacy::TypeRegistrySet;
use subxt_metadata::Metadata;
pub use crate::config::substrate::{ SpecVersionForRange, SubstrateHeader };
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
pub use primitive_types::{H256, U256};
/// Construct a [`PolkadotConfig`] using this.
pub struct PolkadotConfigBuilder(SubstrateConfigBuilder);
impl PolkadotConfigBuilder {
/// Create a new [`PolkadotConfigBuilder`].
pub fn new() -> Self {
let inner = SubstrateConfigBuilder::new()
.set_legacy_types(frame_decode::legacy_types::polkadot::relay_chain());
PolkadotConfigBuilder(inner)
}
/// Set the metadata to be used for decoding blocks at the given spec versions.
pub fn set_metadata_for_spec_versions(
mut self,
ranges: impl Iterator<Item = (u32, Arc<Metadata>)>,
) -> Self {
self = Self(self.0.set_metadata_for_spec_versions(ranges));
self
}
/// Given an iterator of block ranges to spec version of the form `(start, end, spec_version)`, add them
/// to this configuration.
pub fn set_spec_version_for_block_ranges(
mut self,
ranges: impl Iterator<Item = SpecVersionForRange>,
) -> Self {
self = Self(self.0.set_spec_version_for_block_ranges(ranges));
self
}
/// Construct the [`PolkadotConfig`] from this builder.
pub fn build(self) -> PolkadotConfig {
PolkadotConfig(self.0.build())
}
}
/// Configuration that's suitable for the Polkadot Relay Chain.
#[derive(Debug, Clone)]
pub struct PolkadotConfig(SubstrateConfig);
impl Config for PolkadotConfig {
type AccountId = <SubstrateConfig as Config>::AccountId;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type AssetId = <SubstrateConfig as Config>::AssetId;
// Address on Polkadot has no account index, whereas it's u32 on
// the default substrate dev node.
type Address = MultiAddress<Self::AccountId, ()>;
// These are the same as the default substrate node, but redefined
// because we need to pass the PolkadotConfig trait as a param.
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> Option<TypeRegistrySet<'_>> {
self.0.legacy_types_for_spec_version(spec_version)
}
fn spec_version_for_block_number(&self, block_number: u32) -> Option<u32> {
self.0.spec_version_for_block_number(block_number)
}
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<Metadata>> {
self.0.metadata_for_spec_version(spec_version)
}
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<Metadata>,
) {
self.0.set_metadata_for_spec_version(spec_version, metadata)
}
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for a polkadot node.
pub type PolkadotExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// A builder which leads to [`PolkadotExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type PolkadotExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
+529
View File
@@ -0,0 +1,529 @@
// 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.
//! Substrate specific configuration
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, Hasher, Header};
use crate::config::Hash;
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
use codec::{Decode, Encode};
pub use primitive_types::{H256, U256};
use serde::{Deserialize, Serialize};
use subxt_metadata::Metadata;
use crate::utils::RangeMap;
use scale_info_legacy::{ChainTypeRegistry, TypeRegistrySet};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
/// Construct a [`SubstrateConfig`] using this.
pub struct SubstrateConfigBuilder {
legacy_types: Option<ChainTypeRegistry>,
spec_version_for_block_number: RangeMap<u32, u32>,
metadata_for_spec_version: Mutex<HashMap<u32, Arc<Metadata>>>,
}
impl Default for SubstrateConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl SubstrateConfigBuilder {
/// Create a new builder to construct a [`SubstrateConfig`] from.
pub fn new() -> Self {
SubstrateConfigBuilder {
legacy_types: None,
spec_version_for_block_number: RangeMap::empty(),
metadata_for_spec_version: Mutex::new(HashMap::new()),
}
}
/// Set the legacy types to use for this configuration. This enables support for
/// blocks produced by Runtimes that emit metadata older than V14.
pub fn set_legacy_types(mut self, legacy_types: ChainTypeRegistry) -> Self {
self.legacy_types = Some(legacy_types);
self
}
/// Set the metadata to be used for decoding blocks at the given spec versions.
pub fn set_metadata_for_spec_versions(
self,
ranges: impl Iterator<Item = (u32, Arc<Metadata>)>,
) -> Self {
let mut map = self.metadata_for_spec_version.lock().unwrap();
for (spec_version, metadata) in ranges {
map.insert(spec_version, metadata);
}
drop(map);
self
}
/// Given an iterator of block ranges to spec version of the form `(start, end, spec_version)`, add them
/// to this configuration.
pub fn set_spec_version_for_block_ranges(
mut self,
ranges: impl Iterator<Item = SpecVersionForRange>,
) -> Self {
let mut m = RangeMap::builder();
for version_for_range in ranges {
let start = version_for_range.block_range.start;
let end = version_for_range.block_range.end;
let spec_version = version_for_range.spec_version;
m = m.add_range(start, end, spec_version);
}
self.spec_version_for_block_number = m.build();
self
}
/// Construct the [`SubstrateConfig`] from this builder.
pub fn build(self) -> SubstrateConfig {
SubstrateConfig {
inner: Arc::new(SubstrateConfigInner {
legacy_types: self.legacy_types,
spec_version_for_block_number: self.spec_version_for_block_number,
metadata_for_spec_version: self.metadata_for_spec_version,
})
}
}
}
/// Define a spec version for a range of blocks. The new spec version is expected
/// to begin at the first block in the range and end just prior to the last block
/// in the range.
pub struct SpecVersionForRange {
/// The block range that this spec version applies to. Inclusive of the start
/// and exclusive of the enc.
pub block_range: std::ops::Range<u32>,
/// The spec version at this block range.
pub spec_version: u32,
}
/// Configuration that's suitable for standard Substrate chains (ie those
/// that have not customized the block hash type).
#[derive(Debug, Clone)]
pub struct SubstrateConfig {
inner: Arc<SubstrateConfigInner>
}
#[derive(Debug)]
struct SubstrateConfigInner {
legacy_types: Option<ChainTypeRegistry>,
spec_version_for_block_number: RangeMap<u32, u32>,
metadata_for_spec_version: Mutex<HashMap<u32, Arc<Metadata>>>,
}
impl SubstrateConfig {
/// Build a new [`SubstrateConfig`].
pub fn builder() -> SubstrateConfigBuilder {
SubstrateConfigBuilder::new()
}
}
impl Config for SubstrateConfig {
type AccountId = AccountId32;
type Address = MultiAddress<Self::AccountId, u32>;
type Signature = MultiSignature;
type Hasher = DynamicHasher256;
type Header = SubstrateHeader<<Self::Hasher as Hasher>::Hash>;
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
type AssetId = u32;
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> Option<TypeRegistrySet<'_>> {
self.inner
.legacy_types
.as_ref()
.map(|types| types.for_spec_version(spec_version as u64))
}
fn spec_version_for_block_number(&self, block_number: u32) -> Option<u32> {
self.inner.spec_version_for_block_number
.get(block_number)
.copied()
}
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<Metadata>> {
self.inner
.metadata_for_spec_version
.lock()
.unwrap()
.get(&spec_version)
.cloned()
}
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<Metadata>,
) {
self.inner
.metadata_for_spec_version
.lock()
.unwrap()
.insert(spec_version, metadata);
}
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for the default substrate node.
pub type SubstrateExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type SubstrateExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
/// A hasher (ie implements [`Hasher`]) which hashes values using the blaks2_256 algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlakeTwo256;
impl Hasher for BlakeTwo256 {
type Hash = H256;
fn new(_metadata: &Metadata) -> Self {
Self
}
fn hash(&self, s: &[u8]) -> Self::Hash {
sp_crypto_hashing::blake2_256(s).into()
}
}
/// A hasher (ie implements [`Hasher`]) which inspects the runtime metadata to decide how to
/// hash types, falling back to blake2_256 if the hasher information is not available.
///
/// Currently this hasher supports only `BlakeTwo256` and `Keccak256` hashing methods.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DynamicHasher256(HashType);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HashType {
// Most chains use this:
BlakeTwo256,
// Chains like Hyperbridge use this (tends to be eth compatible chains)
Keccak256,
// If we don't have V16 metadata, we'll emit this and default to BlakeTwo256.
Unknown,
}
impl Hasher for DynamicHasher256 {
type Hash = H256;
fn new(metadata: &Metadata) -> Self {
// Determine the Hash associated type used for the current chain, if possible.
let Some(system_pallet) = metadata.pallet_by_name("System") else {
return Self(HashType::Unknown);
};
let Some(hash_ty_id) = system_pallet.associated_type_id("Hashing") else {
return Self(HashType::Unknown);
};
let ty = metadata
.types()
.resolve(hash_ty_id)
.expect("Type information for 'Hashing' associated type should be in metadata");
let hash_type = match ty.path.ident().as_deref().unwrap_or("") {
"BlakeTwo256" => HashType::BlakeTwo256,
"Keccak256" => HashType::Keccak256,
_ => HashType::Unknown,
};
Self(hash_type)
}
fn hash(&self, s: &[u8]) -> Self::Hash {
match self.0 {
HashType::BlakeTwo256 | HashType::Unknown => sp_crypto_hashing::blake2_256(s).into(),
HashType::Keccak256 => sp_crypto_hashing::keccak_256(s).into(),
}
}
}
/// A generic Substrate header type, adapted from `sp_runtime::generic::Header`.
/// The block number and hasher can be configured to adapt this for other nodes.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubstrateHeader<Hash> {
/// The parent hash.
pub parent_hash: Hash,
/// The block number.
#[serde(
serialize_with = "serialize_number",
deserialize_with = "deserialize_number"
)]
#[codec(compact)]
pub number: u32,
/// The state trie merkle root
pub state_root: Hash,
/// The merkle root of the extrinsics.
pub extrinsics_root: Hash,
/// A chain-specific digest of data useful for light clients or referencing auxiliary data.
pub digest: Digest,
}
impl<H> Header for SubstrateHeader<H>
where
H: Hash,
SubstrateHeader<H>: Encode + Decode,
{
fn number(&self) -> u32 {
self.number.into()
}
}
/// Generic header digest. From `sp_runtime::generic::digest`.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
pub struct Digest {
/// A list of digest items.
pub logs: Vec<DigestItem>,
}
/// Digest item that is able to encode/decode 'system' digest items and
/// provide opaque access to other items. From `sp_runtime::generic::digest`.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DigestItem {
/// A pre-runtime digest.
///
/// These are messages from the consensus engine to the runtime, although
/// the consensus engine can (and should) read them itself to avoid
/// code and state duplication. It is erroneous for a runtime to produce
/// these, but this is not (yet) checked.
///
/// NOTE: the runtime is not allowed to panic or fail in an `on_initialize`
/// call if an expected `PreRuntime` digest is not present. It is the
/// responsibility of a external block verifier to check this. Runtime API calls
/// will initialize the block without pre-runtime digests, so initialization
/// cannot fail when they are missing.
PreRuntime(ConsensusEngineId, Vec<u8>),
/// A message from the runtime to the consensus engine. This should *never*
/// be generated by the native code of any consensus engine, but this is not
/// checked (yet).
Consensus(ConsensusEngineId, Vec<u8>),
/// Put a Seal on it. This is only used by native code, and is never seen
/// by runtimes.
Seal(ConsensusEngineId, Vec<u8>),
/// Some other thing. Unsupported and experimental.
Other(Vec<u8>),
/// An indication for the light clients that the runtime execution
/// environment is updated.
///
/// Currently this is triggered when:
/// 1. Runtime code blob is changed or
/// 2. `heap_pages` value is changed.
RuntimeEnvironmentUpdated,
}
// From sp_runtime::generic, DigestItem enum indexes are encoded using this:
#[repr(u32)]
#[derive(Encode, Decode)]
enum DigestItemType {
Other = 0u32,
Consensus = 4u32,
Seal = 5u32,
PreRuntime = 6u32,
RuntimeEnvironmentUpdated = 8u32,
}
impl Encode for DigestItem {
fn encode(&self) -> Vec<u8> {
let mut v = Vec::new();
match self {
Self::Consensus(val, data) => {
DigestItemType::Consensus.encode_to(&mut v);
(val, data).encode_to(&mut v);
}
Self::Seal(val, sig) => {
DigestItemType::Seal.encode_to(&mut v);
(val, sig).encode_to(&mut v);
}
Self::PreRuntime(val, data) => {
DigestItemType::PreRuntime.encode_to(&mut v);
(val, data).encode_to(&mut v);
}
Self::Other(val) => {
DigestItemType::Other.encode_to(&mut v);
val.encode_to(&mut v);
}
Self::RuntimeEnvironmentUpdated => {
DigestItemType::RuntimeEnvironmentUpdated.encode_to(&mut v);
}
}
v
}
}
impl Decode for DigestItem {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let item_type: DigestItemType = Decode::decode(input)?;
match item_type {
DigestItemType::PreRuntime => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::PreRuntime(vals.0, vals.1))
}
DigestItemType::Consensus => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::Consensus(vals.0, vals.1))
}
DigestItemType::Seal => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::Seal(vals.0, vals.1))
}
DigestItemType::Other => Ok(Self::Other(Decode::decode(input)?)),
DigestItemType::RuntimeEnvironmentUpdated => Ok(Self::RuntimeEnvironmentUpdated),
}
}
}
/// Consensus engine unique ID. From `sp_runtime::ConsensusEngineId`.
pub type ConsensusEngineId = [u8; 4];
impl serde::Serialize for DigestItem {
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.using_encoded(|bytes| impl_serde::serialize::serialize(bytes, seq))
}
}
impl<'a> serde::Deserialize<'a> for DigestItem {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let r = impl_serde::serialize::deserialize(de)?;
Decode::decode(&mut &r[..])
.map_err(|e| serde::de::Error::custom(format!("Decode error: {e}")))
}
}
fn serialize_number<S, T: Copy + Into<U256>>(val: &T, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let u256: U256 = (*val).into();
serde::Serialize::serialize(&u256, s)
}
fn deserialize_number<'a, D, T: TryFrom<U256>>(d: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'a>,
{
// At the time of writing, Smoldot gives back block numbers in numeric rather
// than hex format. So let's support deserializing from both here:
let number_or_hex = NumberOrHex::deserialize(d)?;
let u256 = number_or_hex.into_u256();
TryFrom::try_from(u256).map_err(|_| serde::de::Error::custom("Try from failed"))
}
/// A number type that can be serialized both as a number or a string that encodes a number in a
/// string.
///
/// We allow two representations of the block number as input. Either we deserialize to the type
/// that is specified in the block type or we attempt to parse given hex value.
///
/// The primary motivation for having this type is to avoid overflows when using big integers in
/// JavaScript (which we consider as an important RPC API consumer).
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum NumberOrHex {
/// The number represented directly.
Number(u64),
/// Hex representation of the number.
Hex(U256),
}
impl NumberOrHex {
/// Converts this number into an U256.
pub fn into_u256(self) -> U256 {
match self {
NumberOrHex::Number(n) => n.into(),
NumberOrHex::Hex(h) => h,
}
}
}
impl From<NumberOrHex> for U256 {
fn from(num_or_hex: NumberOrHex) -> U256 {
num_or_hex.into_u256()
}
}
macro_rules! into_number_or_hex {
($($t: ty)+) => {
$(
impl From<$t> for NumberOrHex {
fn from(x: $t) -> Self {
NumberOrHex::Number(x.into())
}
}
)+
}
}
into_number_or_hex!(u8 u16 u32 u64);
impl From<u128> for NumberOrHex {
fn from(n: u128) -> Self {
NumberOrHex::Hex(n.into())
}
}
impl From<U256> for NumberOrHex {
fn from(n: U256) -> Self {
NumberOrHex::Hex(n)
}
}
#[cfg(test)]
mod test {
use super::*;
// Smoldot returns numeric block numbers in the header at the time of writing;
// ensure we can deserialize them properly.
#[test]
fn can_deserialize_numeric_block_number() {
let numeric_block_number_json = r#"
{
"digest": {
"logs": []
},
"extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": 4,
"parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"#;
let header: SubstrateHeader<H256> =
serde_json::from_str(numeric_block_number_json).expect("valid block header");
assert_eq!(header.number(), 4);
}
// Substrate returns hex block numbers; ensure we can also deserialize those OK.
#[test]
fn can_deserialize_hex_block_number() {
let numeric_block_number_json = r#"
{
"digest": {
"logs": []
},
"extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": "0x04",
"parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"#;
let header: SubstrateHeader<H256> =
serde_json::from_str(numeric_block_number_json).expect("valid block header");
assert_eq!(header.number(), 4);
}
}
+703
View File
@@ -0,0 +1,703 @@
// 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, ClientState };
use crate::config::ExtrinsicParamsEncoder;
use crate::config::{Config, HashFor};
use crate::error::ExtrinsicParamsError;
use crate::utils::{Era, Static};
use codec::{Compact, Encode};
use core::any::Any;
use core::fmt::Debug;
use derive_where::derive_where;
use std::collections::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!(&params.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,
}
}
+933
View File
@@ -0,0 +1,933 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types representing the errors that can be returned.
mod dispatch_error;
mod hex;
use thiserror::Error as DeriveError;
#[cfg(feature = "unstable-light-client")]
pub use subxt_lightclient::LightClientError;
// Re-export dispatch error types:
pub use dispatch_error::{
ArithmeticError, DispatchError, ModuleError, TokenError, TransactionalError,
};
// Re-expose the errors we use from other crates here:
pub use subxt_metadata::Metadata;
pub use hex::Hex;
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
pub use subxt_metadata::TryFromError as MetadataTryFromError;
/// A global error type. Any of the errors exposed here can convert into this
/// error via `.into()`, but this error isn't itself exposed from anything.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
ExtrinsicDecodeErrorAt(#[from] ExtrinsicDecodeErrorAt),
#[error(transparent)]
ConstantError(#[from] ConstantError),
#[error(transparent)]
CustomValueError(#[from] CustomValueError),
#[error(transparent)]
StorageKeyError(#[from] StorageKeyError),
#[error(transparent)]
StorageValueError(#[from] StorageValueError),
#[error(transparent)]
BackendError(#[from] BackendError),
#[error(transparent)]
BlockError(#[from] BlockError),
#[error(transparent)]
AccountNonceError(#[from] AccountNonceError),
#[error(transparent)]
OnlineClientError(#[from] OnlineClientError),
#[error(transparent)]
RuntimeUpdaterError(#[from] RuntimeUpdaterError),
#[error(transparent)]
RuntimeUpdateeApplyError(#[from] RuntimeUpdateeApplyError),
#[error(transparent)]
RuntimeApiError(#[from] RuntimeApiError),
#[error(transparent)]
EventsError(#[from] EventsError),
#[error(transparent)]
ExtrinsicError(#[from] ExtrinsicError),
#[error(transparent)]
ViewFunctionError(#[from] ViewFunctionError),
#[error(transparent)]
TransactionProgressError(#[from] TransactionProgressError),
#[error(transparent)]
TransactionStatusError(#[from] TransactionStatusError),
#[error(transparent)]
TransactionEventsError(#[from] TransactionEventsError),
#[error(transparent)]
TransactionFinalizedSuccessError(#[from] TransactionFinalizedSuccessError),
#[error(transparent)]
ModuleErrorDetailsError(#[from] ModuleErrorDetailsError),
#[error(transparent)]
ModuleErrorDecodeError(#[from] ModuleErrorDecodeError),
#[error(transparent)]
DispatchErrorDecodeError(#[from] DispatchErrorDecodeError),
#[error(transparent)]
StorageError(#[from] StorageError),
// Dev note: Subxt doesn't directly return Raw* errors. These exist so that when
// users use common crates (like parity-scale-codec and subxt-rpcs), errors returned
// there can be handled automatically using ? when the expected error is subxt::Error.
#[error("Other RPC client error: {0}")]
OtherRpcClientError(#[from] subxt_rpcs::Error),
#[error("Other codec error: {0}")]
OtherCodecError(#[from] codec::Error),
#[cfg(feature = "unstable-light-client")]
#[error("Other light client error: {0}")]
OtherLightClientError(#[from] subxt_lightclient::LightClientError),
#[cfg(feature = "unstable-light-client")]
#[error("Other light client RPC error: {0}")]
OtherLightClientRpcError(#[from] subxt_lightclient::LightClientRpcError),
// Dev note: Nothing in subxt should ever emit this error. It can instead be used
// to easily map other errors into a subxt::Error for convenience. Some From impls
// make this automatic for common "other" error types.
#[error("Other error: {0}")]
Other(Box<dyn std::error::Error + Send + Sync + 'static>),
}
impl From<std::convert::Infallible> for Error {
fn from(value: std::convert::Infallible) -> Self {
match value {}
}
}
impl Error {
/// Create a generic error. This is a quick workaround when you are using
/// [`Error`] and have a non-Subxt error to return.
pub fn other<E: std::error::Error + Send + Sync + 'static>(error: E) -> Error {
Error::Other(Box::new(error))
}
/// Create a generic error from a string. This is a quick workaround when you are using
/// [`Error`] and have a non-Subxt error to return.
pub fn other_str(error: impl Into<String>) -> Error {
#[derive(thiserror::Error, Debug, Clone)]
#[error("{0}")]
struct StrError(String);
Error::Other(Box::new(StrError(error.into())))
}
/// Checks whether the error was caused by a RPC re-connection.
pub fn is_disconnected_will_reconnect(&self) -> bool {
matches!(
self.backend_error(),
Some(BackendError::Rpc(RpcError::ClientError(
subxt_rpcs::Error::DisconnectedWillReconnect(_)
)))
)
}
/// Checks whether the error was caused by a RPC request being rejected.
pub fn is_rpc_limit_reached(&self) -> bool {
matches!(
self.backend_error(),
Some(BackendError::Rpc(RpcError::LimitReached))
)
}
fn backend_error(&self) -> Option<&BackendError> {
match self {
Error::BlockError(e) => e.backend_error(),
Error::AccountNonceError(e) => e.backend_error(),
Error::OnlineClientError(e) => e.backend_error(),
Error::RuntimeUpdaterError(e) => e.backend_error(),
Error::RuntimeApiError(e) => e.backend_error(),
Error::EventsError(e) => e.backend_error(),
Error::ExtrinsicError(e) => e.backend_error(),
Error::ViewFunctionError(e) => e.backend_error(),
Error::TransactionProgressError(e) => e.backend_error(),
Error::TransactionEventsError(e) => e.backend_error(),
Error::TransactionFinalizedSuccessError(e) => e.backend_error(),
Error::StorageError(e) => e.backend_error(),
// Any errors that **don't** return a BackendError anywhere will return None:
_ => None,
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum BackendError {
#[error("Backend error: RPC error: {0}")]
Rpc(#[from] RpcError),
#[error("Backend error: Could not find metadata version {0}")]
MetadataVersionNotFound(u32),
#[error("Backend error: Could not codec::Decode Runtime API response: {0}")]
CouldNotScaleDecodeRuntimeResponse(codec::Error),
#[error("Backend error: Could not codec::Decode metadata bytes into subxt::Metadata: {0}")]
CouldNotDecodeMetadata(codec::Error),
// This is for errors in `Backend` implementations which aren't any of the "pre-defined" set above:
#[error("Custom backend error: {0}")]
Other(String),
}
impl BackendError {
/// Checks whether the error was caused by a RPC re-connection.
pub fn is_disconnected_will_reconnect(&self) -> bool {
matches!(
self,
BackendError::Rpc(RpcError::ClientError(
subxt_rpcs::Error::DisconnectedWillReconnect(_)
))
)
}
/// Checks whether the error was caused by a RPC request being rejected.
pub fn is_rpc_limit_reached(&self) -> bool {
matches!(self, BackendError::Rpc(RpcError::LimitReached))
}
}
impl From<subxt_rpcs::Error> for BackendError {
fn from(value: subxt_rpcs::Error) -> Self {
BackendError::Rpc(RpcError::ClientError(value))
}
}
/// An RPC error. Since we are generic over the RPC client that is used,
/// the error is boxed and could be casted.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RpcError {
/// Error related to the RPC client.
#[error("RPC error: {0}")]
ClientError(#[from] subxt_rpcs::Error),
/// This error signals that we got back a [`subxt_rpcs::methods::chain_head::MethodResponse::LimitReached`],
/// which is not technically an RPC error but is treated as an error in our own APIs.
#[error("RPC error: limit reached")]
LimitReached,
/// The RPC subscription was dropped.
#[error("RPC error: subscription dropped.")]
SubscriptionDropped,
}
/// Block error
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum BlockError {
#[error(
"Could not find the block body with hash {block_hash} (perhaps it was on a non-finalized fork?)"
)]
BlockNotFound { block_hash: Hex },
#[error("Could not download the block header with hash {block_hash}: {reason}")]
CouldNotGetBlockHeader {
block_hash: Hex,
reason: BackendError,
},
#[error("Could not download the latest block header: {0}")]
CouldNotGetLatestBlock(BackendError),
#[error("Could not subscribe to all blocks: {0}")]
CouldNotSubscribeToAllBlocks(BackendError),
#[error("Could not subscribe to best blocks: {0}")]
CouldNotSubscribeToBestBlocks(BackendError),
#[error("Could not subscribe to finalized blocks: {0}")]
CouldNotSubscribeToFinalizedBlocks(BackendError),
#[error("Error getting account nonce at block {block_hash}")]
AccountNonceError {
block_hash: Hex,
account_id: Hex,
reason: AccountNonceError,
},
}
impl BlockError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
BlockError::CouldNotGetBlockHeader { reason: e, .. }
| BlockError::CouldNotGetLatestBlock(e)
| BlockError::CouldNotSubscribeToAllBlocks(e)
| BlockError::CouldNotSubscribeToBestBlocks(e)
| BlockError::CouldNotSubscribeToFinalizedBlocks(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum AccountNonceError {
#[error("Could not retrieve account nonce: {0}")]
CouldNotRetrieve(#[from] BackendError),
#[error("Could not decode account nonce: {0}")]
CouldNotDecode(#[from] codec::Error),
#[error("Wrong number of account nonce bytes returned: {0} (expected 2, 4 or 8)")]
WrongNumberOfBytes(usize),
}
impl AccountNonceError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
AccountNonceError::CouldNotRetrieve(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum OnlineClientError {
#[error("Cannot construct OnlineClient: {0}")]
RpcError(#[from] subxt_rpcs::Error),
#[error(
"Cannot construct OnlineClient: Cannot fetch latest finalized block to obtain init details from: {0}"
)]
CannotGetLatestFinalizedBlock(BackendError),
#[error("Cannot construct OnlineClient: Cannot fetch genesis hash: {0}")]
CannotGetGenesisHash(BackendError),
#[error("Cannot construct OnlineClient: Cannot fetch current runtime version: {0}")]
CannotGetCurrentRuntimeVersion(BackendError),
#[error("Cannot construct OnlineClient: Cannot fetch metadata: {0}")]
CannotFetchMetadata(BackendError),
}
impl OnlineClientError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
OnlineClientError::CannotGetLatestFinalizedBlock(e)
| OnlineClientError::CannotGetGenesisHash(e)
| OnlineClientError::CannotGetCurrentRuntimeVersion(e)
| OnlineClientError::CannotFetchMetadata(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum RuntimeUpdaterError {
#[error("Error subscribing to runtime updates: The update stream ended unexpectedly")]
UnexpectedEndOfUpdateStream,
#[error("Error subscribing to runtime updates: The finalized block stream ended unexpectedly")]
UnexpectedEndOfBlockStream,
#[error("Error subscribing to runtime updates: Can't stream runtime version: {0}")]
CannotStreamRuntimeVersion(BackendError),
#[error("Error subscribing to runtime updates: Can't get next runtime version in stream: {0}")]
CannotGetNextRuntimeVersion(BackendError),
#[error("Error subscribing to runtime updates: Cannot stream finalized blocks: {0}")]
CannotStreamFinalizedBlocks(BackendError),
#[error("Error subscribing to runtime updates: Cannot get next finalized block in stream: {0}")]
CannotGetNextFinalizedBlock(BackendError),
#[error("Cannot fetch new metadata for runtime update: {0}")]
CannotFetchNewMetadata(BackendError),
#[error(
"Error subscribing to runtime updates: Cannot find the System.LastRuntimeUpgrade storage entry"
)]
CantFindSystemLastRuntimeUpgrade,
#[error("Error subscribing to runtime updates: Cannot fetch last runtime upgrade: {0}")]
CantFetchLastRuntimeUpgrade(StorageError),
#[error("Error subscribing to runtime updates: Cannot decode last runtime upgrade: {0}")]
CannotDecodeLastRuntimeUpgrade(StorageValueError),
}
impl RuntimeUpdaterError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
RuntimeUpdaterError::CannotStreamRuntimeVersion(e)
| RuntimeUpdaterError::CannotGetNextRuntimeVersion(e)
| RuntimeUpdaterError::CannotStreamFinalizedBlocks(e)
| RuntimeUpdaterError::CannotGetNextFinalizedBlock(e)
| RuntimeUpdaterError::CannotFetchNewMetadata(e) => Some(e),
_ => None,
}
}
}
/// Error that can occur during upgrade.
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum RuntimeUpdateeApplyError {
#[error("The proposed runtime update is the same as the current version")]
SameVersion,
}
/// Error working with Runtime APIs
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum RuntimeApiError {
#[error("The static Runtime API address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Runtime API trait not found: {0}")]
TraitNotFound(String),
#[error("Runtime API method {method_name} not found in trait {trait_name}")]
MethodNotFound {
trait_name: String,
method_name: String,
},
#[error("Failed to encode Runtime API inputs: {0}")]
CouldNotEncodeInputs(frame_decode::runtime_apis::RuntimeApiInputsEncodeError),
#[error("Failed to decode Runtime API: {0}")]
CouldNotDecodeResponse(frame_decode::runtime_apis::RuntimeApiDecodeError<u32>),
#[error("Cannot access Runtime APIs at latest block: Cannot fetch latest finalized block: {0}")]
CannotGetLatestFinalizedBlock(BackendError),
#[error("Cannot call the Runtime API: {0}")]
CannotCallApi(BackendError),
}
impl RuntimeApiError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
RuntimeApiError::CannotGetLatestFinalizedBlock(e)
| RuntimeApiError::CannotCallApi(e) => Some(e),
_ => None,
}
}
}
/// Error working with events.
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum EventsError {
#[error("Can't decode event: can't decode phase: {0}")]
CannotDecodePhase(codec::Error),
#[error("Can't decode event: can't decode pallet index: {0}")]
CannotDecodePalletIndex(codec::Error),
#[error("Can't decode event: can't decode variant index: {0}")]
CannotDecodeVariantIndex(codec::Error),
#[error("Can't decode event: can't find pallet with index {0}")]
CannotFindPalletWithIndex(u8),
#[error(
"Can't decode event: can't find variant with index {variant_index} in pallet {pallet_name}"
)]
CannotFindVariantWithIndex {
pallet_name: String,
variant_index: u8,
},
#[error("Can't decode field {field_name:?} in event {pallet_name}.{event_name}: {reason}")]
CannotDecodeFieldInEvent {
pallet_name: String,
event_name: String,
field_name: String,
reason: scale_decode::visitor::DecodeError,
},
#[error("Can't decode event topics: {0}")]
CannotDecodeEventTopics(codec::Error),
#[error("Can't decode the fields of event {pallet_name}.{event_name}: {reason}")]
CannotDecodeEventFields {
pallet_name: String,
event_name: String,
reason: scale_decode::Error,
},
#[error("Can't decode event {pallet_name}.{event_name} to Event enum: {reason}")]
CannotDecodeEventEnum {
pallet_name: String,
event_name: String,
reason: scale_decode::Error,
},
#[error("Cannot access events at latest block: Cannot fetch latest finalized block: {0}")]
CannotGetLatestFinalizedBlock(BackendError),
#[error("Cannot fetch event bytes: {0}")]
CannotFetchEventBytes(BackendError),
}
impl EventsError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
EventsError::CannotGetLatestFinalizedBlock(e)
| EventsError::CannotFetchEventBytes(e) => Some(e),
_ => None,
}
}
}
/// Error working with extrinsics.
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum ExtrinsicError {
#[error("The extrinsic payload is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find extrinsic: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error("Can't find extrinsic: call name {call_name} doesn't exist in pallet {pallet_name}")]
CallNameNotFound {
pallet_name: String,
call_name: String,
},
#[error("Can't encode the extrinsic call data: {0}")]
CannotEncodeCallData(scale_encode::Error),
#[error("Subxt does not support the extrinsic versions expected by the chain")]
UnsupportedVersion,
#[error("Cannot construct the required transaction extensions: {0}")]
Params(#[from] ExtrinsicParamsError),
#[error("Cannot decode transaction extension '{name}': {error}")]
CouldNotDecodeTransactionExtension {
/// The extension name.
name: String,
/// The decode error.
error: scale_decode::Error,
},
#[error(
"After decoding the extrinsic at index {extrinsic_index}, {num_leftover_bytes} bytes were left, suggesting that decoding may have failed"
)]
LeftoverBytes {
/// Index of the extrinsic that failed to decode.
extrinsic_index: usize,
/// Number of bytes leftover after decoding the extrinsic.
num_leftover_bytes: usize,
},
#[error("{0}")]
ExtrinsicDecodeErrorAt(#[from] ExtrinsicDecodeErrorAt),
#[error("Failed to decode the fields of an extrinsic at index {extrinsic_index}: {error}")]
CannotDecodeFields {
/// Index of the extrinsic whose fields we could not decode
extrinsic_index: usize,
/// The decode error.
error: scale_decode::Error,
},
#[error("Failed to decode the extrinsic at index {extrinsic_index} to a root enum: {error}")]
CannotDecodeIntoRootExtrinsic {
/// Index of the extrinsic that we failed to decode
extrinsic_index: usize,
/// The decode error.
error: scale_decode::Error,
},
#[error("Could not download block body to extract extrinsics from: {0}")]
CannotGetBlockBody(BackendError),
#[error("Block not found: {0}")]
BlockNotFound(Hex),
#[error(
"Extrinsic submission error: Cannot get latest finalized block to grab account nonce at: {0}"
)]
CannotGetLatestFinalizedBlock(BackendError),
#[error("Cannot find block header for block {block_hash}")]
CannotFindBlockHeader { block_hash: Hex },
#[error("Error getting account nonce at block {block_hash}")]
AccountNonceError {
block_hash: Hex,
account_id: Hex,
reason: AccountNonceError,
},
#[error("Cannot submit extrinsic: {0}")]
ErrorSubmittingTransaction(BackendError),
#[error("A transaction status error was returned while submitting the extrinsic: {0}")]
TransactionStatusError(TransactionStatusError),
#[error(
"The transaction status stream encountered an error while submitting the extrinsic: {0}"
)]
TransactionStatusStreamError(BackendError),
#[error(
"The transaction status stream unexpectedly ended, so we don't know the status of the submitted extrinsic"
)]
UnexpectedEndOfTransactionStatusStream,
#[error("Cannot get fee info from Runtime API: {0}")]
CannotGetFeeInfo(BackendError),
#[error("Cannot get validation info from Runtime API: {0}")]
CannotGetValidationInfo(BackendError),
#[error("Cannot decode ValidationResult bytes: {0}")]
CannotDecodeValidationResult(codec::Error),
#[error("ValidationResult bytes could not be decoded")]
UnexpectedValidationResultBytes(Vec<u8>),
}
impl ExtrinsicError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
ExtrinsicError::CannotGetBlockBody(e)
| ExtrinsicError::CannotGetLatestFinalizedBlock(e)
| ExtrinsicError::ErrorSubmittingTransaction(e)
| ExtrinsicError::TransactionStatusStreamError(e)
| ExtrinsicError::CannotGetFeeInfo(e)
| ExtrinsicError::CannotGetValidationInfo(e) => Some(e),
ExtrinsicError::AccountNonceError { reason, .. } => reason.backend_error(),
_ => None,
}
}
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum CustomValueError {
#[error("The static custom value address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("The custom value '{0}' was not found")]
NotFound(String),
#[error("Failed to decode custom value: {0}")]
CouldNotDecodeCustomValue(frame_decode::custom_values::CustomValueDecodeError<u32>),
}
/// Error working with View Functions.
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum ViewFunctionError {
#[error("The static View Function address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find View Function: pallet {0} not found")]
PalletNotFound(String),
#[error("Can't find View Function {function_name} in pallet {pallet_name}")]
ViewFunctionNotFound {
pallet_name: String,
function_name: String,
},
#[error("Failed to encode View Function inputs: {0}")]
CouldNotEncodeInputs(frame_decode::view_functions::ViewFunctionInputsEncodeError),
#[error("Failed to decode View Function: {0}")]
CouldNotDecodeResponse(frame_decode::view_functions::ViewFunctionDecodeError<u32>),
#[error(
"Cannot access View Functions at latest block: Cannot fetch latest finalized block: {0}"
)]
CannotGetLatestFinalizedBlock(BackendError),
#[error("Cannot call the View Function Runtime API: {0}")]
CannotCallApi(BackendError),
}
impl ViewFunctionError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
ViewFunctionError::CannotGetLatestFinalizedBlock(e)
| ViewFunctionError::CannotCallApi(e) => Some(e),
_ => None,
}
}
}
/// Error during the transaction progress.
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum TransactionProgressError {
#[error("Cannot get the next transaction progress update: {0}")]
CannotGetNextProgressUpdate(BackendError),
#[error("Error during transaction progress: {0}")]
TransactionStatusError(#[from] TransactionStatusError),
#[error(
"The transaction status stream unexpectedly ended, so we have no further transaction progress updates"
)]
UnexpectedEndOfTransactionStatusStream,
}
impl TransactionProgressError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
TransactionProgressError::CannotGetNextProgressUpdate(e) => Some(e),
TransactionProgressError::TransactionStatusError(_) => None,
TransactionProgressError::UnexpectedEndOfTransactionStatusStream => None,
}
}
}
/// An error emitted as the result of a transaction progress update.
#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum TransactionStatusError {
/// An error happened on the node that the transaction was submitted to.
#[error("Error handling transaction: {0}")]
Error(String),
/// The transaction was deemed invalid.
#[error("The transaction is not valid: {0}")]
Invalid(String),
/// The transaction was dropped.
#[error("The transaction was dropped: {0}")]
Dropped(String),
}
/// Error fetching events for a just-submitted transaction
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum TransactionEventsError {
#[error(
"The block containing the submitted transaction ({block_hash}) could not be downloaded: {error}"
)]
CannotFetchBlockBody {
block_hash: Hex,
error: BackendError,
},
#[error(
"Cannot find the the submitted transaction (hash: {transaction_hash}) in the block (hash: {block_hash}) it is supposed to be in."
)]
CannotFindTransactionInBlock {
block_hash: Hex,
transaction_hash: Hex,
},
#[error("The block containing the submitted transaction ({block_hash}) could not be found")]
BlockNotFound { block_hash: Hex },
#[error(
"Could not decode event at index {event_index} for the submitted transaction at block {block_hash}: {error}"
)]
CannotDecodeEventInBlock {
event_index: usize,
block_hash: Hex,
error: EventsError,
},
#[error("Could not fetch events for the submitted transaction: {error}")]
CannotFetchEventsForTransaction {
block_hash: Hex,
transaction_hash: Hex,
error: EventsError,
},
#[error("The transaction led to a DispatchError, but we failed to decode it: {error}")]
CannotDecodeDispatchError {
error: DispatchErrorDecodeError,
bytes: Vec<u8>,
},
#[error("The transaction failed with the following dispatch error: {0}")]
ExtrinsicFailed(#[from] DispatchError),
}
impl TransactionEventsError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
TransactionEventsError::CannotFetchBlockBody { error, .. } => Some(error),
TransactionEventsError::CannotDecodeEventInBlock { error, .. }
| TransactionEventsError::CannotFetchEventsForTransaction { error, .. } => {
error.backend_error()
}
_ => None,
}
}
}
/// Error waiting for the transaction to be finalized and successful.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs, clippy::large_enum_variant)]
pub enum TransactionFinalizedSuccessError {
#[error("Could not finalize the transaction: {0}")]
FinalizationError(#[from] TransactionProgressError),
#[error("The transaction did not succeed: {0}")]
SuccessError(#[from] TransactionEventsError),
}
impl TransactionFinalizedSuccessError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
TransactionFinalizedSuccessError::FinalizationError(e) => e.backend_error(),
TransactionFinalizedSuccessError::SuccessError(e) => e.backend_error(),
}
}
}
/// Error decoding the [`DispatchError`]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ModuleErrorDetailsError {
#[error(
"Could not get details for the DispatchError: could not find pallet index {pallet_index}"
)]
PalletNotFound { pallet_index: u8 },
#[error(
"Could not get details for the DispatchError: could not find error index {error_index} in pallet {pallet_name}"
)]
ErrorVariantNotFound {
pallet_name: String,
error_index: u8,
},
}
/// Error decoding the [`ModuleError`]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
#[error("Could not decode the DispatchError::Module payload into the given type: {0}")]
pub struct ModuleErrorDecodeError(scale_decode::Error);
/// Error decoding the [`DispatchError`]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum DispatchErrorDecodeError {
#[error(
"Could not decode the DispatchError: could not find the corresponding type ID in the metadata"
)]
DispatchErrorTypeIdNotFound,
#[error("Could not decode the DispatchError: {0}")]
CouldNotDecodeDispatchError(scale_decode::Error),
#[error("Could not decode the DispatchError::Module variant")]
CouldNotDecodeModuleError {
/// The bytes corresponding to the Module variant we were unable to decode:
bytes: Vec<u8>,
},
}
/// Error working with storage.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageError {
#[error("The static storage address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find storage value: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error(
"Storage entry '{entry_name}' not found in pallet {pallet_name} in the live chain metadata"
)]
StorageEntryNotFound {
pallet_name: String,
entry_name: String,
},
#[error("Cannot obtain storage information from metadata: {0}")]
StorageInfoError(frame_decode::storage::StorageInfoError<'static>),
#[error("Cannot encode storage key: {0}")]
StorageKeyEncodeError(frame_decode::storage::StorageKeyEncodeError),
#[error("Cannot create a key to iterate over a plain entry")]
CannotIterPlainEntry {
pallet_name: String,
entry_name: String,
},
#[error(
"Wrong number of key parts provided to iterate a storage address. We expected at most {max_expected} key parts but got {got} key parts"
)]
WrongNumberOfKeyPartsProvidedForIterating { max_expected: usize, got: usize },
#[error(
"Wrong number of key parts provided to fetch a storage address. We expected {expected} key parts but got {got} key parts"
)]
WrongNumberOfKeyPartsProvidedForFetching { expected: usize, got: usize },
#[error("Cannot access storage at latest block: Cannot fetch latest finalized block: {0}")]
CannotGetLatestFinalizedBlock(BackendError),
#[error(
"No storage value found at the given address, and no default value to fall back to using."
)]
NoValueFound,
#[error("Cannot fetch the storage value: {0}")]
CannotFetchValue(BackendError),
#[error("Cannot iterate storage values: {0}")]
CannotIterateValues(BackendError),
#[error("Encountered an error iterating over storage values: {0}")]
StreamFailure(BackendError),
#[error("Cannot decode the storage version for a given entry: {0}")]
CannotDecodeStorageVersion(codec::Error),
}
impl StorageError {
fn backend_error(&self) -> Option<&BackendError> {
match self {
StorageError::CannotGetLatestFinalizedBlock(e)
| StorageError::CannotFetchValue(e)
| StorageError::CannotIterateValues(e)
| StorageError::StreamFailure(e) => Some(e),
_ => None,
}
}
}
/// Something went wrong working with a constant.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ConstantError {
#[error("The static constant address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find constant: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error(
"Constant '{constant_name}' not found in pallet {pallet_name} in the live chain metadata"
)]
ConstantNameNotFound {
pallet_name: String,
constant_name: String,
},
#[error("Failed to decode constant: {0}")]
CouldNotDecodeConstant(frame_decode::constants::ConstantDecodeError<u32>),
#[error("Cannot obtain constant information from metadata: {0}")]
ConstantInfoError(frame_decode::constants::ConstantInfoError<'static>),
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageKeyError {
#[error("Can't decode the storage key: {error}")]
StorageKeyDecodeError {
bytes: Vec<u8>,
error: frame_decode::storage::StorageKeyDecodeError<u32>,
},
#[error("Can't decode the values from the storage key: {0}")]
CannotDecodeValuesInKey(frame_decode::storage::StorageKeyValueDecodeError),
#[error(
"Cannot decode storage key: there were leftover bytes, indicating that the decoding failed"
)]
LeftoverBytes { bytes: Vec<u8> },
#[error("Can't decode a single value from the storage key part at index {index}: {error}")]
CannotDecodeValueInKey {
index: usize,
error: scale_decode::Error,
},
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageValueError {
#[error("Cannot decode storage value: {0}")]
CannotDecode(frame_decode::storage::StorageValueDecodeError<u32>),
#[error(
"Cannot decode storage value: there were leftover bytes, indicating that the decoding failed"
)]
LeftoverBytes { bytes: Vec<u8> },
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
#[error("Cannot decode extrinsic at index {extrinsic_index}: {error}")]
pub struct ExtrinsicDecodeErrorAt {
pub extrinsic_index: usize,
pub error: ExtrinsicDecodeErrorAtReason,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ExtrinsicDecodeErrorAtReason {
#[error("{0}")]
DecodeError(frame_decode::extrinsics::ExtrinsicDecodeError),
#[error("Leftover bytes")]
LeftoverBytes(Vec<u8>),
}
/// An error that can be emitted when trying to construct an instance of [`crate::config::ExtrinsicParams`],
/// encode data from the instance, or match on signed extensions.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ExtrinsicParamsError {
#[error("Cannot find type id '{type_id} in the metadata (context: {context})")]
MissingTypeId {
/// Type ID.
type_id: u32,
/// Some arbitrary context to help narrow the source of the error.
context: &'static str,
},
#[error("The chain expects a signed extension with the name {0}, but we did not provide one")]
UnknownTransactionExtension(String),
#[error("Error constructing extrinsic parameters: {0}")]
Custom(Box<dyn core::error::Error + Send + Sync + 'static>),
}
impl ExtrinsicParamsError {
/// Create a custom [`ExtrinsicParamsError`] from a string.
pub fn custom<S: Into<String>>(error: S) -> Self {
let error: String = error.into();
let error: Box<dyn core::error::Error + Send + Sync + 'static> = Box::from(error);
ExtrinsicParamsError::Custom(error)
}
}
impl From<core::convert::Infallible> for ExtrinsicParamsError {
fn from(value: core::convert::Infallible) -> Self {
match value {}
}
}
+358
View File
@@ -0,0 +1,358 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! A representation of the dispatch error; an error returned when
//! something fails in trying to submit/execute a transaction.
use super::{DispatchErrorDecodeError, ModuleErrorDecodeError, ModuleErrorDetailsError};
use subxt_metadata::Metadata;
use core::fmt::Debug;
use scale_decode::{DecodeAsType, TypeResolver, visitor::DecodeAsTypeResult};
use std::{borrow::Cow, marker::PhantomData};
/// An error dispatching a transaction.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
pub enum DispatchError {
/// Some error occurred.
#[error("Some unknown error occurred.")]
Other,
/// Failed to lookup some data.
#[error("Failed to lookup some data.")]
CannotLookup,
/// A bad origin.
#[error("Bad origin.")]
BadOrigin,
/// A custom error in a module.
#[error("Pallet error: {0}")]
Module(ModuleError),
/// At least one consumer is remaining so the account cannot be destroyed.
#[error("At least one consumer is remaining so the account cannot be destroyed.")]
ConsumerRemaining,
/// There are no providers so the account cannot be created.
#[error("There are no providers so the account cannot be created.")]
NoProviders,
/// There are too many consumers so the account cannot be created.
#[error("There are too many consumers so the account cannot be created.")]
TooManyConsumers,
/// An error to do with tokens.
#[error("Token error: {0}")]
Token(TokenError),
/// An arithmetic error.
#[error("Arithmetic error: {0}")]
Arithmetic(ArithmeticError),
/// The number of transactional layers has been reached, or we are not in a transactional layer.
#[error("Transactional error: {0}")]
Transactional(TransactionalError),
/// Resources exhausted, e.g. attempt to read/write data which is too large to manipulate.
#[error(
"Resources exhausted, e.g. attempt to read/write data which is too large to manipulate."
)]
Exhausted,
/// The state is corrupt; this is generally not going to fix itself.
#[error("The state is corrupt; this is generally not going to fix itself.")]
Corruption,
/// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later.
#[error(
"Some resource (e.g. a preimage) is unavailable right now. This might fix itself later."
)]
Unavailable,
/// Root origin is not allowed.
#[error("Root origin is not allowed.")]
RootNotAllowed,
}
/// An error relating to tokens when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TokenError {
/// Funds are unavailable.
#[error("Funds are unavailable.")]
FundsUnavailable,
/// Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved.
#[error(
"Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved."
)]
OnlyProvider,
/// Account cannot exist with the funds that would be given.
#[error("Account cannot exist with the funds that would be given.")]
BelowMinimum,
/// Account cannot be created.
#[error("Account cannot be created.")]
CannotCreate,
/// The asset in question is unknown.
#[error("The asset in question is unknown.")]
UnknownAsset,
/// Funds exist but are frozen.
#[error("Funds exist but are frozen.")]
Frozen,
/// Operation is not supported by the asset.
#[error("Operation is not supported by the asset.")]
Unsupported,
/// Account cannot be created for a held balance.
#[error("Account cannot be created for a held balance.")]
CannotCreateHold,
/// Withdrawal would cause unwanted loss of account.
#[error("Withdrawal would cause unwanted loss of account.")]
NotExpendable,
/// Account cannot receive the assets.
#[error("Account cannot receive the assets.")]
Blocked,
}
/// An error relating to arithmetic when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum ArithmeticError {
/// Underflow.
#[error("Underflow.")]
Underflow,
/// Overflow.
#[error("Overflow.")]
Overflow,
/// Division by zero.
#[error("Division by zero.")]
DivisionByZero,
}
/// An error relating to the transactional layers when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransactionalError {
/// Too many transactional layers have been spawned.
#[error("Too many transactional layers have been spawned.")]
LimitReached,
/// A transactional layer was expected, but does not exist.
#[error("A transactional layer was expected, but does not exist.")]
NoLayer,
}
/// Details about a module error that has occurred.
#[derive(Clone, thiserror::Error)]
#[non_exhaustive]
pub struct ModuleError {
metadata: Metadata,
/// Bytes representation:
/// - `bytes[0]`: pallet index
/// - `bytes[1]`: error index
/// - `bytes[2..]`: 3 bytes specific for the module error
bytes: [u8; 5],
}
impl PartialEq for ModuleError {
fn eq(&self, other: &Self) -> bool {
// A module error is the same if the raw underlying details are the same.
self.bytes == other.bytes
}
}
impl Eq for ModuleError {}
/// Custom `Debug` implementation, ignores the very large `metadata` field, using it instead (as
/// intended) to resolve the actual pallet and error names. This is much more useful for debugging.
impl Debug for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let details = self.details_string();
write!(f, "ModuleError(<{details}>)")
}
}
impl std::fmt::Display for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let details = self.details_string();
write!(f, "{details}")
}
}
impl ModuleError {
/// Return more details about this error.
pub fn details(&self) -> Result<ModuleErrorDetails<'_>, ModuleErrorDetailsError> {
let pallet = self
.metadata
.pallet_by_error_index(self.pallet_index())
.ok_or(ModuleErrorDetailsError::PalletNotFound {
pallet_index: self.pallet_index(),
})?;
let variant = pallet
.error_variant_by_index(self.error_index())
.ok_or_else(|| ModuleErrorDetailsError::ErrorVariantNotFound {
pallet_name: pallet.name().into(),
error_index: self.error_index(),
})?;
Ok(ModuleErrorDetails { pallet, variant })
}
/// Return a formatted string of the resolved error details for debugging/display purposes.
pub fn details_string(&self) -> String {
match self.details() {
Ok(details) => format!(
"{pallet_name}::{variant_name}",
pallet_name = details.pallet.name(),
variant_name = details.variant.name,
),
Err(_) => format!(
"Unknown pallet error '{bytes:?}' (pallet and error details cannot be retrieved)",
bytes = self.bytes
),
}
}
/// Return the underlying module error data that was decoded.
pub fn bytes(&self) -> [u8; 5] {
self.bytes
}
/// Obtain the pallet index from the underlying byte data.
pub fn pallet_index(&self) -> u8 {
self.bytes[0]
}
/// Obtain the error index from the underlying byte data.
pub fn error_index(&self) -> u8 {
self.bytes[1]
}
/// Attempts to decode the ModuleError into the top outer Error enum.
pub fn as_root_error<E: DecodeAsType>(&self) -> Result<E, ModuleErrorDecodeError> {
let decoded = E::decode_as_type(
&mut &self.bytes[..],
self.metadata.outer_enums().error_enum_ty(),
self.metadata.types(),
)
.map_err(ModuleErrorDecodeError)?;
Ok(decoded)
}
}
/// Details about the module error.
pub struct ModuleErrorDetails<'a> {
/// The pallet that the error is in
pub pallet: subxt_metadata::PalletMetadata<'a>,
/// The variant representing the error
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
impl DispatchError {
/// Attempt to decode a runtime [`DispatchError`].
#[doc(hidden)]
pub fn decode_from<'a>(
bytes: impl Into<Cow<'a, [u8]>>,
metadata: Metadata,
) -> Result<Self, DispatchErrorDecodeError> {
let bytes = bytes.into();
let dispatch_error_ty_id = metadata
.dispatch_error_ty()
.ok_or(DispatchErrorDecodeError::DispatchErrorTypeIdNotFound)?;
// The aim is to decode our bytes into roughly this shape. This is copied from
// `sp_runtime::DispatchError`; we need the variant names and any inner variant
// names/shapes to line up in order for decoding to be successful.
#[derive(scale_decode::DecodeAsType)]
enum DecodedDispatchError {
Other,
CannotLookup,
BadOrigin,
Module(DecodedModuleErrorBytes),
ConsumerRemaining,
NoProviders,
TooManyConsumers,
Token(TokenError),
Arithmetic(ArithmeticError),
Transactional(TransactionalError),
Exhausted,
Corruption,
Unavailable,
RootNotAllowed,
}
// ModuleError is a bit special; we want to support being decoded from either
// a legacy format of 2 bytes, or a newer format of 5 bytes. So, just grab the bytes
// out when decoding to manually work with them.
struct DecodedModuleErrorBytes(Vec<u8>);
struct DecodedModuleErrorBytesVisitor<R: TypeResolver>(PhantomData<R>);
impl<R: TypeResolver> scale_decode::Visitor for DecodedModuleErrorBytesVisitor<R> {
type Error = scale_decode::Error;
type Value<'scale, 'info> = DecodedModuleErrorBytes;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
_type_id: R::TypeId,
_types: &'info R,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>>
{
DecodeAsTypeResult::Decoded(Ok(DecodedModuleErrorBytes(input.to_vec())))
}
}
impl scale_decode::IntoVisitor for DecodedModuleErrorBytes {
type AnyVisitor<R: TypeResolver> = DecodedModuleErrorBytesVisitor<R>;
fn into_visitor<R: TypeResolver>() -> DecodedModuleErrorBytesVisitor<R> {
DecodedModuleErrorBytesVisitor(PhantomData)
}
}
// Decode into our temporary error:
let decoded_dispatch_err = DecodedDispatchError::decode_as_type(
&mut &*bytes,
dispatch_error_ty_id,
metadata.types(),
)
.map_err(DispatchErrorDecodeError::CouldNotDecodeDispatchError)?;
// Convert into the outward-facing error, mainly by handling the Module variant.
let dispatch_error = match decoded_dispatch_err {
// Mostly we don't change anything from our decoded to our outward-facing error:
DecodedDispatchError::Other => DispatchError::Other,
DecodedDispatchError::CannotLookup => DispatchError::CannotLookup,
DecodedDispatchError::BadOrigin => DispatchError::BadOrigin,
DecodedDispatchError::ConsumerRemaining => DispatchError::ConsumerRemaining,
DecodedDispatchError::NoProviders => DispatchError::NoProviders,
DecodedDispatchError::TooManyConsumers => DispatchError::TooManyConsumers,
DecodedDispatchError::Token(val) => DispatchError::Token(val),
DecodedDispatchError::Arithmetic(val) => DispatchError::Arithmetic(val),
DecodedDispatchError::Transactional(val) => DispatchError::Transactional(val),
DecodedDispatchError::Exhausted => DispatchError::Exhausted,
DecodedDispatchError::Corruption => DispatchError::Corruption,
DecodedDispatchError::Unavailable => DispatchError::Unavailable,
DecodedDispatchError::RootNotAllowed => DispatchError::RootNotAllowed,
// But we apply custom logic to transform the module error into the outward facing version:
DecodedDispatchError::Module(module_bytes) => {
let module_bytes = module_bytes.0;
// The old version is 2 bytes; a pallet and error index.
// The new version is 5 bytes; a pallet and error index and then 3 extra bytes.
let bytes = if module_bytes.len() == 2 {
[module_bytes[0], module_bytes[1], 0, 0, 0]
} else if module_bytes.len() == 5 {
[
module_bytes[0],
module_bytes[1],
module_bytes[2],
module_bytes[3],
module_bytes[4],
]
} else {
tracing::warn!(
"Can't decode error sp_runtime::DispatchError: bytes do not match known shapes"
);
// Return _all_ of the bytes; every "unknown" return should be consistent.
return Err(DispatchErrorDecodeError::CouldNotDecodeModuleError {
bytes: bytes.to_vec(),
});
};
// And return our outward-facing version:
DispatchError::Module(ModuleError { metadata, bytes })
}
};
Ok(dispatch_error)
}
}
+15
View File
@@ -0,0 +1,15 @@
/// Display hex strings.
#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
pub struct Hex(String);
impl<T: AsRef<[u8]>> From<T> for Hex {
fn from(value: T) -> Self {
Hex(hex::encode(value.as_ref()))
}
}
impl std::fmt::Display for Hex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
+346
View File
@@ -0,0 +1,346 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
// TODO: REMOVE BEFORE MERGING.
#![allow(missing_docs)]
//! Subxt is a library for interacting with Substrate based nodes. Using it looks something like this:
//!
//! ```rust,ignore
#![doc = include_str!("../examples/tx_basic.rs")]
//! ```
//!
//! Take a look at [the Subxt guide](book) to learn more about how to use Subxt.
#[cfg(any(
all(feature = "web", feature = "native"),
not(any(feature = "web", feature = "native"))
))]
compile_error!("subxt: exactly one of the 'web' and 'native' features should be used.");
// Suppress an unused dependency warning because these are
// only used in example code snippets at the time of writing.
#[cfg(test)]
mod only_used_in_docs_or_tests {
use subxt_signer as _;
use tokio as _;
use tracing_subscriber as _;
}
// // Internal helper macros
// #[macro_use]
// mod macros;
pub mod config;
pub mod client;
pub mod error;
pub mod utils;
// pub mod book;
// pub mod backend;
// pub mod blocks;
// pub mod constants;
// pub mod custom_values;
// pub mod events;
// pub mod runtime_api;
// pub mod storage;
// pub mod tx;
// pub mod view_functions;
// /// This module provides a [`Config`] type, which is used to define various
// /// types that are important in order to speak to a particular chain.
// /// [`SubstrateConfig`] provides a default set of these types suitable for the
// /// default Substrate node implementation, and [`PolkadotConfig`] for a
// /// Polkadot node.
// pub mod config {
// pub use subxt_core::config::{
// Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, ExtrinsicParams,
// ExtrinsicParamsEncoder, Hash, HashFor, Hasher, Header, PolkadotConfig,
// PolkadotExtrinsicParams, SubstrateConfig, SubstrateExtrinsicParams, TransactionExtension,
// polkadot, substrate, transaction_extensions,
// };
// pub use subxt_core::error::ExtrinsicParamsError;
// }
// /// Types representing the metadata obtained from a node.
// pub mod metadata {
// pub use subxt_metadata::*;
// }
// /// Submit dynamic transactions.
// pub mod dynamic {
// pub use subxt_core::dynamic::*;
// }
// // Expose light client bits
// cfg_unstable_light_client! {
// pub use subxt_lightclient as lightclient;
// }
// // Expose a few of the most common types at root,
// // but leave most types behind their respective modules.
// pub use crate::{
// client::{OfflineClient, OnlineClient},
// config::{Config, PolkadotConfig, SubstrateConfig},
// error::Error,
// metadata::Metadata,
// };
/// Re-export external crates that are made use of in the subxt API.
pub mod ext {
pub use codec;
pub use frame_metadata;
pub use futures;
pub use scale_bits;
pub use scale_decode;
pub use scale_encode;
pub use scale_value;
pub use subxt_rpcs;
#[cfg(feature = "jsonrpsee")]
pub use jsonrpsee;
}
/// Generate a strongly typed API for interacting with a Substrate runtime from its metadata of WASM.
///
/// # Metadata
///
/// First, you'll need to get hold of some metadata for the node you'd like to interact with. One
/// way to do this is by using the `subxt` CLI tool:
///
/// ```bash
/// # Install the CLI tool:
/// cargo install subxt-cli
/// # Use it to download metadata (in this case, from a node running locally)
/// subxt metadata > polkadot_metadata.scale
/// ```
///
/// Run `subxt metadata --help` for more options.
///
/// # Basic usage
///
/// We can generate an interface to a chain given either:
/// - A locally saved SCALE encoded metadata file (see above) for that chain,
/// - The Runtime WASM for that chain, or
/// - A URL pointing at the JSON-RPC interface for a node on that chain.
///
/// In each case, the `subxt` macro will use this data to populate the annotated module with all of the methods
/// and types required for interacting with the chain that the Runtime/metadata was loaded from.
///
/// Let's look at each of these:
///
/// ## Using a locally saved metadata file
///
/// Annotate a Rust module with the `subxt` attribute referencing a metadata file like so:
///
/// ```rust,no_run,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// )]
/// mod polkadot {}
/// ```
///
/// ## Using a WASM runtime via `runtime_path = "..."`
///
/// This requires the `runtime-wasm-path` feature flag.
///
/// Annotate a Rust module with the `subxt` attribute referencing some runtime WASM like so:
///
/// ```rust,ignore
/// #[subxt::subxt(
/// runtime_path = "../artifacts/westend_runtime.wasm",
/// )]
/// mod polkadot {}
/// ```
///
/// ## Connecting to a node to download metadata via `runtime_metadata_insecure_url = "..."`
///
/// This will, at compile time, connect to the JSON-RPC interface for some node at the URL given,
/// download the metadata from it, and use that. This can be useful in CI, but is **not recommended**
/// in production code, because:
///
/// - The compilation time is increased since we have to download metadata from a URL each time. If
/// the node we connect to is unresponsive, this will be slow or could fail.
/// - The metadata may change from what is expected without notice, causing compilation to fail if
/// it leads to changes in the generated interfaces that are being used.
/// - The node that you connect to could be malicious and provide incorrect metadata for the chain.
///
/// ```rust,ignore
/// #[subxt::subxt(
/// runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443"
/// )]
/// mod polkadot {}
/// ```
///
/// # Configuration
///
/// This macro supports a number of attributes to configure what is generated:
///
/// ## `crate = "..."`
///
/// Use this attribute to specify a custom path to the `subxt_core` crate:
///
/// ```rust,standalone_crate
/// # pub extern crate subxt_core;
/// # pub mod path { pub mod to { pub use subxt_core; } }
/// # fn main() {}
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// crate = "crate::path::to::subxt_core"
/// )]
/// mod polkadot {}
/// ```
///
/// This is useful if you write a library which uses this macro, but don't want to force users to depend on `subxt`
/// at the top level too. By default the path `::subxt` is used.
///
/// ## `substitute_type(path = "...", with = "...")`
///
/// This attribute replaces any reference to the generated type at the path given by `path` with a
/// reference to the path given by `with`.
///
/// ```rust,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// substitute_type(path = "sp_arithmetic::per_things::Perbill", with = "crate::Foo")
/// )]
/// mod polkadot {}
///
/// # #[derive(
/// # scale_encode::EncodeAsType,
/// # scale_decode::DecodeAsType,
/// # codec::Encode,
/// # codec::Decode,
/// # Clone,
/// # Debug,
/// # )]
/// // In reality this needs some traits implementing on
/// // it to allow it to be used in place of Perbill:
/// pub struct Foo(u32);
/// # impl codec::CompactAs for Foo {
/// # type As = u32;
/// # fn encode_as(&self) -> &Self::As {
/// # &self.0
/// # }
/// # fn decode_from(x: Self::As) -> Result<Self, codec::Error> {
/// # Ok(Foo(x))
/// # }
/// # }
/// # impl From<codec::Compact<Foo>> for Foo {
/// # fn from(v: codec::Compact<Foo>) -> Foo {
/// # v.0
/// # }
/// # }
/// # fn main() {}
/// ```
///
/// If the type you're substituting contains generic parameters, you can "pattern match" on those, and
/// make use of them in the substituted type, like so:
///
/// ```rust,no_run,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// substitute_type(
/// path = "sp_runtime::multiaddress::MultiAddress<A, B>",
/// with = "::subxt::utils::Static<sp_runtime::MultiAddress<A, B>>"
/// )
/// )]
/// mod polkadot {}
/// ```
///
/// The above is also an example of using the [`crate::utils::Static`] type to wrap some type which doesn't
/// on it's own implement [`scale_encode::EncodeAsType`] or [`scale_decode::DecodeAsType`], which are required traits
/// for any substitute type to implement by default.
///
/// ## `derive_for_all_types = "..."`
///
/// By default, all generated types derive a small set of traits. This attribute allows you to derive additional
/// traits on all generated types:
///
/// ```rust,no_run,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// derive_for_all_types = "Eq, PartialEq"
/// )]
/// mod polkadot {}
/// ```
///
/// Any substituted types (including the default substitutes) must also implement these traits in order to avoid errors
/// here.
///
/// ## `derive_for_type(path = "...", derive = "...")`
///
/// Unlike the above, which derives some trait on every generated type, this attribute allows you to derive traits only
/// for specific types. Note that any types which are used inside the specified type may also need to derive the same traits.
///
/// ```rust,no_run,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// derive_for_all_types = "Eq, PartialEq",
/// derive_for_type(path = "frame_support::PalletId", derive = "Ord, PartialOrd"),
/// derive_for_type(path = "sp_runtime::ModuleError", derive = "Hash"),
/// )]
/// mod polkadot {}
/// ```
///
/// ## `generate_docs`
///
/// By default, documentation is not generated via the macro, since IDEs do not typically make use of it. This attribute
/// forces documentation to be generated, too.
///
/// ```rust,no_run,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// generate_docs
/// )]
/// mod polkadot {}
/// ```
///
/// ## `runtime_types_only`
///
/// By default, the macro will generate various interfaces to make using Subxt simpler in addition with any types that need
/// generating to make this possible. This attribute makes the codegen only generate the types and not the Subxt interface.
///
/// ```rust,no_run,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// runtime_types_only
/// )]
/// mod polkadot {}
/// ```
///
/// ## `no_default_derives`
///
/// By default, the macro will add all derives necessary for the generated code to play nicely with Subxt. Adding this attribute
/// removes all default derives.
///
/// ```rust,no_run,standalone_crate
/// #[subxt::subxt(
/// runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
/// runtime_types_only,
/// no_default_derives,
/// derive_for_all_types="codec::Encode, codec::Decode"
/// )]
/// mod polkadot {}
/// ```
///
/// **Note**: At the moment, you must derive at least one of `codec::Encode` or `codec::Decode` or `scale_encode::EncodeAsType` or
/// `scale_decode::DecodeAsType` (because we add `#[codec(..)]` attributes on some fields/types during codegen), and you must use this
/// feature in conjunction with `runtime_types_only` (or manually specify a bunch of defaults to make codegen work properly when
/// generating the subxt interfaces).
///
/// ## `unstable_metadata`
///
/// This attribute works only in combination with `runtime_metadata_insecure_url`. By default, the macro will fetch the latest stable
/// version of the metadata from the target node. This attribute makes the codegen attempt to fetch the unstable version of
/// the metadata first. This is **not recommended** in production code, since the unstable metadata a node is providing is likely
/// to be incompatible with Subxt.
///
/// ```rust,ignore
/// #[subxt::subxt(
/// runtime_metadata_insecure_url = "wss://rpc.polkadot.io:443",
/// unstable_metadata
/// )]
/// mod polkadot {}
/// ```
pub use subxt_macro::subxt;
+79
View File
@@ -0,0 +1,79 @@
// 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.
//! Miscellaneous utility helpers.
mod account_id;
mod account_id20;
pub mod bits;
mod era;
mod multi_address;
mod multi_signature;
mod static_type;
mod unchecked_extrinsic;
mod wrapper_opaque;
mod yesnomaybe;
mod range_map;
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
pub use range_map::{ RangeMap, RangeMapBuilder, RangeMapError };
pub use account_id::AccountId32;
pub use account_id20::AccountId20;
pub use era::Era;
pub use multi_address::MultiAddress;
pub use multi_signature::MultiSignature;
pub use primitive_types::{H160, H256, H512};
pub use static_type::Static;
pub use unchecked_extrinsic::UncheckedExtrinsic;
pub use wrapper_opaque::WrapperKeepOpaque;
pub use yesnomaybe::{Maybe, No, NoMaybe, Yes, YesMaybe, YesNo};
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Encoded(pub Vec<u8>);
impl codec::Encode for Encoded {
fn encode(&self) -> Vec<u8> {
self.0.to_owned()
}
}
/// Decodes a compact encoded value from the beginning of the provided bytes,
/// returning the value and any remaining bytes.
pub fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> {
let cursor = &mut &*bytes;
let val = <Compact<u64>>::decode(cursor)?;
Ok((val.0, *cursor))
}
/// A version of [`core::marker::PhantomData`] that is also Send and Sync (which is fine
/// because regardless of the generic param, it is always possible to Send + Sync this
/// 0 size type).
#[derive(Encode, Decode, scale_info::TypeInfo)]
#[derive_where(Clone, PartialEq, Debug, Eq, Default, Hash)]
#[scale_info(skip_type_params(T))]
#[doc(hidden)]
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
impl<T> PhantomDataSendSync<T> {
pub fn new() -> Self {
Self(core::marker::PhantomData)
}
}
unsafe impl<T> Send for PhantomDataSendSync<T> {}
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// This represents a key-value collection and is SCALE compatible
/// with collections like BTreeMap. This has the same type params
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
/// A quick helper to encode some bytes to hex.
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
+188
View File
@@ -0,0 +1,188 @@
// 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.
//! The "default" Substrate/Polkadot AccountId. This is used in codegen, as well as signing related bits.
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_core::AccountId32`
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
use codec::{Decode, Encode};
use serde::{Deserialize, Serialize};
use thiserror::Error as DeriveError;
/// A 32-byte cryptographic identifier. This is a simplified version of Substrate's
/// `sp_core::crypto::AccountId32`. To obtain more functionality, convert this into
/// that type.
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
Debug,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
scale_info::TypeInfo,
)]
pub struct AccountId32(pub [u8; 32]);
impl AsRef<[u8]> for AccountId32 {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsRef<[u8; 32]> for AccountId32 {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl From<[u8; 32]> for AccountId32 {
fn from(x: [u8; 32]) -> Self {
AccountId32(x)
}
}
impl AccountId32 {
// Return the ss58-check string for this key. Adapted from `sp_core::crypto`. We need this to
// serialize our account appropriately but otherwise don't care.
fn to_ss58check(&self) -> String {
// For serializing to a string to obtain the account nonce, we use the default substrate
// prefix (since we have no way to otherwise pick one). It doesn't really matter, since when
// it's deserialized back in system_accountNextIndex, we ignore this (so long as it's valid).
const SUBSTRATE_SS58_PREFIX: u8 = 42;
// prefix <= 63 just take up one byte at the start:
let mut v = vec![SUBSTRATE_SS58_PREFIX];
// then push the account ID bytes.
v.extend(self.0);
// then push a 2 byte checksum of what we have so far.
let r = ss58hash(&v);
v.extend(&r[0..2]);
// then encode to base58.
use base58::ToBase58;
v.to_base58()
}
// This isn't strictly needed, but to give our AccountId32 a little more usefulness, we also
// implement the logic needed to decode an AccountId32 from an SS58 encoded string. This is exposed
// via a `FromStr` impl.
fn from_ss58check(s: &str) -> Result<Self, FromSs58Error> {
const CHECKSUM_LEN: usize = 2;
let body_len = 32;
use base58::FromBase58;
let data = s.from_base58().map_err(|_| FromSs58Error::BadBase58)?;
if data.len() < 2 {
return Err(FromSs58Error::BadLength);
}
let prefix_len = match data[0] {
0..=63 => 1,
64..=127 => 2,
_ => return Err(FromSs58Error::InvalidPrefix),
};
if data.len() != prefix_len + body_len + CHECKSUM_LEN {
return Err(FromSs58Error::BadLength);
}
let hash = ss58hash(&data[0..body_len + prefix_len]);
let checksum = &hash[0..CHECKSUM_LEN];
if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum {
// Invalid checksum.
return Err(FromSs58Error::InvalidChecksum);
}
let result = data[prefix_len..body_len + prefix_len]
.try_into()
.map_err(|_| FromSs58Error::BadLength)?;
Ok(AccountId32(result))
}
}
/// An error obtained from trying to interpret an SS58 encoded string into an AccountId32
#[derive(Clone, Copy, Eq, PartialEq, Debug, DeriveError)]
#[allow(missing_docs)]
pub enum FromSs58Error {
#[error("Base 58 requirement is violated")]
BadBase58,
#[error("Length is bad")]
BadLength,
#[error("Invalid checksum")]
InvalidChecksum,
#[error("Invalid SS58 prefix byte.")]
InvalidPrefix,
}
// We do this just to get a checksum to help verify the validity of the address in to_ss58check
fn ss58hash(data: &[u8]) -> Vec<u8> {
use blake2::{Blake2b512, Digest};
const PREFIX: &[u8] = b"SS58PRE";
let mut ctx = Blake2b512::new();
ctx.update(PREFIX);
ctx.update(data);
ctx.finalize().to_vec()
}
impl Serialize for AccountId32 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_ss58check())
}
}
impl<'de> Deserialize<'de> for AccountId32 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
AccountId32::from_ss58check(&String::deserialize(deserializer)?)
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
}
}
impl core::fmt::Display for AccountId32 {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.to_ss58check())
}
}
impl core::str::FromStr for AccountId32 {
type Err = FromSs58Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
AccountId32::from_ss58check(s)
}
}
#[cfg(test)]
mod test {
use super::*;
use sp_core::{self, crypto::Ss58Codec};
use sp_keyring::sr25519::Keyring;
#[test]
fn ss58_is_compatible_with_substrate_impl() {
let keyrings = vec![Keyring::Alice, Keyring::Bob, Keyring::Charlie];
for keyring in keyrings {
let substrate_account = keyring.to_account_id();
let local_account = AccountId32(substrate_account.clone().into());
// Both should encode to ss58 the same way:
let substrate_ss58 = substrate_account.to_ss58check();
assert_eq!(substrate_ss58, local_account.to_ss58check());
// Both should decode from ss58 back to the same:
assert_eq!(
sp_core::crypto::AccountId32::from_ss58check(&substrate_ss58).unwrap(),
substrate_account
);
assert_eq!(
AccountId32::from_ss58check(&substrate_ss58).unwrap(),
local_account
);
}
}
}
+150
View File
@@ -0,0 +1,150 @@
// 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.
//! `AccountId20` is a representation of Ethereum address derived from hashing the public key.
use codec::{Decode, Encode};
use keccak_hash::keccak;
use serde::{Deserialize, Serialize};
use thiserror::Error as DeriveError;
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
Debug,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
scale_info::TypeInfo,
)]
/// Ethereum-compatible `AccountId`.
pub struct AccountId20(pub [u8; 20]);
impl AsRef<[u8]> for AccountId20 {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsRef<[u8; 20]> for AccountId20 {
fn as_ref(&self) -> &[u8; 20] {
&self.0
}
}
impl From<[u8; 20]> for AccountId20 {
fn from(x: [u8; 20]) -> Self {
AccountId20(x)
}
}
impl AccountId20 {
/// Convert to a public key hash
pub fn checksum(&self) -> String {
let hex_address = hex::encode(self.0);
let hash = keccak(hex_address.as_bytes());
let mut checksum_address = String::with_capacity(42);
checksum_address.push_str("0x");
for (i, ch) in hex_address.chars().enumerate() {
// Get the corresponding nibble from the hash
let nibble = (hash[i / 2] >> (if i % 2 == 0 { 4 } else { 0 })) & 0xf;
if nibble >= 8 {
checksum_address.push(ch.to_ascii_uppercase());
} else {
checksum_address.push(ch);
}
}
checksum_address
}
}
/// An error obtained from trying to interpret a hex encoded string into an AccountId20
#[derive(Clone, Copy, Eq, PartialEq, Debug, DeriveError)]
#[allow(missing_docs)]
pub enum FromChecksumError {
#[error("Length is bad")]
BadLength,
#[error("Invalid checksum")]
InvalidChecksum,
#[error("Invalid checksum prefix byte.")]
InvalidPrefix,
}
impl Serialize for AccountId20 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.checksum())
}
}
impl<'de> Deserialize<'de> for AccountId20 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<AccountId20>()
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
}
}
impl core::fmt::Display for AccountId20 {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.checksum())
}
}
impl core::str::FromStr for AccountId20 {
type Err = FromChecksumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 42 {
return Err(FromChecksumError::BadLength);
}
if !s.starts_with("0x") {
return Err(FromChecksumError::InvalidPrefix);
}
hex::decode(&s.as_bytes()[2..])
.map_err(|_| FromChecksumError::InvalidChecksum)?
.try_into()
.map(AccountId20)
.map_err(|_| FromChecksumError::BadLength)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn deserialisation() {
let key_hashes = vec![
"0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac",
"0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0",
"0x798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc",
"0x773539d4Ac0e786233D90A233654ccEE26a613D9",
"0xFf64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB",
"0xC0F0f4ab324C46e55D02D0033343B4Be8A55532d",
];
for key_hash in key_hashes {
let parsed: AccountId20 = key_hash.parse().expect("Failed to parse");
let encoded = parsed.checksum();
// `encoded` should be equal to the initial key_hash
assert_eq!(encoded, key_hash);
}
}
}
+264
View File
@@ -0,0 +1,264 @@
// 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.
//! Generic `scale_bits` over `bitvec`-like `BitOrder` and `BitFormat` types.
use codec::{Compact, Input};
use core::marker::PhantomData;
use scale_bits::{
Bits,
scale::format::{Format, OrderFormat, StoreFormat},
};
use scale_decode::{IntoVisitor, TypeResolver};
/// Associates `bitvec::store::BitStore` trait with corresponding, type-erased `scale_bits::StoreFormat` enum.
///
/// Used to decode bit sequences by providing `scale_bits::StoreFormat` using
/// `bitvec`-like type type parameters.
pub trait BitStore {
/// Corresponding `scale_bits::StoreFormat` value.
const FORMAT: StoreFormat;
/// Number of bits that the backing store types holds.
const BITS: u32;
}
macro_rules! impl_store {
($ty:ident, $wrapped:ty) => {
impl BitStore for $wrapped {
const FORMAT: StoreFormat = StoreFormat::$ty;
const BITS: u32 = <$wrapped>::BITS;
}
};
}
impl_store!(U8, u8);
impl_store!(U16, u16);
impl_store!(U32, u32);
impl_store!(U64, u64);
/// Associates `bitvec::order::BitOrder` trait with corresponding, type-erased `scale_bits::OrderFormat` enum.
///
/// Used to decode bit sequences in runtime by providing `scale_bits::OrderFormat` using
/// `bitvec`-like type type parameters.
pub trait BitOrder {
/// Corresponding `scale_bits::OrderFormat` value.
const FORMAT: OrderFormat;
}
macro_rules! impl_order {
($ty:ident) => {
#[doc = concat!("Type-level value that corresponds to `scale_bits::OrderFormat::", stringify!($ty), "` at run-time")]
#[doc = concat!(" and `bitvec::order::BitOrder::", stringify!($ty), "` at the type level.")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum $ty {}
impl BitOrder for $ty {
const FORMAT: OrderFormat = OrderFormat::$ty;
}
};
}
impl_order!(Lsb0);
impl_order!(Msb0);
/// Constructs a run-time format parameters based on the corresponding type-level parameters.
fn bit_format<Store: BitStore, Order: BitOrder>() -> Format {
Format {
order: Order::FORMAT,
store: Store::FORMAT,
}
}
/// `scale_bits::Bits` generic over the bit store (`u8`/`u16`/`u32`/`u64`) and bit order (LSB, MSB)
/// used for SCALE encoding/decoding. Uses `scale_bits::Bits`-default `u8` and LSB format underneath.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedBits<Store, Order> {
bits: Bits,
_marker: PhantomData<(Store, Order)>,
}
impl<Store, Order> DecodedBits<Store, Order> {
/// Extracts the underlying `scale_bits::Bits` value.
pub fn into_bits(self) -> Bits {
self.bits
}
/// References the underlying `scale_bits::Bits` value.
pub fn as_bits(&self) -> &Bits {
&self.bits
}
}
impl<Store, Order> core::iter::FromIterator<bool> for DecodedBits<Store, Order> {
fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
DecodedBits {
bits: Bits::from_iter(iter),
_marker: PhantomData,
}
}
}
impl<Store: BitStore, Order: BitOrder> codec::Decode for DecodedBits<Store, Order> {
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
/// Equivalent of `BitSlice::MAX_BITS` on 32bit machine.
const ARCH32BIT_BITSLICE_MAX_BITS: u32 = 0x1fff_ffff;
let Compact(bits) = <Compact<u32>>::decode(input)?;
// Otherwise it is impossible to store it on 32bit machine.
if bits > ARCH32BIT_BITSLICE_MAX_BITS {
return Err("Attempt to decode a BitVec with too many bits".into());
}
// NOTE: Replace with `bits.div_ceil(Store::BITS)` if `int_roundings` is stabilised
let elements = (bits / Store::BITS) + u32::from(bits % Store::BITS != 0);
let bytes_in_elem = Store::BITS.saturating_div(u8::BITS);
let bytes_needed = (elements * bytes_in_elem) as usize;
// NOTE: We could reduce allocations if it would be possible to directly
// decode from an `Input` type using a custom format (rather than default <u8, Lsb0>)
// for the `Bits` type.
let mut storage = codec::Encode::encode(&Compact(bits));
let prefix_len = storage.len();
storage.reserve_exact(bytes_needed);
storage.extend(vec![0; bytes_needed]);
input.read(&mut storage[prefix_len..])?;
let decoder = scale_bits::decode_using_format_from(&storage, bit_format::<Store, Order>())?;
let bits = decoder.collect::<Result<Vec<_>, _>>()?;
let bits = Bits::from_iter(bits);
Ok(DecodedBits {
bits,
_marker: PhantomData,
})
}
}
impl<Store: BitStore, Order: BitOrder> codec::Encode for DecodedBits<Store, Order> {
fn size_hint(&self) -> usize {
self.bits.size_hint()
}
fn encoded_size(&self) -> usize {
self.bits.encoded_size()
}
fn encode(&self) -> Vec<u8> {
scale_bits::encode_using_format(self.bits.iter(), bit_format::<Store, Order>())
}
}
#[doc(hidden)]
pub struct DecodedBitsVisitor<S, O, R: TypeResolver>(core::marker::PhantomData<(S, O, R)>);
impl<Store, Order, R: TypeResolver> scale_decode::Visitor for DecodedBitsVisitor<Store, Order, R> {
type Value<'scale, 'info> = DecodedBits<Store, Order>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
type_id: R::TypeId,
types: &'info R,
) -> scale_decode::visitor::DecodeAsTypeResult<
Self,
Result<Self::Value<'scale, 'info>, Self::Error>,
> {
let res =
scale_decode::visitor::decode_with_visitor(input, type_id, types, Bits::into_visitor())
.map(|bits| DecodedBits {
bits,
_marker: PhantomData,
});
scale_decode::visitor::DecodeAsTypeResult::Decoded(res)
}
}
impl<Store, Order> scale_decode::IntoVisitor for DecodedBits<Store, Order> {
type AnyVisitor<R: scale_decode::TypeResolver> = DecodedBitsVisitor<Store, Order, R>;
fn into_visitor<R: TypeResolver>() -> DecodedBitsVisitor<Store, Order, R> {
DecodedBitsVisitor(PhantomData)
}
}
impl<Store, Order> scale_encode::EncodeAsType for DecodedBits<Store, Order> {
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.bits.encode_as_type_to(type_id, types, out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt::Debug;
use bitvec::vec::BitVec;
use codec::Decode as _;
// NOTE: We don't use `bitvec::order` types in our implementation, since we
// don't want to depend on `bitvec`. Rather than reimplementing the unsafe
// trait on our types here for testing purposes, we simply convert and
// delegate to `bitvec`'s own types.
trait ToBitVec {
type Order: bitvec::order::BitOrder;
}
impl ToBitVec for Lsb0 {
type Order = bitvec::order::Lsb0;
}
impl ToBitVec for Msb0 {
type Order = bitvec::order::Msb0;
}
fn scales_like_bitvec_and_roundtrips<
'a,
Store: BitStore + bitvec::store::BitStore + PartialEq,
Order: BitOrder + ToBitVec + Debug + PartialEq,
>(
input: impl IntoIterator<Item = &'a bool>,
) where
BitVec<Store, <Order as ToBitVec>::Order>: codec::Encode + codec::Decode,
{
let input: Vec<_> = input.into_iter().copied().collect();
let decoded_bits = DecodedBits::<Store, Order>::from_iter(input.clone());
let bitvec = BitVec::<Store, <Order as ToBitVec>::Order>::from_iter(input);
let decoded_bits_encoded = codec::Encode::encode(&decoded_bits);
let bitvec_encoded = codec::Encode::encode(&bitvec);
assert_eq!(decoded_bits_encoded, bitvec_encoded);
let decoded_bits_decoded =
DecodedBits::<Store, Order>::decode(&mut &decoded_bits_encoded[..])
.expect("SCALE-encoding DecodedBits to roundtrip");
let bitvec_decoded =
BitVec::<Store, <Order as ToBitVec>::Order>::decode(&mut &bitvec_encoded[..])
.expect("SCALE-encoding BitVec to roundtrip");
assert_eq!(decoded_bits, decoded_bits_decoded);
assert_eq!(bitvec, bitvec_decoded);
}
#[test]
fn decoded_bitvec_scales_and_roundtrips() {
let test_cases = [
vec![],
vec![true],
vec![false],
vec![true, false, true],
vec![true, false, true, false, false, false, false, false, true],
[vec![true; 5], vec![false; 5], vec![true; 1], vec![false; 3]].concat(),
[vec![true; 9], vec![false; 9], vec![true; 9], vec![false; 9]].concat(),
];
for test_case in &test_cases {
scales_like_bitvec_and_roundtrips::<u8, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u16, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u32, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u64, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u8, Msb0>(test_case);
scales_like_bitvec_and_roundtrips::<u16, Msb0>(test_case);
scales_like_bitvec_and_roundtrips::<u32, Msb0>(test_case);
scales_like_bitvec_and_roundtrips::<u64, Msb0>(test_case);
}
}
}
+233
View File
@@ -0,0 +1,233 @@
// 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.
use codec::{Decode, Encode};
use scale_decode::{
IntoVisitor, TypeResolver, Visitor,
ext::scale_type_resolver,
visitor::{TypeIdFor, types::Composite, types::Variant},
};
use scale_encode::EncodeAsType;
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
/// An era to describe the longevity of a transaction.
#[derive(
PartialEq,
Default,
Eq,
Clone,
Copy,
Debug,
serde::Serialize,
serde::Deserialize,
scale_info::TypeInfo,
)]
pub enum Era {
/// The transaction is valid forever. The genesis hash must be present in the signed content.
#[default]
Immortal,
/// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
///
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
/// of `system` module.
Mortal {
/// The number of blocks that the tx will be valid for after the checkpoint block
/// hash found in the signer payload.
period: u64,
/// The phase in the period that this transaction's lifetime begins (and, importantly,
/// implies which block hash is included in the signature material). If the `period` is
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
/// `period` is.
phase: u64,
},
}
// E.g. with period == 4:
// 0 10 20 30 40
// 0123456789012345678901234567890123456789012
// |...|
// authored -/ \- expiry
// phase = 1
// n = Q(current - phase, period) + phase
impl Era {
/// Create a new era based on a period (which should be a power of two between 4 and 65536
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
/// after the start).
///
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
/// prunes old blocks and renders transactions immediately invalid.
pub fn mortal(period: u64, current: u64) -> Self {
let period = period
.checked_next_power_of_two()
.unwrap_or(1 << 16)
.clamp(4, 1 << 16);
let phase = current % period;
let quantize_factor = (period >> 12).max(1);
let quantized_phase = phase / quantize_factor * quantize_factor;
Self::Mortal {
period,
phase: quantized_phase,
}
}
}
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
// it's really the most important bit here.
impl codec::Encode for Era {
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
match self {
Self::Immortal => output.push_byte(0),
Self::Mortal { period, phase } => {
let quantize_factor = (*period >> 12).max(1);
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
| ((phase / quantize_factor) << 4) as u16;
encoded.encode_to(output);
}
}
}
}
impl codec::Decode for Era {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let first = input.read_byte()?;
if first == 0 {
Ok(Self::Immortal)
} else {
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
let period = 2 << (encoded % (1 << 4));
let quantize_factor = (period >> 12).max(1);
let phase = (encoded >> 4) * quantize_factor;
if period >= 4 && phase < period {
Ok(Self::Mortal { period, phase })
} else {
Err("Invalid period and phase".into())
}
}
}
}
/// Define manually how to encode an Era given some type information. Here we
/// basically check that the type we're targeting is called "Era" and then codec::Encode.
impl EncodeAsType for Era {
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
// Visit the type to check that it is an Era. This is only a rough check.
let visitor = scale_type_resolver::visitor::new((), |_, _| false)
.visit_variant(|_, path, _variants| path.last() == Some("Era"));
let is_era = types
.resolve_type(type_id.clone(), visitor)
.unwrap_or_default();
if !is_era {
return Err(scale_encode::Error::custom_string(format!(
"Type {type_id:?} is not a valid Era type; expecting either Immortal or MortalX variant"
)));
}
// if the type looks valid then just scale encode our Era.
self.encode_to(out);
Ok(())
}
}
/// Define manually how to decode an Era given some type information. Here we check that the
/// variant we're decoding is one of the expected Era variants, and that the field is correct if so,
/// ensuring that this will fail if trying to decode something that isn't an Era.
pub struct EraVisitor<R>(core::marker::PhantomData<R>);
impl IntoVisitor for Era {
type AnyVisitor<R: TypeResolver> = EraVisitor<R>;
fn into_visitor<R: TypeResolver>() -> Self::AnyVisitor<R> {
EraVisitor(core::marker::PhantomData)
}
}
impl<R: TypeResolver> Visitor for EraVisitor<R> {
type Value<'scale, 'resolver> = Era;
type Error = scale_decode::Error;
type TypeResolver = R;
// Unwrap any newtype wrappers around the era, eg the CheckMortality extension (which actually
// has 2 fields, but scale_info seems to automatically ignore the PhantomData field). This
// allows us to decode directly from CheckMortality into Era.
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
if value.remaining() != 1 {
return Err(scale_decode::Error::custom_string(format!(
"Expected any wrapper around Era to have exactly one field, but got {} fields",
value.remaining()
)));
}
value
.decode_item(self)
.expect("1 field expected; checked above.")
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let variant = value.name();
// If the variant is immortal, we know the outcome.
if variant == "Immortal" {
return Ok(Era::Immortal);
}
// Otherwise, we expect a variant Mortal1..Mortal255 where the number
// here is the first byte, and the second byte is conceptually a field of this variant.
// This weird encoding is because the Era is compressed to just 1 byte if immortal and
// just 2 bytes if mortal.
//
// Note: We _could_ just assume we'll have 2 bytes to work with and decode the era directly,
// but checking the variant names ensures that the thing we think is an Era actually _is_
// one, based on the type info for it.
let first_byte = variant
.strip_prefix("Mortal")
.and_then(|s| s.parse::<u8>().ok())
.ok_or_else(|| {
scale_decode::Error::custom_string(format!(
"Expected MortalX variant, but got {variant}"
))
})?;
// We need 1 field in the MortalN variant containing the second byte.
let mortal_fields = value.fields();
if mortal_fields.remaining() != 1 {
return Err(scale_decode::Error::custom_string(format!(
"Expected Mortal{} to have one u8 field, but got {} fields",
first_byte,
mortal_fields.remaining()
)));
}
let second_byte = mortal_fields
.decode_item(u8::into_visitor())
.expect("At least one field should exist; checked above.")
.map_err(|e| {
scale_decode::Error::custom_string(format!(
"Expected mortal variant field to be u8, but: {e}"
))
})?;
// Now that we have both bytes we can decode them into the era using
// the same logic as the codec::Decode impl does.
Era::decode(&mut &[first_byte, second_byte][..]).map_err(|e| {
scale_decode::Error::custom_string(format!(
"Failed to codec::Decode Era from Mortal bytes: {e}"
))
})
}
}
+43
View File
@@ -0,0 +1,43 @@
// 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.
//! The "default" Substrate/Polkadot Address type. This is used in codegen, as well as signing related bits.
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiAddress`
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
use codec::{Decode, Encode};
/// A multi-format address wrapper for on-chain accounts. This is a simplified version of Substrate's
/// `sp_runtime::MultiAddress`.
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
Debug,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
scale_info::TypeInfo,
)]
pub enum MultiAddress<AccountId, AccountIndex> {
/// It's an account ID (pubkey).
Id(AccountId),
/// It's an account index.
Index(#[codec(compact)] AccountIndex),
/// It's some arbitrary raw bytes.
Raw(Vec<u8>),
/// It's a 32 byte representation.
Address32([u8; 32]),
/// Its a 20 byte representation.
Address20([u8; 20]),
}
impl<AccountId, AccountIndex> From<AccountId> for MultiAddress<AccountId, AccountIndex> {
fn from(a: AccountId) -> Self {
Self::Id(a)
}
}
+21
View File
@@ -0,0 +1,21 @@
// 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.
//! The "default" Substrate/Polkadot Signature type. This is used in codegen, as well as signing related bits.
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiSignature`
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
use codec::{Decode, Encode};
/// Signature container that can store known signature types. This is a simplified version of
/// `sp_runtime::MultiSignature`. To obtain more functionality, convert this into that type.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, scale_info::TypeInfo)]
pub enum MultiSignature {
/// An Ed25519 signature.
Ed25519([u8; 64]),
/// An Sr25519 signature.
Sr25519([u8; 64]),
/// An ECDSA/SECP256k1 signature (a 512-bit value, plus 8 bits for recovery ID).
Ecdsa([u8; 65]),
}
+159
View File
@@ -0,0 +1,159 @@
use std::fmt::Display;
/// A map that associates ranges of keys with values.
#[derive(Debug, Clone)]
pub struct RangeMap<K, V> {
// (range_start, range_ended, value). This is
// guaranteed to be sorted and have non-overlapping ranges.
mapping: Vec<(K, K, V)>,
}
impl<K: Clone + Copy + Display + PartialOrd + Ord, V> RangeMap<K, V> {
/// Build an empty [`RangeMap`] as a placeholder.
pub fn empty() -> Self {
RangeMap {
mapping: Vec::new(),
}
}
/// Build a [`RangeMap`].
pub fn builder() -> RangeMapBuilder<K, V> {
RangeMapBuilder {
mapping: Vec::new(),
}
}
/// Return the value whose key is within the range, or None if not found.
pub fn get(&self, key: K) -> Option<&V> {
let idx = self
.mapping
.binary_search_by_key(&key, |&(start, end, _)| {
if key >= start && key < end {
key
} else {
start
}
})
.ok()?;
self.mapping.get(idx).map(|(_, _, val)| val)
}
}
/// A builder for constructing a [`RangeMap`]. Use [``RangeMap::builder()`] to create one.
#[derive(Debug, Clone)]
pub struct RangeMapBuilder<K, V> {
mapping: Vec<(K, K, V)>,
}
impl<K: Clone + Copy + Display + PartialOrd + Ord, V> RangeMapBuilder<K, V> {
/// Try to add a range, mapping block numbers to a spec version.
///
/// Returns an error if the range is empty or overlaps with an existing range.
pub fn try_add_range(
&mut self,
start: K,
end: K,
val: V,
) -> Result<&mut Self, RangeMapError<K>> {
let (start, end) = if start < end {
(start, end)
} else {
(end, start)
};
if start == end {
return Err(RangeMapError::EmptyRange(start));
}
if let Some(&(s, e, _)) = self.mapping.iter().find(|&&(s, e, _)| start < e && end > s) {
return Err(RangeMapError::OverlappingRanges {
proposed: (start, end),
existing: (s, e),
});
}
self.mapping.push((start, end, val));
Ok(self)
}
/// Add a range of blocks with the given spec version.
///
/// # Panics
///
/// This method will panic if the range is empty or overlaps with an existing range.
pub fn add_range(mut self, start: K, end: K, val: V) -> Self {
if let Err(e) = self.try_add_range(start, end, val) {
panic!("{e}")
}
self
}
/// Finish adding ranges and build the [`RangeMap`].
pub fn build(mut self) -> RangeMap<K, V> {
self.mapping.sort_by_key(|&(start, _, _)| start);
RangeMap {
mapping: self.mapping,
}
}
}
/// An error that can occur when calling [`RangeMapBuilder::try_add_range()`].
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RangeMapError<K: Display> {
/// An error indicating that the proposed block range is empty.
#[error("Block range cannot be empty: start and end values must be different, but got {} for both", .0)]
EmptyRange(K),
/// An error indicating that the proposed block range overlaps with an existing one.
#[error("Overlapping block ranges are not allowed: proposed range is {}..{}, but we already have {}..{}", proposed.0, proposed.1, existing.0, existing.1)]
OverlappingRanges {
/// The range being proposed / added.
proposed: (K, K),
/// The existing range which overlaps.
existing: (K, K)
},
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_rangemap_get() {
let spec_version = RangeMap::builder()
.add_range(0, 100, 1)
.add_range(100, 200, 2)
.add_range(200, 300, 3)
.build();
assert_eq!(spec_version.get(0), Some(&1));
assert_eq!(spec_version.get(50), Some(&1));
assert_eq!(spec_version.get(100), Some(&2));
assert_eq!(spec_version.get(150), Some(&2));
assert_eq!(spec_version.get(200), Some(&3));
assert_eq!(spec_version.get(250), Some(&3));
assert_eq!(spec_version.get(300), None);
}
#[test]
fn test_rangemap_set() {
let mut spec_version = RangeMap::builder()
.add_range(0, 100, 1)
.add_range(200, 300, 3);
assert_eq!(
spec_version.try_add_range(99, 130, 2).unwrap_err(),
RangeMapError::OverlappingRanges {
proposed: (99, 130),
existing: (0, 100),
}
);
assert_eq!(
spec_version.try_add_range(170, 201, 2).unwrap_err(),
RangeMapError::OverlappingRanges {
proposed: (170, 201),
existing: (200, 300),
}
);
}
}
+79
View File
@@ -0,0 +1,79 @@
// 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.
use codec::{Decode, Encode};
use scale_decode::{IntoVisitor, TypeResolver, Visitor, visitor::DecodeAsTypeResult};
use scale_encode::EncodeAsType;
/// If the type inside this implements [`Encode`], this will implement [`scale_encode::EncodeAsType`].
/// If the type inside this implements [`Decode`], this will implement [`scale_decode::DecodeAsType`].
///
/// In either direction, we ignore any type information and just attempt to encode/decode statically
/// via the [`Encode`] and [`Decode`] implementations. This can be useful as an adapter for types which
/// do not implement [`scale_encode::EncodeAsType`] and [`scale_decode::DecodeAsType`] themselves, but
/// it's best to avoid using it where possible as it will not take into account any type information,
/// and is thus more likely to encode or decode incorrectly.
#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub struct Static<T>(pub T);
impl<T: Encode> EncodeAsType for Static<T> {
fn encode_as_type_to<R: TypeResolver>(
&self,
_type_id: R::TypeId,
_types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.0.encode_to(out);
Ok(())
}
}
pub struct StaticDecodeAsTypeVisitor<T, R>(core::marker::PhantomData<(T, R)>);
impl<T: Decode, R: TypeResolver> Visitor for StaticDecodeAsTypeVisitor<T, R> {
type Value<'scale, 'info> = Static<T>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
_type_id: R::TypeId,
_types: &'info R,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
use scale_decode::{Error, visitor::DecodeError};
let decoded = T::decode(input)
.map(Static)
.map_err(|e| Error::new(DecodeError::CodecError(e).into()));
DecodeAsTypeResult::Decoded(decoded)
}
}
impl<T: Decode> IntoVisitor for Static<T> {
type AnyVisitor<R: TypeResolver> = StaticDecodeAsTypeVisitor<T, R>;
fn into_visitor<R: TypeResolver>() -> StaticDecodeAsTypeVisitor<T, R> {
StaticDecodeAsTypeVisitor(core::marker::PhantomData)
}
}
// Make it easy to convert types into Static where required.
impl<T> From<T> for Static<T> {
fn from(value: T) -> Self {
Static(value)
}
}
// Static<T> is just a marker type and should be as transparent as possible:
impl<T> core::ops::Deref for Static<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for Static<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
+137
View File
@@ -0,0 +1,137 @@
// 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.
//! The "default" Substrate/Polkadot UncheckedExtrinsic.
//! This is used in codegen for runtime API calls.
//!
//! The inner bytes represent the encoded extrinsic expected by the
//! runtime APIs. Deriving `EncodeAsType` would lead to the inner
//! bytes to be re-encoded (length prefixed).
use core::marker::PhantomData;
use codec::{Decode, Encode};
use scale_decode::{DecodeAsType, IntoVisitor, TypeResolver, Visitor, visitor::DecodeAsTypeResult};
use super::{Encoded, Static};
/// The unchecked extrinsic from substrate.
#[derive(Clone, Debug, Eq, PartialEq, Encode)]
pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>(
Static<Encoded>,
#[codec(skip)] PhantomData<(Address, Call, Signature, Extra)>,
);
impl<Address, Call, Signature, Extra> UncheckedExtrinsic<Address, Call, Signature, Extra> {
/// Construct a new [`UncheckedExtrinsic`].
pub fn new(bytes: Vec<u8>) -> Self {
Self(Static(Encoded(bytes)), PhantomData)
}
/// Get the bytes of the encoded extrinsic.
pub fn bytes(&self) -> &[u8] {
self.0.0.0.as_slice()
}
}
impl<Address, Call, Signature, Extra> Decode
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
// The bytes for an UncheckedExtrinsic are first a compact
// encoded length, and then the bytes following. This is the
// same encoding as a Vec, so easiest ATM is just to decode
// into that, and then encode the vec bytes to get our extrinsic
// bytes, which we save into an `Encoded` to preserve as-is.
let xt_vec: Vec<u8> = Decode::decode(input)?;
Ok(UncheckedExtrinsic::new(xt_vec))
}
}
impl<Address, Call, Signature, Extra> scale_encode::EncodeAsType
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.0.encode_as_type_to(type_id, types, out)
}
}
impl<Address, Call, Signature, Extra> From<Vec<u8>>
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
fn from(bytes: Vec<u8>) -> Self {
UncheckedExtrinsic::new(bytes)
}
}
impl<Address, Call, Signature, Extra> From<UncheckedExtrinsic<Address, Call, Signature, Extra>>
for Vec<u8>
{
fn from(bytes: UncheckedExtrinsic<Address, Call, Signature, Extra>) -> Self {
bytes.0.0.0
}
}
pub struct UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R: TypeResolver>(
PhantomData<(Address, Call, Signature, Extra, R)>,
);
impl<Address, Call, Signature, Extra, R: TypeResolver> Visitor
for UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R>
{
type Value<'scale, 'info> = UncheckedExtrinsic<Address, Call, Signature, Extra>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
type_id: R::TypeId,
types: &'info R,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
DecodeAsTypeResult::Decoded(Self::Value::decode_as_type(input, type_id, types))
}
}
impl<Address, Call, Signature, Extra> IntoVisitor
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
type AnyVisitor<R: TypeResolver> =
UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R>;
fn into_visitor<R: TypeResolver>()
-> UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R> {
UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn unchecked_extrinsic_encoding() {
// A tx is basically some bytes with a compact length prefix; ie an encoded vec:
let tx_bytes = vec![1u8, 2, 3].encode();
let unchecked_extrinsic = UncheckedExtrinsic::<(), (), (), ()>::new(tx_bytes.clone());
let encoded_tx_bytes = unchecked_extrinsic.encode();
// The encoded representation must not alter the provided bytes.
assert_eq!(tx_bytes, encoded_tx_bytes);
// However, for decoding we expect to be able to read the extrinsic from the wire
// which would be length prefixed.
let decoded_tx = UncheckedExtrinsic::<(), (), (), ()>::decode(&mut &tx_bytes[..]).unwrap();
let decoded_tx_bytes = decoded_tx.bytes();
let encoded_tx_bytes = decoded_tx.encode();
assert_eq!(decoded_tx_bytes, encoded_tx_bytes);
// Ensure we can decode the tx and fetch only the tx bytes.
assert_eq!(vec![1, 2, 3], encoded_tx_bytes);
}
}
+237
View File
@@ -0,0 +1,237 @@
// 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.
use super::PhantomDataSendSync;
use codec::{Compact, Decode, DecodeAll, Encode};
use derive_where::derive_where;
use scale_decode::{IntoVisitor, TypeResolver, Visitor, ext::scale_type_resolver::visitor};
use scale_encode::EncodeAsType;
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
/// access the real type `T` [`Self::try_decode`] needs to be used.
// Dev notes:
//
// - This is adapted from [here](https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs).
// - The encoded bytes will be a compact encoded length followed by that number of bytes.
// - However, the TypeInfo describes the type as a composite with first a compact encoded length and next the type itself.
// [`Encode`] and [`Decode`] impls will "just work" to take this into a `Vec<u8>`, but we need a custom [`EncodeAsType`]
// and [`Visitor`] implementation to encode and decode based on TypeInfo.
#[derive(Encode, Decode)]
#[derive_where(Debug, Clone, PartialEq, Eq, Default, Hash)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T> WrapperKeepOpaque<T> {
/// Try to decode the wrapped type from the inner `data`.
///
/// Returns `None` if the decoding failed.
pub fn try_decode(&self) -> Option<T>
where
T: Decode,
{
T::decode_all(&mut &self.data[..]).ok()
}
/// Returns the length of the encoded `T`.
pub fn encoded_len(&self) -> usize {
self.data.len()
}
/// Returns the encoded data.
pub fn encoded(&self) -> &[u8] {
&self.data
}
/// Create from the given encoded `data`.
pub fn from_encoded(data: Vec<u8>) -> Self {
Self {
data,
_phantom: PhantomDataSendSync::new(),
}
}
/// Create from some raw value by encoding it.
pub fn from_value(value: T) -> Self
where
T: Encode,
{
Self {
data: value.encode(),
_phantom: PhantomDataSendSync::new(),
}
}
}
impl<T> EncodeAsType for WrapperKeepOpaque<T> {
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
use scale_encode::error::{Error, ErrorKind, Kind};
let ctx = (type_id.clone(), out);
let visitor = visitor::new(ctx, |(type_id, _out), _| {
// Check that the target shape lines up: any other shape but composite is wrong.
Err(Error::new(ErrorKind::WrongShape {
actual: Kind::Struct,
expected_id: format!("{type_id:?}"),
}))
})
.visit_composite(|(_type_id, out), _path, _fields| {
self.data.encode_to(out);
Ok(())
});
types
.resolve_type(type_id.clone(), visitor)
.map_err(|_| Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))?
}
}
pub struct WrapperKeepOpaqueVisitor<T, R>(core::marker::PhantomData<(T, R)>);
impl<T, R: TypeResolver> Visitor for WrapperKeepOpaqueVisitor<T, R> {
type Value<'scale, 'info> = WrapperKeepOpaque<T>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn visit_composite<'scale, 'info>(
self,
value: &mut scale_decode::visitor::types::Composite<'scale, 'info, R>,
_type_id: R::TypeId,
) -> Result<Self::Value<'scale, 'info>, Self::Error> {
use scale_decode::error::{Error, ErrorKind};
use scale_decode::visitor::DecodeError;
if value.name() != Some("WrapperKeepOpaque") {
return Err(Error::new(ErrorKind::VisitorDecodeError(
DecodeError::TypeResolvingError(format!(
"Expected a type named 'WrapperKeepOpaque', got: {:?}",
value.name()
)),
)));
}
if value.remaining() != 2 {
return Err(Error::new(ErrorKind::WrongLength {
actual_len: value.remaining(),
expected_len: 2,
}));
}
// The field to decode is a compact len followed by bytes. Decode the length, then grab the bytes.
let Compact(len) = value
.decode_item(Compact::<u32>::into_visitor())
.expect("length checked")?;
let field = value.next().expect("length checked")?;
// Sanity check that the compact length we decoded lines up with the number of bytes encoded in the next field.
if field.bytes().len() != len as usize {
return Err(Error::custom_str(
"WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len",
));
}
Ok(WrapperKeepOpaque {
data: field.bytes().to_vec(),
_phantom: PhantomDataSendSync::new(),
})
}
}
impl<T> IntoVisitor for WrapperKeepOpaque<T> {
type AnyVisitor<R: TypeResolver> = WrapperKeepOpaqueVisitor<T, R>;
fn into_visitor<R: TypeResolver>() -> WrapperKeepOpaqueVisitor<T, R> {
WrapperKeepOpaqueVisitor(core::marker::PhantomData)
}
}
#[cfg(test)]
mod test {
use scale_decode::DecodeAsType;
use super::*;
// Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs
// and used for tests to check that we can work with the expected TypeInfo without needing to import
// the frame_support crate, which has quite a lot of dependencies.
impl<T: scale_info::TypeInfo + 'static> scale_info::TypeInfo for WrapperKeepOpaque<T> {
type Identity = Self;
fn type_info() -> scale_info::Type {
use scale_info::{Path, Type, TypeParameter, build::Fields, meta_type};
Type::builder()
.path(Path::new("WrapperKeepOpaque", module_path!()))
.type_params(vec![TypeParameter::new("T", Some(meta_type::<T>()))])
.composite(
Fields::unnamed()
.field(|f| f.compact::<u32>())
.field(|f| f.ty::<T>().type_name("T")),
)
}
}
/// Given a type definition, return type ID and registry representing it.
fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
let m = scale_info::MetaType::new::<T>();
let mut types = scale_info::Registry::new();
let id = types.register_type(&m);
let portable_registry: scale_info::PortableRegistry = types.into();
(id.id, portable_registry)
}
fn roundtrips_like_scale_codec<T>(t: T)
where
T: EncodeAsType
+ DecodeAsType
+ Encode
+ Decode
+ PartialEq
+ core::fmt::Debug
+ scale_info::TypeInfo
+ 'static,
{
let (type_id, types) = make_type::<T>();
let scale_codec_encoded = t.encode();
let encode_as_type_encoded = t.encode_as_type(type_id, &types).unwrap();
assert_eq!(
scale_codec_encoded, encode_as_type_encoded,
"encoded bytes should match"
);
let decode_as_type_bytes = &mut &*scale_codec_encoded;
let decoded_as_type = T::decode_as_type(decode_as_type_bytes, type_id, &types)
.expect("decode-as-type decodes");
let decode_scale_codec_bytes = &mut &*scale_codec_encoded;
let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes");
assert!(
decode_as_type_bytes.is_empty(),
"no bytes should remain in decode-as-type impl"
);
assert!(
decode_scale_codec_bytes.is_empty(),
"no bytes should remain in codec-decode impl"
);
assert_eq!(
decoded_as_type, decoded_scale_codec,
"decoded values should match"
);
}
#[test]
fn wrapper_keep_opaque_roundtrips_ok() {
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64));
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true));
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4]));
}
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
/// A unit marker enum.
pub enum Yes {}
/// A unit marker enum.
pub enum Maybe {}
/// A unit marker enum.
pub enum No {}
/// This is implemented for [`Yes`] and [`No`] and
/// allows us to check at runtime which of these types is present.
pub trait YesNo {
/// [`Yes`]
fn is_yes() -> bool {
false
}
/// [`No`]
fn is_no() -> bool {
false
}
}
impl YesNo for Yes {
fn is_yes() -> bool {
true
}
}
impl YesNo for No {
fn is_no() -> bool {
true
}
}
/// This is implemented for [`Yes`] and [`Maybe`] and
/// allows us to check at runtime which of these types is present.
pub trait YesMaybe {
/// [`Yes`]
fn is_yes() -> bool {
false
}
/// [`Maybe`]
fn is_maybe() -> bool {
false
}
}
impl YesMaybe for Yes {
fn is_yes() -> bool {
true
}
}
impl YesMaybe for Maybe {
fn is_maybe() -> bool {
true
}
}
/// This is implemented for [`No`] and [`Maybe`] and
/// allows us to check at runtime which of these types is present.
pub trait NoMaybe {
/// [`No`]
fn is_no() -> bool {
false
}
/// [`Maybe`]
fn is_maybe() -> bool {
false
}
}
impl NoMaybe for No {
fn is_no() -> bool {
true
}
}
impl NoMaybe for Maybe {
fn is_maybe() -> bool {
true
}
}