subxt-core crate (#1466)

* start migrating, broken

* first iteration of updating

* fmt and clippy

* add Composite<u32> decoding via scale value patch

* bump scale type gen versions

* fix decoding with new scale decode

* compiling with changed deps

* core utils, condig, client, metadata

* core crate compiling

* signer crate no once lock

* add core to no-std-tests, change imports

* broken commit, start pulling everything together in subxt

* port more things to subxt

* events in core crate, extrinsics sadly much more difficult

* almost all examples pass again

* dynamic values fix in examples

* fix no std issue and fmt

* remove unused dependencies

* fix lightclient impl

* runtime version refactor

* formatting and addressing nits

* more comments addressed

* update wasm example and no-std-signer tests

* other nits and error impl on signer errors

* fix feature flag

* fix runtime version refactor

* fix doc links

* fix integration tests

* fix feature flag gated client state

* fix native feature in CI

* fix lightclient utils

* make imports more lean in subxt-core

* integrate changes from subxt-core imports into subxt

* other changes in subxt simplify imports more

* fix  and docs

* doc false for cli

* fix clippy

* remove events block hash in tests

* codegen no-std support in generated code

* export alloc crate for no-std codegen

* fix doc test

* implement James comments

* remove std traits, use core traits instead

* address nits

* remove unusued dep in no-std tests

* fix Box import in no_std

* sp-crypto-hashing instead of sp-core-hashing

* bump scale-typegen, add no std codegen tests

* fix some things

* replace unmaintained derivative with derive_where to remove non-canonical warnings

* fmt

* remove unused dep

* fix deps

* update artifacts to fix type ID mismatches

* bump to latest scale-typegen

---------

Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
Tadeo Hepperle
2024-03-27 09:55:08 +01:00
committed by GitHub
parent 92c1ba7f66
commit a0cb14aa4f
106 changed files with 24329 additions and 26882 deletions
+19
View File
@@ -0,0 +1,19 @@
use scale_decode::DecodeAsFields;
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
///
/// Generated API structures that represent an extrinsic implement this trait.
///
/// The trait is utilized to decode emitted extrinsics from a block, via obtaining the
/// form of the `Extrinsic` from the metadata.
pub trait StaticExtrinsic: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Call name.
const CALL: &'static str;
/// Returns true if the given pallet and call names match this extrinsic.
fn is_extrinsic(pallet: &str, call: &str) -> bool {
Self::PALLET == pallet && Self::CALL == call
}
}
+71
View File
@@ -0,0 +1,71 @@
use crate::{config::Config, metadata::Metadata};
use derive_where::derive_where;
/// Each client should be able to provide access to the following fields
/// - runtime version
/// - genesis hash
/// - metadata
#[derive_where(Clone, Debug)]
pub struct ClientState<C: Config> {
genesis_hash: C::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
}
impl<C: Config> ClientState<C> {
pub fn new(genesis_hash: C::Hash, runtime_version: RuntimeVersion, metadata: Metadata) -> Self {
Self {
genesis_hash,
runtime_version,
metadata,
}
}
pub fn metadata(&self) -> Metadata {
self.metadata.clone()
}
pub fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version
}
pub fn genesis_hash(&self) -> C::Hash {
self.genesis_hash
}
}
/// Runtime version information needed to submit transactions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RuntimeVersion {
spec_version: u32,
transaction_version: u32,
}
impl RuntimeVersion {
pub fn new(spec_version: u32, transaction_version: u32) -> Self {
RuntimeVersion {
spec_version,
transaction_version,
}
}
/// 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 fn spec_version(&self) -> u32 {
self.spec_version
}
/// 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 fn transaction_version(&self) -> u32 {
self.transaction_version
}
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::signed_extensions::CheckNonceParams;
use super::{signed_extensions, ExtrinsicParams};
use super::{Config, Header};
/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions
/// and how to apply them to a given chain.
pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
T,
(
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<T>,
signed_extensions::CheckMortality<T>,
signed_extensions::ChargeAssetTxPayment<T>,
signed_extensions::ChargeTransactionPayment,
),
>;
/// 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.
mortality: Option<Mortality<T::Hash>>,
/// `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,
}
struct Mortality<Hash> {
/// Block hash that mortality starts from
checkpoint_hash: Hash,
/// Block number that mortality starts from (must
// point to the same block as the hash above)
checkpoint_number: u64,
/// How many blocks the tx is mortal for
period: u64,
}
impl<T: Config> Default for DefaultExtrinsicParamsBuilder<T> {
fn default() -> Self {
Self {
mortality: None,
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 mortal, given a block header that it should be mortal from,
/// and the number of blocks (roughly; it'll be rounded to a power of two) that it will
/// be mortal for.
pub fn mortal(mut self, from_block: &T::Header, for_n_blocks: u64) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block.hash(),
checkpoint_number: from_block.number().into(),
period: for_n_blocks,
});
self
}
/// Provide a specific nonce for the submitter of the extrinsic
pub fn nonce(mut self, nonce: u64) -> Self {
self.nonce = Some(nonce);
self
}
/// Make the transaction mortal, given a block number and block hash (which must both point to
/// the same block) that it should be mortal from, and the number of blocks (roughly; it'll be
/// rounded to a power of two) that it will be mortal for.
///
/// Prefer to use [`DefaultExtrinsicParamsBuilder::mortal()`], which ensures that the block hash
/// and number align.
pub fn mortal_unchecked(
mut self,
from_block_number: u64,
from_block_hash: T::Hash,
for_n_blocks: u64,
) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block_hash,
checkpoint_number: from_block_number,
period: for_n_blocks,
});
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 = if let Some(mortality) = self.mortality {
signed_extensions::CheckMortalityParams::mortal(
mortality.period,
mortality.checkpoint_number,
mortality.checkpoint_hash,
)
} else {
signed_extensions::CheckMortalityParams::immortal()
};
let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id {
signed_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
} else {
signed_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
};
let charge_transaction_params =
signed_extensions::ChargeTransactionPaymentParams::tip(self.tip);
let check_nonce_params = CheckNonceParams(self.nonce);
(
(),
(),
check_nonce_params,
(),
check_mortality_params,
charge_asset_tx_params,
charge_transaction_params,
)
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2019-2023 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 super::refine_params::RefineParams;
use crate::{client::ClientState, error::ExtrinsicParamsError, Config};
use alloc::vec::Vec;
/// 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 + '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: RefineParams<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 "signed extra" parameters
/// to some buffer that has been provided. These are the parameters
/// which are sent along with the transaction, as well as taken into
/// account when signing the transaction.
fn encode_extra_to(&self, _v: &mut Vec<u8>) {}
/// This is expected to SCALE encode the "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_additional_to(&self, _v: &mut Vec<u8>) {}
}
+156
View File
@@ -0,0 +1,156 @@
// 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;
mod refine_params;
pub mod polkadot;
pub mod signed_extensions;
pub mod substrate;
use crate::macros::cfg_substrate_compat;
use codec::{Decode, Encode};
use core::fmt::Debug;
use scale_decode::DecodeAsType;
use scale_encode::EncodeAsType;
use serde::{de::DeserializeOwned, Serialize};
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
pub use refine_params::{RefineParams, RefineParamsData};
pub use signed_extensions::SignedExtension;
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
/// Runtime types.
// Note: the `Send + Sync + 'static` bound isn't strictly required, but currently deriving
// TypeInfo automatically applies a 'static bound to all generic types (including this one),
// And we want the compiler to infer `Send` and `Sync` OK for things which have `T: Config`
// rather than having to `unsafe impl` them ourselves.
pub trait Config: Sized + Send + Sync + 'static {
/// The output of the `Hasher` function.
type Hash: BlockHash;
/// The account ID type.
type AccountId: Debug + Clone + Encode;
/// The address type.
type Address: Debug + Encode + From<Self::AccountId>;
/// The signature type.
type Signature: Debug + Encode;
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
type Hasher: Debug + Hasher<Output = Self::Hash>;
/// The block header.
type Header: Debug + Header<Hasher = Self::Hasher> + Sync + Send + DeserializeOwned;
/// 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;
}
/// 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 BlockHash:
Debug
+ Copy
+ Send
+ Sync
+ Decode
+ AsRef<[u8]>
+ Serialize
+ DeserializeOwned
+ Encode
+ PartialEq
+ Eq
+ core::hash::Hash
{
}
impl<T> BlockHash 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 {
/// The type given back from the hash operation
type Output;
/// Hash some bytes to the given output type.
fn hash(s: &[u8]) -> Self::Output;
/// Hash some SCALE encodable type to the given output type.
fn hash_of<S: Encode>(s: &S) -> Self::Output {
let out = s.encode();
Self::hash(&out)
}
}
/// This represents the block header type used by a node.
pub trait Header: Sized + Encode + Decode {
/// The block number type for this header.
type Number: Into<u64>;
/// The hasher used to hash this header.
type Hasher: Hasher;
/// Return the block number of this header.
fn number(&self) -> Self::Number;
/// Hash this header.
fn hash(&self) -> <Self::Hasher as Hasher>::Output {
Self::Hasher::hash_of(self)
}
}
cfg_substrate_compat! {
/// implement subxt's Hasher and Header traits for some substrate structs
mod substrate_impls {
use super::*;
impl<T: sp_runtime::traits::Header> Header for T
where
<T as sp_runtime::traits::Header>::Number: Into<u64>,
{
type Number = T::Number;
type Hasher = T::Hashing;
fn number(&self) -> Self::Number {
*self.number()
}
}
impl<T: sp_runtime::traits::Hash> Hasher for T {
type Output = T::Output;
fn hash(s: &[u8]) -> Self::Output {
<T as sp_runtime::traits::Hash>::hash(s)
}
}
}
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2019-2023 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::SubstrateConfig;
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
pub use primitive_types::{H256, U256};
/// Default set of commonly used types by Polkadot nodes.
pub enum PolkadotConfig {}
impl Config for PolkadotConfig {
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = MultiAddress<Self::AccountId, ()>;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
type AssetId = u32;
}
/// 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>;
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Refining params with values fetched from the chain
use crate::Config;
/// Data that can be used to refine the params of signed extensions.
pub struct RefineParamsData<T: Config> {
account_nonce: u64,
block_number: u64,
block_hash: T::Hash,
}
impl<T: Config> RefineParamsData<T> {
#[doc(hidden)]
/// Creates a new [`RefineParamsData`] instance. Called from `subxt` when refining signed extensions.
pub fn new(account_nonce: u64, block_number: u64, block_hash: T::Hash) -> Self {
RefineParamsData {
account_nonce,
block_number,
block_hash,
}
}
/// account nonce for extrinsic author
pub fn account_nonce(&self) -> u64 {
self.account_nonce
}
/// latest finalized block number
pub fn block_number(&self) -> u64 {
self.block_number
}
/// latest finalized block hash
pub fn block_hash(&self) -> T::Hash {
self.block_hash
}
}
/// Types implementing [`RefineParams`] can be modified to reflect live information from the chain.
pub trait RefineParams<T: Config> {
/// Refine params to an extrinsic. There is usually some notion of 'the param is already set/unset' in types implementing this trait.
/// The refinement should most likely not affect cases where a param is in a 'is already set by the user' state.
fn refine(&mut self, _data: &RefineParamsData<T>) {}
}
impl<T: Config> RefineParams<T> for () {}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
impl <T: Config, $($ident : RefineParams<T>),+> RefineParams<T> for ($($ident,)+){
fn refine(&mut self, data: &RefineParamsData<T>) {
$(self.$index.refine(data);)+
}
}
}
}
#[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);
};
+511
View File
@@ -0,0 +1,511 @@
// Copyright 2019-2023 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 signed extensions, each
//! of which implements [`SignedExtension`], and can be used in conjunction with
//! [`AnyOf`] to configure the set of signed extensions which are known about
//! when interacting with a chain.
use super::extrinsic_params::ExtrinsicParams;
use super::refine_params::RefineParamsData;
use super::RefineParams;
use crate::client::ClientState;
use crate::config::ExtrinsicParamsEncoder;
use crate::error::ExtrinsicParamsError;
use crate::utils::Era;
use crate::Config;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::vec::Vec;
use codec::{Compact, Encode};
use core::fmt::Debug;
use derive_where::derive_where;
use hashbrown::HashMap;
use scale_decode::DecodeAsType;
use scale_info::PortableRegistry;
/// A single [`SignedExtension`] has a unique name, but is otherwise the
/// same as [`ExtrinsicParams`] in describing how to encode the extra and
/// additional data.
pub trait SignedExtension<T: Config>: ExtrinsicParams<T> {
/// The type representing the `extra` bytes of a signed extension.
/// Decoding from this type should be symmetrical to the respective
/// `ExtrinsicParamsEncoder::encode_extra_to()` implementation of this signed extension.
type Decoded: DecodeAsType;
/// This should return true if the signed 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 [`CheckSpecVersion`] signed 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_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckSpecVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckSpecVersion"
}
}
/// The [`CheckNonce`] signed extension.
pub struct CheckNonce(Compact<u64>);
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
type Params = CheckNonceParams;
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
// If no nonce is set (nor by user nor refinement), use a nonce of 0.
let nonce = params.0.unwrap_or(0);
Ok(CheckNonce(Compact(nonce)))
}
}
impl ExtrinsicParamsEncoder for CheckNonce {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckNonce {
type Decoded = u64;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckNonce"
}
}
/// Params for [`CheckNonce`]
#[derive(Debug, Clone, Default)]
pub struct CheckNonceParams(pub Option<u64>);
impl<T: Config> RefineParams<T> for CheckNonceParams {
fn refine(&mut self, data: &RefineParamsData<T>) {
if self.0.is_none() {
self.0 = Some(data.account_nonce());
}
}
}
/// The [`CheckTxVersion`] signed 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_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckTxVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckTxVersion"
}
}
/// The [`CheckGenesis`] signed extension.
pub struct CheckGenesis<T: Config>(T::Hash);
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_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckGenesis<T> {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckGenesis"
}
}
/// The [`CheckMortality`] signed extension.
pub struct CheckMortality<T: Config> {
era: Era,
checkpoint: T::Hash,
}
/// Parameters to configure the [`CheckMortality`] signed extension.
pub struct CheckMortalityParams<T: Config>(Option<CheckMortalityParamsInner<T>>);
struct CheckMortalityParamsInner<T: Config> {
era: Era,
checkpoint: Option<T::Hash>,
}
impl<T: Config> Default for CheckMortalityParams<T> {
fn default() -> Self {
CheckMortalityParams(None)
}
}
impl<T: Config> RefineParams<T> for CheckMortalityParams<T> {
fn refine(&mut self, data: &RefineParamsData<T>) {
if self.0.is_none() {
// By default we refine the params to have a mortal transaction valid for 32 blocks.
const TX_VALID_FOR: u64 = 32;
*self =
CheckMortalityParams::mortal(TX_VALID_FOR, data.block_number(), data.block_hash());
}
}
}
impl<T: Config> CheckMortalityParams<T> {
/// Configure a mortal transaction. The `period` is (roughly) how many
/// blocks the transaction will be valid for. The `block_number` and
/// `block_hash` should both point to the same block, and are the block that
/// the transaction is mortal from.
pub fn mortal(period: u64, block_number: u64, block_hash: T::Hash) -> Self {
Self(Some(CheckMortalityParamsInner {
era: Era::mortal(period, block_number),
checkpoint: Some(block_hash),
}))
}
/// An immortal transaction.
pub fn immortal() -> Self {
Self(Some(CheckMortalityParamsInner {
era: Era::Immortal,
checkpoint: None,
}))
}
}
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
type Params = CheckMortalityParams<T>;
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
let check_mortality = if let Some(params) = params.0 {
CheckMortality {
era: params.era,
checkpoint: params.checkpoint.unwrap_or(client.genesis_hash()),
}
} else {
CheckMortality {
era: Era::Immortal,
checkpoint: client.genesis_hash(),
}
};
Ok(check_mortality)
}
}
impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.era.encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.checkpoint.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckMortality<T> {
type Decoded = Era;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckMortality"
}
}
/// The [`ChargeAssetTxPayment`] signed 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()
}
}
/// Parameters to configure the [`ChargeAssetTxPayment`] signed 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> 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> RefineParams<T> for ChargeAssetTxPaymentParams<T> {}
impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
(self.tip, &self.asset_id).encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment<T> {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeAssetTxPayment"
}
}
/// The [`ChargeTransactionPayment`] signed 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
}
}
/// Parameters to configure the [`ChargeTransactionPayment`] signed 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> 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<T: Config> RefineParams<T> for ChargeTransactionPaymentParams {}
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.tip.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeTransactionPayment"
}
}
/// This accepts a tuple of [`SignedExtension`]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>>,
_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: SignedExtension<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 signed 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().signed_extensions().iter().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> = 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().signed_extensions().iter().enumerate() {
let Some(ext) = exts_by_index.remove(&idx) else {
if is_type_empty(e.extra_ty(), types) {
continue
} else {
return Err(ExtrinsicParamsError::UnknownSignedExtension(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: SignedExtension<T>,)+
{
fn encode_extra_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_extra_to(v);
}
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_additional_to(v);
}
}
}
}
}
#[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,
}
}
+337
View File
@@ -0,0 +1,337 @@
// Copyright 2019-2023 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 alloc::format;
use alloc::vec::Vec;
use codec::{Decode, Encode};
use serde::{Deserialize, Serialize};
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
pub use primitive_types::{H256, U256};
/// Default set of commonly used types by Substrate runtimes.
// Note: We only use this at the type level, so it should be impossible to
// create an instance of it.
pub enum SubstrateConfig {}
impl Config for SubstrateConfig {
type Hash = H256;
type AccountId = AccountId32;
type Address = MultiAddress<Self::AccountId, u32>;
type Signature = MultiSignature;
type Hasher = BlakeTwo256;
type Header = SubstrateHeader<u32, BlakeTwo256>;
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
type AssetId = u32;
}
/// 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 type that can hash values using the blaks2_256 algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
pub struct BlakeTwo256;
impl Hasher for BlakeTwo256 {
type Output = H256;
fn hash(s: &[u8]) -> Self::Output {
sp_crypto_hashing::blake2_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<N: Copy + Into<U256> + TryFrom<U256>, H: Hasher> {
/// The parent hash.
pub parent_hash: H::Output,
/// The block number.
#[serde(
serialize_with = "serialize_number",
deserialize_with = "deserialize_number"
)]
#[codec(compact)]
pub number: N,
/// The state trie merkle root
pub state_root: H::Output,
/// The merkle root of the extrinsics.
pub extrinsics_root: H::Output,
/// A chain-specific digest of data useful for light clients or referencing auxiliary data.
pub digest: Digest,
}
impl<N, H> Header for SubstrateHeader<N, H>
where
N: Copy + Into<u64> + Into<U256> + TryFrom<U256> + Encode,
H: Hasher + Encode,
SubstrateHeader<N, H>: Encode + Decode,
{
type Number = N;
type Hasher = H;
fn number(&self) -> Self::Number {
self.number
}
}
/// 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<u32, BlakeTwo256> =
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<u32, BlakeTwo256> =
serde_json::from_str(numeric_block_number_json).expect("valid block header");
assert_eq!(header.number(), 4);
}
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use alloc::borrow::Cow;
use alloc::string::String;
use derive_where::derive_where;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait ConstantAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
/// The name of the constant in a given pallet.
fn constant_name(&self) -> &str;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents the address of a constant.
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct Address<ReturnTy> {
pallet_name: Cow<'static, str>,
constant_name: Cow<'static, str>,
constant_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<ReturnTy>,
}
/// The type of address typically used to return dynamic constant values.
pub type DynamicAddress = Address<DecodedValueThunk>;
impl<ReturnTy> Address<ReturnTy> {
/// Create a new [`Address`] to use to look up a constant.
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
constant_name: Cow::Owned(constant_name.into()),
constant_hash: None,
_marker: core::marker::PhantomData,
}
}
/// Create a new [`Address`] that will be validated
/// against node metadata using the hash given.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
constant_name: &'static str,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
constant_name: Cow::Borrowed(constant_name),
constant_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
/// Do not validate this constant prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
pallet_name: self.pallet_name,
constant_name: self.constant_name,
constant_hash: None,
_marker: self._marker,
}
}
}
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for Address<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn constant_name(&self) -> &str {
&self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// Construct a new dynamic constant lookup.
pub fn dynamic(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> DynamicAddress {
DynamicAddress::new(pallet_name, constant_name)
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing constants.
mod constant_address;
pub use constant_address::{dynamic, Address, ConstantAddress, DynamicAddress};
use alloc::borrow::ToOwned;
use crate::{
error::MetadataError,
metadata::{DecodeWithMetadata, MetadataExt},
Error, Metadata,
};
/// Run validation logic against some constant address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or constant in question do not exist at all).
pub fn validate_constant<Address: ConstantAddress>(
metadata: &subxt_metadata::Metadata,
address: &Address,
) -> Result<(), Error> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = metadata
.pallet_by_name_err(address.pallet_name())?
.constant_hash(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
})?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
}
pub fn get_constant<Address: ConstantAddress>(
metadata: &Metadata,
address: &Address,
) -> Result<Address::Target, Error> {
// 1. Validate constant shape if hash given:
validate_constant(metadata, address)?;
// 2. Attempt to decode the constant into the type given:
let constant = metadata
.pallet_by_name_err(address.pallet_name())?
.constant_by_name(address.constant_name())
.ok_or_else(|| MetadataError::ConstantNameNotFound(address.constant_name().to_owned()))?;
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
&mut constant.value(),
constant.ty(),
metadata,
)?;
Ok(value)
}
@@ -0,0 +1,76 @@
use derive_where::derive_where;
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use crate::utils::Yes;
/// This represents the address of a custom value in in the metadata.
/// Anything, that implements the [CustomValueAddress] trait can be used, to fetch
/// custom values from the metadata.
/// The trait is implemented by [str] for dynamic loopup and [StaticAddress] for static queries.
pub trait CustomValueAddress {
/// The type of the custom value.
type Target: DecodeWithMetadata;
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
/// Should be `()` for custom values, that have an invalid type id.
type IsDecodable;
/// the name (key) by which the custom value can be accessed in the metadata.
fn name(&self) -> &str;
/// An optional hash which, if present, can be checked against node metadata.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
impl CustomValueAddress for str {
type Target = DecodedValueThunk;
type IsDecodable = Yes;
fn name(&self) -> &str {
self
}
}
/// A static address to a custom value.
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct StaticAddress<ReturnTy, IsDecodable> {
name: &'static str,
hash: Option<[u8; 32]>,
phantom: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
}
impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
#[doc(hidden)]
/// Creates a new StaticAddress.
pub fn new_static(name: &'static str, hash: [u8; 32]) -> StaticAddress<ReturnTy, IsDecodable> {
StaticAddress::<ReturnTy, IsDecodable> {
name,
hash: Some(hash),
phantom: core::marker::PhantomData,
}
}
/// Do not validate this custom value prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
name: self.name,
hash: None,
phantom: self.phantom,
}
}
}
impl<R: DecodeWithMetadata, Y> CustomValueAddress for StaticAddress<R, Y> {
type Target = R;
type IsDecodable = Y;
fn name(&self) -> &str {
self.name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.hash
}
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing custom types
mod custom_value_address;
use crate::utils::Yes;
pub use custom_value_address::{CustomValueAddress, StaticAddress};
use crate::{
error::MetadataError,
metadata::{DecodeWithMetadata, MetadataExt},
Error, Metadata,
};
use alloc::vec::Vec;
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
pub fn validate_custom_value<Address: CustomValueAddress + ?Sized>(
metadata: &Metadata,
address: &Address,
) -> Result<(), Error> {
if let Some(actual_hash) = address.validation_hash() {
let custom = metadata.custom();
let custom_value = custom
.get(address.name())
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().into()))?;
let expected_hash = custom_value.hash();
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
if metadata.custom().get(address.name()).is_none() {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
/// or a static address from the generated static interface to get a value of a static type returned.
pub fn get_custom_value<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>(
metadata: &Metadata,
address: &Address,
) -> Result<Address::Target, Error> {
// 1. Validate custom value shape if hash given:
validate_custom_value(metadata, address)?;
// 2. Attempt to decode custom value:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
&mut custom_value.bytes(),
custom_value.type_id(),
metadata,
)?;
Ok(value)
}
/// Access the bytes of a custom value by the address it is registered under.
pub fn get_custom_value_bytes<Address: CustomValueAddress + ?Sized>(
metadata: &Metadata,
address: &Address,
) -> Result<Vec<u8>, Error> {
// 1. Validate custom value shape if hash given:
validate_custom_value(metadata, address)?;
// 2. Return the underlying bytes:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
Ok(custom_value.bytes().to_vec())
}
#[cfg(test)]
mod tests {
use alloc::collections::BTreeMap;
use codec::Encode;
use scale_decode::DecodeAsType;
use scale_info::form::PortableForm;
use scale_info::TypeInfo;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec;
use crate::custom_values::get_custom_value;
use crate::Metadata;
#[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)]
pub struct Person {
age: u16,
name: String,
}
fn mock_metadata() -> Metadata {
let person_ty = scale_info::MetaType::new::<Person>();
let unit = scale_info::MetaType::new::<()>();
let mut types = scale_info::Registry::new();
let person_ty_id = types.register_type(&person_ty);
let unit_id = types.register_type(&unit);
let types: scale_info::PortableRegistry = types.into();
let person = Person {
age: 42,
name: "Neo".into(),
};
let person_value_metadata: frame_metadata::v15::CustomValueMetadata<PortableForm> =
frame_metadata::v15::CustomValueMetadata {
ty: person_ty_id,
value: person.encode(),
};
let frame_metadata = frame_metadata::v15::RuntimeMetadataV15 {
types,
pallets: vec![],
extrinsic: frame_metadata::v15::ExtrinsicMetadata {
version: 0,
address_ty: unit_id,
call_ty: unit_id,
signature_ty: unit_id,
extra_ty: unit_id,
signed_extensions: vec![],
},
ty: unit_id,
apis: vec![],
outer_enums: frame_metadata::v15::OuterEnums {
call_enum_ty: unit_id,
event_enum_ty: unit_id,
error_enum_ty: unit_id,
},
custom: frame_metadata::v15::CustomMetadata {
map: BTreeMap::from_iter([("Mr. Robot".to_owned(), person_value_metadata)]),
},
};
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
Metadata::new(metadata)
}
#[test]
fn test_decoding() {
let metadata = mock_metadata();
assert!(get_custom_value(&metadata, "Invalid Address").is_err());
let person_decoded_value_thunk = get_custom_value(&metadata, "Mr. Robot").unwrap();
let person: Person = person_decoded_value_thunk.as_type().unwrap();
assert_eq!(
person,
Person {
age: 42,
name: "Neo".into()
}
)
}
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2019-2023 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 the entry points to create dynamic
//! transactions, storage and constant lookups.
use crate::metadata::{DecodeWithMetadata, Metadata};
use alloc::vec::Vec;
use scale_decode::DecodeAsType;
pub use scale_value::{At, Value};
/// A [`scale_value::Value`] type endowed with contextual information
/// regarding what type was used to decode each part of it. This implements
/// [`crate::metadata::DecodeWithMetadata`], and is used as a return type
/// for dynamic requests.
pub type DecodedValue = scale_value::Value<u32>;
// Submit dynamic transactions.
pub use crate::tx::dynamic as tx;
// Lookup constants dynamically.
pub use crate::constants::dynamic as constant;
// Lookup storage values dynamically.
pub use crate::storage::dynamic as storage;
// Execute runtime API function call dynamically.
pub use crate::runtime_api::dynamic as runtime_api_call;
/// This is the result of making a dynamic request to a node. From this,
/// we can return the raw SCALE bytes that we were handed back, or we can
/// complete the decoding of the bytes into a [`DecodedValue`] type.
pub struct DecodedValueThunk {
type_id: u32,
metadata: Metadata,
scale_bytes: Vec<u8>,
}
impl DecodeWithMetadata for DecodedValueThunk {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self, scale_decode::Error> {
let mut v = Vec::with_capacity(bytes.len());
v.extend_from_slice(bytes);
*bytes = &[];
Ok(DecodedValueThunk {
type_id,
metadata: metadata.clone(),
scale_bytes: v,
})
}
}
impl DecodedValueThunk {
/// Return the SCALE encoded bytes handed back from the node.
pub fn into_encoded(self) -> Vec<u8> {
self.scale_bytes
}
/// Return the SCALE encoded bytes handed back from the node without taking ownership of them.
pub fn encoded(&self) -> &[u8] {
&self.scale_bytes
}
/// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type.
pub fn to_value(&self) -> Result<DecodedValue, scale_decode::Error> {
let val = scale_value::scale::decode_as_type(
&mut &*self.scale_bytes,
&self.type_id,
self.metadata.types(),
)?;
Ok(val)
}
/// decode the `DecodedValueThunk` into a concrete type.
pub fn as_type<T: DecodeAsType>(&self) -> Result<T, scale_decode::Error> {
T::decode_as_type(
&mut &self.scale_bytes[..],
&self.type_id,
self.metadata.types(),
)
}
}
+184
View File
@@ -0,0 +1,184 @@
use alloc::boxed::Box;
use alloc::string::String;
use derive_more::{Display, From};
use subxt_metadata::StorageHasher;
#[derive(Debug, Display, From)]
pub enum Error {
/// Codec error.
#[display(fmt = "Scale codec error: {_0}")]
Codec(codec::Error),
#[display(fmt = "Metadata Error: {_0}")]
Metadata(MetadataError),
#[display(fmt = "Storage Error: {_0}")]
StorageAddress(StorageAddressError),
/// Error decoding to a [`crate::dynamic::Value`].
#[display(fmt = "Error decoding into dynamic value: {_0}")]
Decode(scale_decode::Error),
/// Error encoding from a [`crate::dynamic::Value`].
#[display(fmt = "Error encoding from dynamic value: {_0}")]
Encode(scale_encode::Error),
/// Error constructing the appropriate extrinsic params.
#[display(fmt = "Extrinsic params error: {_0}")]
ExtrinsicParams(ExtrinsicParamsError),
}
impl From<scale_decode::visitor::DecodeError> for Error {
fn from(value: scale_decode::visitor::DecodeError) -> Self {
Error::Decode(value.into())
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
/// Something went wrong trying to access details in the metadata.
#[derive(Clone, Debug, PartialEq, Display)]
#[non_exhaustive]
pub enum MetadataError {
/// The DispatchError type isn't available in the metadata
#[display(fmt = "The DispatchError type isn't available")]
DispatchErrorNotFound,
/// Type not found in metadata.
#[display(fmt = "Type with ID {_0} not found")]
TypeNotFound(u32),
/// Pallet not found (index).
#[display(fmt = "Pallet with index {_0} not found")]
PalletIndexNotFound(u8),
/// Pallet not found (name).
#[display(fmt = "Pallet with name {_0} not found")]
PalletNameNotFound(String),
/// Variant not found.
#[display(fmt = "Variant with index {_0} not found")]
VariantIndexNotFound(u8),
/// Constant not found.
#[display(fmt = "Constant with name {_0} not found")]
ConstantNameNotFound(String),
/// Call not found.
#[display(fmt = "Call with name {_0} not found")]
CallNameNotFound(String),
/// Runtime trait not found.
#[display(fmt = "Runtime trait with name {_0} not found")]
RuntimeTraitNotFound(String),
/// Runtime method not found.
#[display(fmt = "Runtime method with name {_0} not found")]
RuntimeMethodNotFound(String),
/// Call type not found in metadata.
#[display(fmt = "Call type not found in pallet with index {_0}")]
CallTypeNotFoundInPallet(u8),
/// Event type not found in metadata.
#[display(fmt = "Event type not found in pallet with index {_0}")]
EventTypeNotFoundInPallet(u8),
/// Storage details not found in metadata.
#[display(fmt = "Storage details not found in pallet with name {_0}")]
StorageNotFoundInPallet(String),
/// Storage entry not found.
#[display(fmt = "Storage entry {_0} not found")]
StorageEntryNotFound(String),
/// The generated interface used is not compatible with the node.
#[display(fmt = "The generated code is not compatible with the node")]
IncompatibleCodegen,
/// Custom value not found.
#[display(fmt = "Custom value with name {_0} not found")]
CustomValueNameNotFound(String),
}
#[cfg(feature = "std")]
impl std::error::Error for MetadataError {}
/// Something went wrong trying to encode or decode a storage address.
#[derive(Clone, Debug, Display)]
#[non_exhaustive]
pub enum StorageAddressError {
/// Storage lookup does not have the expected number of keys.
#[display(fmt = "Storage lookup requires {expected} keys but more keys have been provided.")]
TooManyKeys {
/// The number of keys provided in the storage address.
expected: usize,
},
/// This storage entry in the metadata does not have the correct number of hashers to fields.
#[display(
fmt = "Storage entry in metadata does not have the correct number of hashers to fields"
)]
WrongNumberOfHashers {
/// The number of hashers in the metadata for this storage entry.
hashers: usize,
/// The number of fields in the metadata for this storage entry.
fields: usize,
},
/// We weren't given enough bytes to decode the storage address/key.
#[display(fmt = "Not enough remaining bytes to decode the storage address/key")]
NotEnoughBytes,
/// We have leftover bytes after decoding the storage address.
#[display(fmt = "We have leftover bytes after decoding the storage address")]
TooManyBytes,
/// The bytes of a storage address are not the expected address for decoding the storage keys of the address.
#[display(
fmt = "Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata"
)]
UnexpectedAddressBytes,
/// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose.
#[display(
fmt = "An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher"
)]
HasherCannotReconstructKey {
/// Type id of the key's type.
ty_id: u32,
/// The invalid hasher that caused this error.
hasher: StorageHasher,
},
}
#[cfg(feature = "std")]
impl std::error::Error for StorageAddressError {}
/// 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(Display, Debug)]
#[non_exhaustive]
pub enum ExtrinsicParamsError {
/// Cannot find a type id in the metadata. The context provides some additional
/// information about the source of the error (eg the signed extension name).
#[display(fmt = "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,
},
/// A signed extension in use on some chain was not provided.
#[display(
fmt = "The chain expects a signed extension with the name {_0}, but we did not provide one"
)]
UnknownSignedExtension(String),
/// Some custom error.
#[display(fmt = "Error constructing extrinsic parameters: {_0}")]
Custom(Box<dyn CustomError>),
}
/// Anything implementing this trait can be used in [`ExtrinsicParamsError::Custom`].
#[cfg(feature = "std")]
pub trait CustomError: std::error::Error + Send + Sync + 'static {}
#[cfg(feature = "std")]
impl<T: std::error::Error + Send + Sync + 'static> CustomError for T {}
/// Anything implementing this trait can be used in [`ExtrinsicParamsError::Custom`].
#[cfg(not(feature = "std"))]
pub trait CustomError: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static {}
#[cfg(not(feature = "std"))]
impl<T: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static> CustomError for T {}
#[cfg(feature = "std")]
impl std::error::Error for ExtrinsicParamsError {}
impl From<core::convert::Infallible> for ExtrinsicParamsError {
fn from(value: core::convert::Infallible) -> Self {
match value {}
}
}
impl From<Box<dyn CustomError>> for ExtrinsicParamsError {
fn from(value: Box<dyn CustomError>) -> Self {
ExtrinsicParamsError::Custom(value)
}
}
+938
View File
@@ -0,0 +1,938 @@
use alloc::sync::Arc;
use alloc::vec::Vec;
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
use scale_decode::{DecodeAsFields, DecodeAsType};
use subxt_metadata::PalletMetadata;
use crate::{error::MetadataError, Config, Error, Metadata};
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
/// Generated API structures that represent an event implement this trait.
///
/// The trait is utilized to decode emitted events from a block, via obtaining the
/// form of the `Event` from the metadata.
pub trait StaticEvent: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Event name.
const EVENT: &'static str;
/// Returns true if the given pallet and event names match this event.
fn is_event(pallet: &str, event: &str) -> bool {
Self::PALLET == pallet && Self::EVENT == event
}
}
/// A collection of events obtained from a block, bundled with the necessary
/// information needed to decode and iterate over them.
#[derive_where(Clone)]
pub struct Events<T: Config> {
metadata: Metadata,
// Note; raw event bytes are prefixed with a Compact<u32> containing
// the number of events to be decoded. The start_idx reflects that, so
// that we can skip over those bytes when decoding them
event_bytes: Arc<[u8]>,
start_idx: usize,
num_events: u32,
marker: core::marker::PhantomData<T>,
}
// Ignore the Metadata when debug-logging events; it's big and distracting.
impl<T: Config> core::fmt::Debug for Events<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Events")
.field("event_bytes", &self.event_bytes)
.field("start_idx", &self.start_idx)
.field("num_events", &self.num_events)
.finish()
}
}
impl<T: Config> Events<T> {
/// Create a new [`Events`] instance from the given bytes.
pub fn decode_from(metadata: Metadata, event_bytes: Vec<u8>) -> Self {
// event_bytes is a SCALE encoded vector of events. So, pluck the
// compact encoded length from the front, leaving the remaining bytes
// for our iterating to decode.
//
// Note: if we get no bytes back, avoid an error reading vec length
// and default to 0 events.
let cursor = &mut &*event_bytes;
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
// Start decoding after the compact encoded bytes.
let start_idx = event_bytes.len() - cursor.len();
Self {
metadata,
event_bytes: event_bytes.into(),
start_idx,
num_events,
marker: core::marker::PhantomData,
}
}
/// The number of events.
pub fn len(&self) -> u32 {
self.num_events
}
/// Are there no events in this block?
// Note: mainly here to satisfy clippy.
pub fn is_empty(&self) -> bool {
self.num_events == 0
}
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
// use of it with our `FilterEvents` stuff.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
// The event bytes ignoring the compact encoded length on the front:
let event_bytes = self.event_bytes.clone();
let metadata = self.metadata.clone();
let num_events = self.num_events;
let mut pos = self.start_idx;
let mut index = 0;
core::iter::from_fn(move || {
if event_bytes.len() <= pos || num_events == index {
None
} else {
match EventDetails::decode_from(metadata.clone(), event_bytes.clone(), pos, index) {
Ok(event_details) => {
// Skip over decoded bytes in next iteration:
pos += event_details.bytes().len();
// Increment the index:
index += 1;
// Return the event details:
Some(Ok(event_details))
}
Err(e) => {
// By setting the position to the "end" of the event bytes,
// the cursor len will become 0 and the iterator will return `None`
// from now on:
pos = event_bytes.len();
Some(Err(e))
}
}
}
})
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return only those which should decode to the provided `Ev` type.
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| {
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
.transpose()
})
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return the first event found which decodes to the provided `Ev` type.
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return the last event found which decodes to the provided `Ev` type.
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().last().transpose()
}
/// Find an event that decodes to the type provided. Returns true if it was found.
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
/// A phase of a block's execution.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Decode, Encode)]
pub enum Phase {
/// Applying an extrinsic.
ApplyExtrinsic(u32),
/// Finalizing the block.
Finalization,
/// Initializing the block.
Initialization,
}
/// The event details.
#[derive(Debug, Clone)]
pub struct EventDetails<T: Config> {
phase: Phase,
/// The index of the event in the list of events in a given block.
index: u32,
all_bytes: Arc<[u8]>,
// start of the bytes (phase, pallet/variant index and then fields and then topic to follow).
start_idx: usize,
// start of the event (ie pallet/variant index and then the fields and topic after).
event_start_idx: usize,
// start of the fields (ie after phase and pallet/variant index).
event_fields_start_idx: usize,
// end of the fields.
event_fields_end_idx: usize,
// end of everything (fields + topics)
end_idx: usize,
metadata: Metadata,
topics: Vec<T::Hash>,
}
impl<T: Config> EventDetails<T> {
/// Attempt to dynamically decode a single event from our events input.
fn decode_from(
metadata: Metadata,
all_bytes: Arc<[u8]>,
start_idx: usize,
index: u32,
) -> Result<EventDetails<T>, Error> {
let input = &mut &all_bytes[start_idx..];
let phase = Phase::decode(input)?;
let event_start_idx = all_bytes.len() - input.len();
let pallet_index = u8::decode(input)?;
let variant_index = u8::decode(input)?;
let event_fields_start_idx = all_bytes.len() - input.len();
// Get metadata for the event:
let event_pallet = metadata.pallet_by_index_err(pallet_index)?;
let event_variant = event_pallet
.event_variant_by_index(variant_index)
.ok_or(MetadataError::VariantIndexNotFound(variant_index))?;
tracing::debug!(
"Decoding Event '{}::{}'",
event_pallet.name(),
&event_variant.name
);
// Skip over the bytes belonging to this event.
for field_metadata in &event_variant.fields {
// Skip over the bytes for this field:
scale_decode::visitor::decode_with_visitor(
input,
&field_metadata.ty.id,
metadata.types(),
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(scale_decode::Error::from)?;
}
// the end of the field bytes.
let event_fields_end_idx = all_bytes.len() - input.len();
// topics come after the event data in EventRecord.
let topics = Vec::<T::Hash>::decode(input)?;
// what bytes did we skip over in total, including topics.
let end_idx = all_bytes.len() - input.len();
Ok(EventDetails {
phase,
index,
start_idx,
event_start_idx,
event_fields_start_idx,
event_fields_end_idx,
end_idx,
all_bytes,
metadata,
topics,
})
}
/// When was the event produced?
pub fn phase(&self) -> Phase {
self.phase
}
/// What index is this event in the stored events for this block.
pub fn index(&self) -> u32 {
self.index
}
/// The index of the pallet that the event originated from.
pub fn pallet_index(&self) -> u8 {
// Note: never panics; we expect these bytes to exist
// in order that the EventDetails could be created.
self.all_bytes[self.event_fields_start_idx - 2]
}
/// The index of the event variant that the event originated from.
pub fn variant_index(&self) -> u8 {
// Note: never panics; we expect these bytes to exist
// in order that the EventDetails could be created.
self.all_bytes[self.event_fields_start_idx - 1]
}
/// The name of the pallet from whence the Event originated.
pub fn pallet_name(&self) -> &str {
self.event_metadata().pallet().name()
}
/// The name of the event (ie the name of the variant that it corresponds to).
pub fn variant_name(&self) -> &str {
&self.event_metadata().variant().name
}
/// Fetch details from the metadata for this event.
pub fn event_metadata(&self) -> EventMetadataDetails {
let pallet = self
.metadata
.pallet_by_index(self.pallet_index())
.expect("event pallet to be found; we did this already during decoding");
let variant = pallet
.event_variant_by_index(self.variant_index())
.expect("event variant to be found; we did this already during decoding");
EventMetadataDetails { pallet, variant }
}
/// Return _all_ of the bytes representing this event, which include, in order:
/// - The phase.
/// - Pallet and event index.
/// - Event fields.
/// - Event Topics.
pub fn bytes(&self) -> &[u8] {
&self.all_bytes[self.start_idx..self.end_idx]
}
/// Return the bytes representing the fields stored in this event.
pub fn field_bytes(&self) -> &[u8] {
&self.all_bytes[self.event_fields_start_idx..self.event_fields_end_idx]
}
/// Decode and provide the event fields back in the form of a [`scale_value::Composite`]
/// type which represents the named or unnamed fields that were present in the event.
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
let bytes = &mut self.field_bytes();
let event_metadata = self.event_metadata();
let mut fields = event_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
let decoded =
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
Ok(decoded)
}
/// Attempt to decode these [`EventDetails`] into a type representing the event fields.
/// Such types are exposed in the codegen as `pallet_name::events::EventName` types.
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
let ev_metadata = self.event_metadata();
if ev_metadata.pallet.name() == E::PALLET && ev_metadata.variant.name == E::EVENT {
let mut fields = ev_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
let decoded =
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
Ok(Some(decoded))
} else {
Ok(None)
}
}
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
/// the pallet and event enum variants as well as the event fields). A compatible
/// type for this is exposed via static codegen as a root level `Event` type.
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, Error> {
let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx];
let decoded = E::decode_as_type(
&mut &bytes[..],
&self.metadata.outer_enums().event_enum_ty(),
self.metadata.types(),
)?;
Ok(decoded)
}
/// Return the topics associated with this event.
pub fn topics(&self) -> &[T::Hash] {
&self.topics
}
}
/// Details for the given event plucked from the metadata.
pub struct EventMetadataDetails<'a> {
pallet: PalletMetadata<'a>,
variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
impl<'a> EventMetadataDetails<'a> {
pub fn pallet(&self) -> PalletMetadata<'a> {
self.pallet
}
pub fn variant(&self) -> &'a scale_info::Variant<scale_info::form::PortableForm> {
self.variant
}
}
/// Event related test utilities used outside this module.
#[cfg(test)]
pub(crate) mod test_utils {
use super::*;
use crate::config::{Config, SubstrateConfig};
use codec::Encode;
use frame_metadata::{
v15::{
CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletEventMetadata, PalletMetadata,
RuntimeMetadataV15,
},
RuntimeMetadataPrefixed,
};
use scale_info::{meta_type, TypeInfo};
/// An "outer" events enum containing exactly one event.
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
pub enum AllEvents<Ev> {
Test(Ev),
}
/// This encodes to the same format an event is expected to encode to
/// in node System.Events storage.
#[derive(Encode)]
pub struct EventRecord<E: Encode> {
phase: Phase,
event: AllEvents<E>,
topics: Vec<<SubstrateConfig as Config>::Hash>,
}
impl<E: Encode> EventRecord<E> {
/// Create a new event record with the given phase, event, and topics.
pub fn new(phase: Phase, event: E, topics: Vec<<SubstrateConfig as Config>::Hash>) -> Self {
Self {
phase,
event: AllEvents::Test(event),
topics,
}
}
}
/// Build an EventRecord, which encoded events in the format expected
/// to be handed back from storage queries to System.Events.
pub fn event_record<E: Encode>(phase: Phase, event: E) -> EventRecord<E> {
EventRecord::new(phase, event, vec![])
}
/// Build fake metadata consisting of a single pallet that knows
/// about the event type provided.
pub fn metadata<E: TypeInfo + 'static>() -> Metadata {
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Call> {
call: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(TypeInfo)]
enum RuntimeCall {
PalletName(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(TypeInfo)]
enum Pallet {
#[allow(unused)]
SomeCall,
}
let pallets = vec![PalletMetadata {
name: "Test",
storage: None,
calls: None,
event: Some(PalletEventMetadata {
ty: meta_type::<E>(),
}),
constants: vec![],
error: None,
index: 0,
docs: vec![],
}];
let extrinsic = ExtrinsicMetadata {
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<RuntimeCall>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let meta = RuntimeMetadataV15::new(
pallets,
extrinsic,
meta_type::<()>(),
vec![],
OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<AllEvents<E>>(),
error_enum_ty: meta_type::<()>(),
},
CustomMetadata {
map: Default::default(),
},
);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
Metadata::new(runtime_metadata.try_into().unwrap())
}
/// Build an `Events` object for test purposes, based on the details provided,
/// and with a default block hash.
pub fn events<E: Decode + Encode>(
metadata: Metadata,
event_records: Vec<EventRecord<E>>,
) -> Events<SubstrateConfig> {
let num_events = event_records.len() as u32;
let mut event_bytes = Vec::new();
for ev in event_records {
ev.encode_to(&mut event_bytes);
}
events_raw(metadata, event_bytes, num_events)
}
/// Much like [`events`], but takes pre-encoded events and event count, so that we can
/// mess with the bytes in tests if we need to.
pub fn events_raw(
metadata: Metadata,
event_bytes: Vec<u8>,
num_events: u32,
) -> Events<SubstrateConfig> {
// Prepend compact encoded length to event bytes:
let mut all_event_bytes = Compact(num_events).encode();
all_event_bytes.extend(event_bytes);
Events::decode_from(metadata, all_event_bytes)
}
}
#[cfg(test)]
mod tests {
use super::{
test_utils::{event_record, events, events_raw, AllEvents, EventRecord},
*,
};
use crate::config::SubstrateConfig;
use crate::events::Phase;
use codec::Encode;
use primitive_types::H256;
use scale_info::TypeInfo;
use scale_value::Value;
/// Build a fake wrapped metadata.
fn metadata<E: TypeInfo + 'static>() -> Metadata {
test_utils::metadata::<E>()
}
/// [`RawEventDetails`] can be annoying to test, because it contains
/// type info in the decoded field Values. Strip that here so that
/// we can compare fields more easily.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct TestRawEventDetails {
pub phase: Phase,
pub index: u32,
pub pallet: String,
pub pallet_index: u8,
pub variant: String,
pub variant_index: u8,
pub fields: Vec<scale_value::Value>,
}
/// Compare some actual [`RawEventDetails`] with a hand-constructed
/// (probably) [`TestRawEventDetails`].
pub fn assert_raw_events_match(
actual: EventDetails<SubstrateConfig>,
expected: TestRawEventDetails,
) {
let actual_fields_no_context: Vec<_> = actual
.field_values()
.expect("can decode field values (2)")
.into_values()
.map(|value| value.remove_context())
.collect();
// Check each of the other fields:
assert_eq!(actual.phase(), expected.phase);
assert_eq!(actual.index(), expected.index);
assert_eq!(actual.pallet_name(), expected.pallet);
assert_eq!(actual.pallet_index(), expected.pallet_index);
assert_eq!(actual.variant_name(), expected.variant);
assert_eq!(actual.variant_index(), expected.variant_index);
assert_eq!(actual_fields_no_context, expected.fields);
}
#[test]
fn statically_decode_single_root_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType)]
enum Event {
A(u8, bool, Vec<String>),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let event = Event::A(1, true, vec!["Hi".into()]);
let events = events::<Event>(
metadata,
vec![event_record(Phase::ApplyExtrinsic(123), event.clone())],
);
let ev = events
.iter()
.next()
.expect("one event expected")
.expect("event should be extracted OK");
// This is the line we're testing:
let decoded_event = ev
.as_root_event::<AllEvents<Event>>()
.expect("can decode event into root enum again");
// It should equal the event we put in:
assert_eq!(decoded_event, AllEvents::Test(event));
}
#[test]
fn dynamically_decode_single_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8, bool, Vec<String>),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let event = Event::A(1, true, vec!["Hi".into()]);
let events = events::<Event>(
metadata,
vec![event_record(Phase::ApplyExtrinsic(123), event)],
);
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
phase: Phase::ApplyExtrinsic(123),
index: 0,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![
Value::u128(1),
Value::bool(true),
Value::unnamed_composite(vec![Value::string("Hi")]),
],
},
);
assert!(event_details.next().is_none());
}
#[test]
fn dynamically_decode_multiple_events() {
#[derive(Clone, Copy, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let event1 = Event::A(1);
let event2 = Event::B(true);
let event3 = Event::A(234);
let events = events::<Event>(
metadata,
vec![
event_record(Phase::Initialization, event1),
event_record(Phase::ApplyExtrinsic(123), event2),
event_record(Phase::Finalization, event3),
],
);
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Initialization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(1)],
},
);
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
pallet: "Test".to_string(),
pallet_index: 0,
variant: "B".to_string(),
variant_index: 1,
fields: vec![Value::bool(true)],
},
);
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 2,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(234)],
},
);
assert!(event_details.next().is_none());
}
#[test]
fn dynamically_decode_multiple_events_until_error() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode 2 events:
let mut event_bytes = vec![];
event_record(Phase::Initialization, Event::A(1)).encode_to(&mut event_bytes);
event_record(Phase::ApplyExtrinsic(123), Event::B(true)).encode_to(&mut event_bytes);
// Push a few naff bytes to the end (a broken third event):
event_bytes.extend_from_slice(&[3, 127, 45, 0, 2]);
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events_raw(
metadata,
event_bytes,
3, // 2 "good" events, and then it'll hit the naff bytes.
);
let mut events_iter = events.iter();
assert_raw_events_match(
events_iter.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Initialization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(1)],
},
);
assert_raw_events_match(
events_iter.next().unwrap().unwrap(),
TestRawEventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
pallet: "Test".to_string(),
pallet_index: 0,
variant: "B".to_string(),
variant_index: 1,
fields: vec![Value::bool(true)],
},
);
// We'll hit an error trying to decode the third event:
assert!(events_iter.next().unwrap().is_err());
// ... and then "None" from then on.
assert!(events_iter.next().is_none());
assert!(events_iter.next().is_none());
}
#[test]
fn compact_event_field() {
#[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
enum Event {
A(#[codec(compact)] u32),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events::<Event>(
metadata,
vec![event_record(Phase::Finalization, Event::A(1))],
);
// Dynamically decode:
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::u128(1)],
},
);
assert!(event_details.next().is_none());
}
#[test]
fn compact_wrapper_struct_field() {
#[derive(Clone, Decode, Debug, PartialEq, Encode, TypeInfo)]
enum Event {
A(#[codec(compact)] CompactWrapper),
}
#[derive(Clone, Decode, Debug, PartialEq, codec::CompactAs, Encode, TypeInfo)]
struct CompactWrapper(u64);
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construct an Events object to iterate them:
let events = events::<Event>(
metadata,
vec![event_record(
Phase::Finalization,
Event::A(CompactWrapper(1)),
)],
);
// Dynamically decode:
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::unnamed_composite(vec![Value::u128(1)])],
},
);
assert!(event_details.next().is_none());
}
#[test]
fn event_containing_explicit_index() {
#[derive(Clone, Debug, PartialEq, Eq, Decode, Encode, TypeInfo)]
#[repr(u8)]
#[allow(trivial_numeric_casts, clippy::unnecessary_cast)] // required because the Encode derive produces a warning otherwise
pub enum MyType {
B = 10u8,
}
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(MyType),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construct an Events object to iterate them:
let events = events::<Event>(
metadata,
vec![event_record(Phase::Finalization, Event::A(MyType::B))],
);
// Dynamically decode:
let mut event_details = events.iter();
assert_raw_events_match(
event_details.next().unwrap().unwrap(),
TestRawEventDetails {
index: 0,
phase: Phase::Finalization,
pallet: "Test".to_string(),
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::unnamed_variant("B", vec![])],
},
);
assert!(event_details.next().is_none());
}
#[test]
fn topics() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo, scale_decode::DecodeAsType)]
enum Event {
A(u8, bool, Vec<String>),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construct an Events object to iterate them:
let event = Event::A(1, true, vec!["Hi".into()]);
let topics = vec![H256::from_low_u64_le(123), H256::from_low_u64_le(456)];
let events = events::<Event>(
metadata,
vec![EventRecord::new(
Phase::ApplyExtrinsic(123),
event,
topics.clone(),
)],
);
let ev = events
.iter()
.next()
.expect("one event expected")
.expect("event should be extracted OK");
assert_eq!(topics, ev.topics());
}
}
+42
View File
@@ -0,0 +1,42 @@
// 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.
//! # Subxt-core
//!
//! `#[no_std]` compatible core crate for subxt.
#![cfg_attr(not(feature = "std"), no_std)]
pub extern crate alloc;
pub mod blocks;
pub mod client;
pub mod config;
pub mod constants;
pub mod custom_values;
pub mod dynamic;
pub mod error;
pub mod events;
pub mod metadata;
pub mod runtime_api;
pub mod storage;
pub mod tx;
pub mod utils;
pub use config::Config;
pub use error::Error;
pub use metadata::Metadata;
#[macro_use]
mod macros;
pub mod ext {
pub use codec;
pub use scale_decode;
pub use scale_encode;
cfg_substrate_compat! {
pub use sp_runtime;
pub use sp_core;
}
}
+17
View File
@@ -0,0 +1,17 @@
macro_rules! cfg_feature {
($feature:literal, $($item:item)*) => {
$(
#[cfg(feature = $feature)]
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
$item
)*
}
}
macro_rules! cfg_substrate_compat {
($($item:item)*) => {
crate::macros::cfg_feature!("substrate-compat", $($item)*);
};
}
pub(crate) use {cfg_feature, cfg_substrate_compat};
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::Metadata;
use alloc::vec::Vec;
/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`].
pub trait DecodeWithMetadata: Sized {
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self, scale_decode::Error>;
}
impl<T: scale_decode::DecodeAsType> DecodeWithMetadata for T {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<T, scale_decode::Error> {
let val = T::decode_as_type(bytes, &type_id, metadata.types())?;
Ok(val)
}
}
/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`].
pub trait EncodeWithMetadata {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), scale_encode::Error>;
}
impl<T: scale_encode::EncodeAsType> EncodeWithMetadata for T {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.encode_as_type_to(&type_id, metadata.types(), bytes)?;
Ok(())
}
}
+137
View File
@@ -0,0 +1,137 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::error::MetadataError;
use alloc::borrow::ToOwned;
use alloc::sync::Arc;
/// A cheaply clone-able representation of the runtime metadata received from a node.
#[derive(Clone, Debug)]
pub struct Metadata {
inner: Arc<subxt_metadata::Metadata>,
}
impl core::ops::Deref for Metadata {
type Target = subxt_metadata::Metadata;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Metadata {
pub fn new(md: subxt_metadata::Metadata) -> Self {
Metadata {
inner: Arc::new(md),
}
}
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
pub fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
self.pallet_by_name(name)
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
}
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
pub fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
self.pallet_by_index(index)
.ok_or(MetadataError::PalletIndexNotFound(index))
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
pub fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError> {
self.runtime_api_trait_by_name(name)
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
}
}
impl From<subxt_metadata::Metadata> for Metadata {
fn from(md: subxt_metadata::Metadata) -> Self {
Metadata::new(md)
}
}
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for Metadata {
type Error = subxt_metadata::TryFromError;
fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
subxt_metadata::Metadata::try_from(value).map(Metadata::from)
}
}
impl codec::Decode for Metadata {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
subxt_metadata::Metadata::decode(input).map(Metadata::new)
}
}
/// Some extension methods on [`subxt_metadata::Metadata`] that return Errors instead of Options.
pub trait MetadataExt {
fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError>;
fn custom_value_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError>;
}
impl MetadataExt for subxt_metadata::Metadata {
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
self.pallet_by_name(name)
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
}
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
self.pallet_by_index(index)
.ok_or(MetadataError::PalletIndexNotFound(index))
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError> {
self.runtime_api_trait_by_name(name)
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
fn custom_value_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError> {
self.custom()
.get(name)
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
}
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2019-2023 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 metadata obtained from a node.
mod decode_encode_traits;
mod metadata_type;
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::{Metadata, MetadataExt};
// Expose metadata types under a sub module in case somebody needs to reference them:
pub use subxt_metadata as types;
+185
View File
@@ -0,0 +1,185 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use alloc::borrow::Cow;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use core::marker::PhantomData;
use derive_where::derive_where;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::Error;
use crate::metadata::{DecodeWithMetadata, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
pub trait RuntimeApiPayload {
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
/// The runtime API method name.
fn method_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`RuntimeApiPayload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
pub struct Payload<ArgsData, ReturnTy> {
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args_data: ArgsData,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
}
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
for Payload<ArgsData, ReturnTy>
{
type ReturnType = ReturnTy;
fn trait_name(&self) -> &str {
&self.trait_name
}
fn method_name(&self) -> &str {
&self.method_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(&self.trait_name)?
.method_by_name(&self.method_name)
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
let mut fields = api_method
.inputs()
.map(|input| scale_encode::Field::named(&input.ty, &input.name));
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// A dynamic runtime API payload.
pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
/// Create a new [`Payload`].
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsData,
) -> Self {
Payload {
trait_name: Cow::Owned(trait_name.into()),
method_name: Cow::Owned(method_name.into()),
args_data,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`Payload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
trait_name: &'static str,
method_name: &'static str,
args_data: ArgsData,
hash: [u8; 32],
) -> Payload<ArgsData, ReturnTy> {
Payload {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args_data,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the trait name.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Returns the method name.
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicRuntimeApiPayload`].
pub fn dynamic(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicRuntimeApiPayload {
Payload::new(trait_name, method_name, args_data.into())
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing and working with storage items.
mod storage_address;
mod storage_key;
pub mod utils;
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use super::storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageKey};
}
pub use storage_key::StorageKey;
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
dynamic::DecodedValueThunk,
error::{Error, MetadataError},
metadata::{DecodeWithMetadata, Metadata},
utils::Yes,
};
use derive_where::derive_where;
use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use alloc::vec::Vec;
use super::{storage_key::StorageHashers, StorageKey};
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
pub trait StorageAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The keys type used to construct this address.
type Keys: StorageKey;
/// Can an entry be fetched from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsFetchable;
/// Can a default entry be obtained from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsDefaultable;
/// Can this address be iterated over?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsIterable;
/// The name of the pallet that the entry lives under.
fn pallet_name(&self) -> &str;
/// The name of the entry in a given pallet that the item is at.
fn entry_name(&self) -> &str;
/// Output the non-prefix bytes; that is, any additional bytes that need
/// to be appended to the key to dig into maps.
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error>;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A concrete storage address. This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; Keys)]
pub struct Address<Keys: StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
keys: Keys,
validation_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
}
/// A typical storage address constructed at runtime rather than via the `subxt` macro; this
/// has no restriction on what it can be used for (since we don't statically know).
pub type DynamicAddress<Keys> = Address<Keys, DecodedValueThunk, Yes, Yes, Yes>;
impl<Keys: StorageKey> DynamicAddress<Keys> {
/// Creates a new dynamic address. As `Keys` you can use a `Vec<scale_value::Value>`
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>, keys: Keys) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
entry_name: Cow::Owned(entry_name.into()),
keys,
validation_hash: None,
_marker: core::marker::PhantomData,
}
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
Address<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`Address`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
entry_name: &'static str,
keys: Keys,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
keys,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
Address<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
{
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Return bytes representing the root of this storage entry (a hash of the pallet and entry name).
pub fn to_root_bytes(&self) -> Vec<u8> {
super::utils::storage_address_root_bytes(self)
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
for Address<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
{
type Target = ReturnTy;
type Keys = Keys;
type IsFetchable = Fetchable;
type IsDefaultable = Defaultable;
type IsIterable = Iterable;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn entry_name(&self) -> &str {
&self.entry_name
}
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(self.pallet_name())?;
let storage = pallet
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
let entry = storage
.entry_by_name(self.entry_name())
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
let hashers = StorageHashers::new(entry.entry_type(), metadata.types())?;
self.keys
.encode_storage_key(bytes, &mut hashers.iter(), metadata.types())?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<Keys: StorageKey>(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Keys,
) -> DynamicAddress<Keys> {
DynamicAddress::new(pallet_name, entry_name, storage_entry_keys)
}
+475
View File
@@ -0,0 +1,475 @@
use super::utils::hash_bytes;
use crate::{
error::{Error, MetadataError, StorageAddressError},
utils::{Encoded, Static},
};
use alloc::vec;
use alloc::vec::Vec;
use derive_where::derive_where;
use scale_decode::visitor::IgnoreVisitor;
use scale_encode::EncodeAsType;
use scale_info::{PortableRegistry, TypeDef};
use scale_value::Value;
use subxt_metadata::{StorageEntryType, StorageHasher};
/// A collection of storage hashers paired with the type ids of the types they should hash.
/// Can be created for each storage entry in the metadata via [`StorageHashers::new()`].
#[derive(Debug)]
pub struct StorageHashers {
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
}
impl StorageHashers {
/// Creates new [`StorageHashers`] from a storage entry. Looks at the [`StorageEntryType`] and
/// assigns a hasher to each type id that makes up the key.
pub fn new(storage_entry: &StorageEntryType, types: &PortableRegistry) -> Result<Self, Error> {
let mut hashers_and_ty_ids = vec![];
if let StorageEntryType::Map {
hashers, key_ty, ..
} = storage_entry
{
let ty = types
.resolve(*key_ty)
.ok_or(MetadataError::TypeNotFound(*key_ty))?;
if let TypeDef::Tuple(tuple) = &ty.type_def {
if hashers.len() == 1 {
// use the same hasher for all fields, if only 1 hasher present:
let hasher = hashers[0];
for f in tuple.fields.iter() {
hashers_and_ty_ids.push((hasher, f.id));
}
} else if hashers.len() < tuple.fields.len() {
return Err(StorageAddressError::WrongNumberOfHashers {
hashers: hashers.len(),
fields: tuple.fields.len(),
}
.into());
} else {
for (i, f) in tuple.fields.iter().enumerate() {
hashers_and_ty_ids.push((hashers[i], f.id));
}
}
} else {
if hashers.len() != 1 {
return Err(StorageAddressError::WrongNumberOfHashers {
hashers: hashers.len(),
fields: 1,
}
.into());
}
hashers_and_ty_ids.push((hashers[0], *key_ty));
};
}
Ok(Self { hashers_and_ty_ids })
}
/// Creates an iterator over the storage hashers and type ids.
pub fn iter(&self) -> StorageHashersIter<'_> {
StorageHashersIter {
hashers: self,
idx: 0,
}
}
}
/// An iterator over all type ids of the key and the respective hashers.
/// See [`StorageHashers::iter()`].
#[derive(Debug)]
pub struct StorageHashersIter<'a> {
hashers: &'a StorageHashers,
idx: usize,
}
impl<'a> StorageHashersIter<'a> {
fn next_or_err(&mut self) -> Result<(StorageHasher, u32), Error> {
self.next().ok_or_else(|| {
StorageAddressError::TooManyKeys {
expected: self.hashers.hashers_and_ty_ids.len(),
}
.into()
})
}
}
impl<'a> Iterator for StorageHashersIter<'a> {
type Item = (StorageHasher, u32);
fn next(&mut self) -> Option<Self::Item> {
let item = self.hashers.hashers_and_ty_ids.get(self.idx).copied()?;
self.idx += 1;
Some(item)
}
}
impl<'a> ExactSizeIterator for StorageHashersIter<'a> {
fn len(&self) -> usize {
self.hashers.hashers_and_ty_ids.len() - self.idx
}
}
/// This trait should be implemented by anything that can be used as one or multiple storage keys.
pub trait StorageKey {
/// Encodes the storage key into some bytes
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error>;
/// Attempts to decode the StorageKey given some bytes and a set of hashers and type IDs that they are meant to represent.
/// The bytes passed to `decode` should start with:
/// - 1. some fixed size hash (for all hashers except `Identity`)
/// - 2. the plain key value itself (for `Identity`, `Blake2_128Concat` and `Twox64Concat` hashers)
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static;
}
/// Implement `StorageKey` for `()` which can be used for keyless storage entries,
/// or to otherwise just ignore some entry.
impl StorageKey for () {
fn encode_storage_key(
&self,
_bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
_types: &PortableRegistry,
) -> Result<(), Error> {
_ = hashers.next_or_err();
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error> {
let (hasher, ty_id) = match hashers.next_or_err() {
Ok((hasher, ty_id)) => (hasher, ty_id),
Err(_) if bytes.is_empty() => return Ok(()),
Err(err) => return Err(err),
};
consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
Ok(())
}
}
/// A storage key for static encoded values.
/// The original value is only present at construction, but can be decoded from the contained bytes.
#[derive_where(Clone, Debug, PartialOrd, PartialEq, Eq)]
pub struct StaticStorageKey<K: ?Sized> {
bytes: Static<Encoded>,
_marker: core::marker::PhantomData<K>,
}
impl<K: codec::Encode + ?Sized> StaticStorageKey<K> {
/// Creates a new static storage key
pub fn new(key: &K) -> Self {
StaticStorageKey {
bytes: Static(Encoded(key.encode())),
_marker: core::marker::PhantomData,
}
}
}
impl<K: codec::Decode + ?Sized> StaticStorageKey<K> {
/// Decodes the encoded inner bytes into the type `K`.
pub fn decoded(&self) -> Result<K, Error> {
let decoded = K::decode(&mut self.bytes())?;
Ok(decoded)
}
}
impl<K: ?Sized> StaticStorageKey<K> {
/// Returns the scale-encoded bytes that make up this key
pub fn bytes(&self) -> &[u8] {
&self.bytes.0 .0
}
}
// Note: The ?Sized bound is necessary to support e.g. `StorageKey<[u8]>`.
impl<K: ?Sized> StorageKey for StaticStorageKey<K> {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
let (hasher, ty_id) = hashers.next_or_err()?;
let encoded_value = self.bytes.encode_as_type(&ty_id, types)?;
hash_bytes(&encoded_value, hasher, bytes);
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
let (hasher, ty_id) = hashers.next_or_err()?;
let key_bytes = consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
// if the hasher had no key appended, we can't decode it into a `StaticStorageKey`.
let Some(key_bytes) = key_bytes else {
return Err(StorageAddressError::HasherCannotReconstructKey { ty_id, hasher }.into());
};
// Return the key bytes.
let key = StaticStorageKey {
bytes: Static(Encoded(key_bytes.to_vec())),
_marker: core::marker::PhantomData::<K>,
};
Ok(key)
}
}
impl StorageKey for Vec<scale_value::Value> {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
for value in self.iter() {
let (hasher, ty_id) = hashers.next_or_err()?;
let encoded_value = value.encode_as_type(&ty_id, types)?;
hash_bytes(&encoded_value, hasher, bytes);
}
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
let mut result: Vec<scale_value::Value> = vec![];
for (hasher, ty_id) in hashers.by_ref() {
match consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)? {
Some(value_bytes) => {
let value =
scale_value::scale::decode_as_type(&mut &*value_bytes, &ty_id, types)?;
result.push(value.remove_context());
}
None => {
result.push(Value::unnamed_composite([]));
}
}
}
// We've consumed all of the hashers, so we expect to also consume all of the bytes:
if !bytes.is_empty() {
return Err(StorageAddressError::TooManyBytes.into());
}
Ok(result)
}
}
// Skip over the hash bytes (including any key at the end), returning bytes
// representing the key if one exists, or None if the hasher has no key appended.
fn consume_hash_returning_key_bytes<'a>(
bytes: &mut &'a [u8],
hasher: StorageHasher,
ty_id: u32,
types: &PortableRegistry,
) -> Result<Option<&'a [u8]>, Error> {
// Strip the bytes off for the actual hash, consuming them.
let bytes_to_strip = hasher.len_excluding_key();
if bytes.len() < bytes_to_strip {
return Err(StorageAddressError::NotEnoughBytes.into());
}
*bytes = &bytes[bytes_to_strip..];
// Now, find the bytes representing the key, consuming them.
let before_key = *bytes;
if hasher.ends_with_key() {
scale_decode::visitor::decode_with_visitor(
bytes,
&ty_id,
types,
IgnoreVisitor::<PortableRegistry>::new(),
)
.map_err(|err| Error::Decode(err.into()))?;
// Return the key bytes, having advanced the input cursor past them.
let key_bytes = &before_key[..before_key.len() - bytes.len()];
Ok(Some(key_bytes))
} else {
// There are no key bytes, so return None.
Ok(None)
}
}
/// Generates StorageKey implementations for tuples
macro_rules! impl_tuples {
($($ty:ident $n:tt),+) => {{
impl<$($ty: StorageKey),+> StorageKey for ($( $ty ),+) {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
$( self.$n.encode_storage_key(bytes, hashers, types)?; )+
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
Ok( ( $( $ty::decode_storage_key(bytes, hashers, types)?, )+ ) )
}
}
}};
}
#[rustfmt::skip]
const _: () = {
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);
};
#[cfg(test)]
mod tests {
use codec::Encode;
use scale_info::{meta_type, PortableRegistry, Registry, TypeInfo};
use subxt_metadata::StorageHasher;
use crate::utils::Era;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use super::{StaticStorageKey, StorageKey};
struct KeyBuilder {
registry: Registry,
bytes: Vec<u8>,
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
}
impl KeyBuilder {
fn new() -> KeyBuilder {
KeyBuilder {
registry: Registry::new(),
bytes: vec![],
hashers_and_ty_ids: vec![],
}
}
fn add<T: TypeInfo + Encode + 'static>(mut self, value: T, hasher: StorageHasher) -> Self {
let id = self.registry.register_type(&meta_type::<T>()).id;
self.hashers_and_ty_ids.push((hasher, id));
for _i in 0..hasher.len_excluding_key() {
self.bytes.push(0);
}
value.encode_to(&mut self.bytes);
self
}
fn build(self) -> (PortableRegistry, Vec<u8>, Vec<(StorageHasher, u32)>) {
(self.registry.into(), self.bytes, self.hashers_and_ty_ids)
}
}
#[test]
fn storage_key_decoding_fuzz() {
let hashers = [
StorageHasher::Blake2_128,
StorageHasher::Blake2_128Concat,
StorageHasher::Blake2_256,
StorageHasher::Identity,
StorageHasher::Twox128,
StorageHasher::Twox256,
StorageHasher::Twox64Concat,
];
let key_preserving_hashers = [
StorageHasher::Blake2_128Concat,
StorageHasher::Identity,
StorageHasher::Twox64Concat,
];
type T4A = (
(),
StaticStorageKey<u32>,
StaticStorageKey<String>,
StaticStorageKey<Era>,
);
type T4B = (
(),
(StaticStorageKey<u32>, StaticStorageKey<String>),
StaticStorageKey<Era>,
);
type T4C = (
((), StaticStorageKey<u32>),
(StaticStorageKey<String>, StaticStorageKey<Era>),
);
let era = Era::Immortal;
for h0 in hashers {
for h1 in key_preserving_hashers {
for h2 in key_preserving_hashers {
for h3 in key_preserving_hashers {
let (types, bytes, hashers_and_ty_ids) = KeyBuilder::new()
.add((), h0)
.add(13u32, h1)
.add("Hello", h2)
.add(era, h3)
.build();
let hashers = super::StorageHashers { hashers_and_ty_ids };
let keys_a =
T4A::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
let keys_b =
T4B::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
let keys_c =
T4C::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
assert_eq!(keys_a.1.decoded().unwrap(), 13);
assert_eq!(keys_b.1 .0.decoded().unwrap(), 13);
assert_eq!(keys_c.0 .1.decoded().unwrap(), 13);
assert_eq!(keys_a.2.decoded().unwrap(), "Hello");
assert_eq!(keys_b.1 .1.decoded().unwrap(), "Hello");
assert_eq!(keys_c.1 .0.decoded().unwrap(), "Hello");
assert_eq!(keys_a.3.decoded().unwrap(), era);
assert_eq!(keys_b.2.decoded().unwrap(), era);
assert_eq!(keys_c.1 .1.decoded().unwrap(), era);
}
}
}
}
}
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! these utility methods complement the [`StorageAddress`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use crate::error::MetadataError;
use crate::metadata::{DecodeWithMetadata, MetadataExt};
use alloc::vec::Vec;
use subxt_metadata::PalletMetadata;
use subxt_metadata::{StorageEntryMetadata, StorageHasher};
use super::StorageAddress;
use crate::{error::Error, metadata::Metadata};
use alloc::borrow::ToOwned;
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
}
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_crypto_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_crypto_hashing::twox_64(input));
bytes.extend(input);
}
}
}
/// Return details about the given storage entry.
pub fn lookup_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a subxt_metadata::Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let storage_metadata = pallet_metadata
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
let storage_entry = storage_metadata
.entry_by_name(entry_name)
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
/// Validate a storage address against the metadata.
pub fn validate_storage_address<Address: StorageAddress>(
address: &Address,
pallet: PalletMetadata<'_>,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(pallet, address.entry_name(), hash)?;
}
Ok(())
}
/// Validate a storage entry against the metadata.
fn validate_storage(
pallet: PalletMetadata<'_>,
storage_name: &str,
hash: [u8; 32],
) -> Result<(), Error> {
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Given some bytes, a pallet and storage name, decode the response.
pub fn decode_storage_with_metadata<T: DecodeWithMetadata>(
bytes: &mut &[u8],
metadata: &Metadata,
storage_metadata: &StorageEntryMetadata,
) -> Result<T, Error> {
let return_ty = storage_metadata.entry_type().value_ty();
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
Ok(val)
}
+180
View File
@@ -0,0 +1,180 @@
// Copyright 2019-2023 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 the trait and types used to represent
//! transactions that can be submitted.
use crate::error::MetadataError;
use crate::metadata::Metadata;
use crate::Error;
use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use alloc::vec::Vec;
use codec::Encode;
use scale_encode::EncodeAsFields;
use scale_value::{Composite, Value, ValueDef, Variant};
pub mod signer;
pub use signer::Signer;
/// This represents a transaction payload that can be submitted
/// to a node.
pub trait TxPayload {
/// Encode call data to the provided output.
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode call data and return the output. This is a convenience
/// wrapper around [`TxPayload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
None
}
}
pub struct ValidationDetails<'a> {
/// The pallet name.
pub pallet_name: &'a str,
/// The call name.
pub call_name: &'a str,
/// A hash (this is generated at compile time in our codegen)
/// to compare against the runtime code.
pub hash: [u8; 32],
}
/// A transaction payload containing some generic `CallData`.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Payload<CallData> {
pallet_name: Cow<'static, str>,
call_name: Cow<'static, str>,
call_data: CallData,
validation_hash: Option<[u8; 32]>,
}
/// The type of a payload typically used for dynamic transaction payloads.
pub type DynamicPayload = Payload<Composite<()>>;
impl<CallData> Payload<CallData> {
/// Create a new [`Payload`].
pub fn new(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: CallData,
) -> Self {
Payload {
pallet_name: Cow::Owned(pallet_name.into()),
call_name: Cow::Owned(call_name.into()),
call_data,
validation_hash: None,
}
}
/// Create a new [`Payload`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
Payload {
pallet_name: Cow::Borrowed(pallet_name),
call_name: Cow::Borrowed(call_name),
call_data,
validation_hash: Some(validation_hash),
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the call data.
pub fn call_data(&self) -> &CallData {
&self.call_data
}
/// Returns the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// Returns the call name.
pub fn call_name(&self) -> &str {
&self.call_name
}
}
impl Payload<Composite<()>> {
/// Convert the dynamic `Composite` payload into a [`Value`].
/// This is useful if you want to use this as an argument for a
/// larger dynamic call that wants to use this as a nested call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.call_data,
}),
};
Value::unnamed_variant(self.pallet_name, [call])
}
}
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
let pallet_index = pallet.index();
let call_index = call.index;
pallet_index.encode_to(out);
call_index.encode_to(out);
let mut fields = call
.fields
.iter()
.map(|f| scale_encode::Field::new(&f.ty.id, f.name.as_deref()));
self.call_data
.encode_as_fields_to(&mut fields, metadata.types(), out)
.expect("The fields are valid types from the metadata, qed;");
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| ValidationDetails {
pallet_name: &self.pallet_name,
call_name: &self.call_name,
hash,
})
}
}
/// Construct a transaction at runtime; essentially an alias to [`Payload::new()`]
/// which provides a [`Composite`] value for the call data.
pub fn dynamic(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: impl Into<Composite<()>>,
) -> DynamicPayload {
Payload::new(pallet_name, call_name, call_data.into())
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! A library to **sub**mit e**xt**rinsics to a
//! [substrate](https://github.com/paritytech/substrate) node via RPC.
use crate::macros::cfg_substrate_compat;
use crate::Config;
/// Signing transactions requires a [`Signer`]. This is responsible for
/// providing the "from" account that the transaction is being signed by,
/// as well as actually signing a SCALE encoded payload.
pub trait Signer<T: Config> {
/// Return the "from" account ID.
fn account_id(&self) -> T::AccountId;
/// Return the "from" address.
fn address(&self) -> T::Address;
/// Takes a signer payload for an extrinsic, and returns a signature based on it.
///
/// Some signers may fail, for instance because the hardware on which the keys are located has
/// refused the operation.
fn sign(&self, signer_payload: &[u8]) -> T::Signature;
}
cfg_substrate_compat! {
pub use pair_signer::PairSigner;
}
// A signer suitable for substrate based chains. This provides compatibility with Substrate
// packages like sp_keyring and such, and so relies on sp_core and sp_runtime to be included.
#[cfg(feature = "substrate-compat")]
mod pair_signer {
use super::Signer;
use crate::Config;
use sp_core::Pair as PairT;
use sp_runtime::{
traits::{IdentifyAccount, Verify},
AccountId32 as SpAccountId32, MultiSignature as SpMultiSignature,
};
/// A [`Signer`] implementation that can be constructed from an [`sp_core::Pair`].
#[derive(Clone, Debug)]
pub struct PairSigner<T: Config, Pair> {
account_id: T::AccountId,
signer: Pair,
}
impl<T, Pair> PairSigner<T, Pair>
where
T: Config,
Pair: PairT,
// We go via an `sp_runtime::MultiSignature`. We can probably generalise this
// by implementing some of these traits on our built-in MultiSignature and then
// requiring them on all T::Signatures, to avoid any go-between.
<SpMultiSignature as Verify>::Signer: From<Pair::Public>,
T::AccountId: From<SpAccountId32>,
{
/// Creates a new [`Signer`] from an [`sp_core::Pair`].
pub fn new(signer: Pair) -> Self {
let account_id =
<SpMultiSignature as Verify>::Signer::from(signer.public()).into_account();
Self {
account_id: account_id.into(),
signer,
}
}
/// Returns the [`sp_core::Pair`] implementation used to construct this.
pub fn signer(&self) -> &Pair {
&self.signer
}
/// Return the account ID.
pub fn account_id(&self) -> &T::AccountId {
&self.account_id
}
}
impl<T, Pair> Signer<T> for PairSigner<T, Pair>
where
T: Config,
Pair: PairT,
Pair::Signature: Into<T::Signature>,
{
fn account_id(&self) -> T::AccountId {
self.account_id.clone()
}
fn address(&self) -> T::Address {
self.account_id.clone().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.signer.sign(signer_payload).into()
}
}
}
+226
View File
@@ -0,0 +1,226 @@
// Copyright 2019-2023 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 alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use codec::{Decode, Encode};
use derive_more::Display;
use serde::{Deserialize, Serialize};
/// 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, Display)]
#[allow(missing_docs)]
pub enum FromSs58Error {
#[display(fmt = "Base 58 requirement is violated")]
BadBase58,
#[display(fmt = "Length is bad")]
BadLength,
#[display(fmt = "Invalid checksum")]
InvalidChecksum,
#[display(fmt = "Invalid SS58 prefix byte.")]
InvalidPrefix,
}
#[cfg(feature = "std")]
impl std::error::Error for FromSs58Error {}
// 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)
}
}
// Improve compat with the substrate version if we're using those crates:
#[cfg(feature = "substrate-compat")]
mod substrate_impls {
use super::*;
impl From<sp_runtime::AccountId32> for AccountId32 {
fn from(value: sp_runtime::AccountId32) -> Self {
Self(value.into())
}
}
impl From<sp_core::sr25519::Public> for AccountId32 {
fn from(value: sp_core::sr25519::Public) -> Self {
let acc: sp_runtime::AccountId32 = value.into();
acc.into()
}
}
impl From<sp_core::ed25519::Public> for AccountId32 {
fn from(value: sp_core::ed25519::Public) -> Self {
let acc: sp_runtime::AccountId32 = value.into();
acc.into()
}
}
}
#[cfg(test)]
mod test {
use super::*;
use sp_core::crypto::Ss58Codec;
use sp_keyring::AccountKeyring;
#[test]
fn ss58_is_compatible_with_substrate_impl() {
let keyrings = vec![
AccountKeyring::Alice,
AccountKeyring::Bob,
AccountKeyring::Charlie,
];
for keyring in keyrings {
let substrate_account = keyring.to_account_id();
// Avoid "From" impl hidden behind "substrate-compat" feature so that this test
// can work either way:
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
);
}
}
}
+266
View File
@@ -0,0 +1,266 @@
// Copyright 2019-2023 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 alloc::vec;
use alloc::vec::Vec;
use codec::{Compact, Input};
use core::marker::PhantomData;
use scale_bits::{
scale::format::{Format, OrderFormat, StoreFormat},
Bits,
};
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);
}
}
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use scale_decode::DecodeAsType;
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,
DecodeAsType,
EncodeAsType,
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())
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2019-2023 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;
pub mod bits;
mod era;
mod multi_address;
mod multi_signature;
mod static_type;
mod unchecked_extrinsic;
mod wrapper_opaque;
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
pub use account_id::AccountId32;
pub use era::Era;
pub use multi_address::MultiAddress;
pub use multi_signature::MultiSignature;
pub use static_type::Static;
pub use unchecked_extrinsic::UncheckedExtrinsic;
pub use wrapper_opaque::WrapperKeepOpaque;
// Used in codegen
#[doc(hidden)]
pub use primitive_types::{H160, H256, H512};
/// 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 unit marker struct signalling that some property is true
pub struct Yes;
/// 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()))
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2019-2023 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 alloc::vec::Vec;
use codec::{Decode, Encode};
/// A multi-format address wrapper for on-chain accounts. This is a simplified version of Substrate's
/// `sp_runtime::MultiAddress`. To obtain more functionality, convert this into that type (this conversion
/// functionality is provided via `From` impls if the `substrate-compat` feature is enabled).
#[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)
}
}
// Improve compat with the substrate version if we're using those crates:
#[cfg(feature = "substrate-compat")]
mod substrate_impls {
use super::{super::AccountId32, *};
impl<N> From<sp_runtime::AccountId32> for MultiAddress<AccountId32, N> {
fn from(value: sp_runtime::AccountId32) -> Self {
let val: AccountId32 = value.into();
val.into()
}
}
impl<Id, N> From<sp_runtime::MultiAddress<Id, N>> for MultiAddress<AccountId32, N>
where
Id: Into<AccountId32>,
{
fn from(value: sp_runtime::MultiAddress<Id, N>) -> Self {
match value {
sp_runtime::MultiAddress::Id(v) => Self::Id(v.into()),
sp_runtime::MultiAddress::Index(v) => Self::Index(v),
sp_runtime::MultiAddress::Raw(v) => Self::Raw(v),
sp_runtime::MultiAddress::Address32(v) => Self::Address32(v),
sp_runtime::MultiAddress::Address20(v) => Self::Address20(v),
}
}
}
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2019-2023 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]),
}
// Improve compat with the substrate version if we're using those crates:
#[cfg(feature = "substrate-compat")]
mod substrate_impls {
use super::*;
impl From<sp_runtime::MultiSignature> for MultiSignature {
fn from(value: sp_runtime::MultiSignature) -> Self {
match value {
sp_runtime::MultiSignature::Ed25519(s) => Self::Ed25519(s.0),
sp_runtime::MultiSignature::Sr25519(s) => Self::Sr25519(s.0),
sp_runtime::MultiSignature::Ecdsa(s) => Self::Ecdsa(s.0),
}
}
}
impl From<sp_core::ed25519::Signature> for MultiSignature {
fn from(value: sp_core::ed25519::Signature) -> Self {
let sig: sp_runtime::MultiSignature = value.into();
sig.into()
}
}
impl From<sp_core::sr25519::Signature> for MultiSignature {
fn from(value: sp_core::sr25519::Signature) -> Self {
let sig: sp_runtime::MultiSignature = value.into();
sig.into()
}
}
impl From<sp_core::ecdsa::Signature> for MultiSignature {
fn from(value: sp_core::ecdsa::Signature) -> Self {
let sig: sp_runtime::MultiSignature = value.into();
sig.into()
}
}
}
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2019-2023 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::{visitor::DecodeAsTypeResult, IntoVisitor, TypeResolver, Visitor};
use scale_encode::EncodeAsType;
use alloc::vec::Vec;
/// 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::{visitor::DecodeError, Error};
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
}
}
+142
View File
@@ -0,0 +1,142 @@
// Copyright 2019-2023 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::{visitor::DecodeAsTypeResult, DecodeAsType, IntoVisitor, TypeResolver, Visitor};
use super::{Encoded, Static};
use alloc::vec::Vec;
/// 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::*;
use alloc::vec;
#[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);
}
}
+231
View File
@@ -0,0 +1,231 @@
// Copyright 2019-2023 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::{ext::scale_type_resolver::visitor, IntoVisitor, TypeResolver, Visitor};
use scale_encode::EncodeAsType;
use alloc::format;
use alloc::vec::Vec;
/// 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 visitor = visitor::new(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(|out, _fields| {
self.data.encode_to(out);
Ok(())
});
types
.resolve_type(type_id, 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};
// TODO: When `scale-type-resolver` [provides struct names](https://github.com/paritytech/scale-type-resolver/issues/4), check that this struct name is `WrapperKeepOpaque`
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 alloc::vec;
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::{build::Fields, meta_type, Path, Type, TypeParameter};
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]));
}
}