diff --git a/Cargo.lock b/Cargo.lock index 1cb864285b..85bcecf80f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4533,6 +4533,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "subxt-core" +version = "0.34.0" +dependencies = [ + "base58", + "bitvec", + "blake2", + "cfg-if", + "derivative", + "derive_more", + "frame-metadata 16.0.0", + "hex", + "impl-serde", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-core", + "sp-core-hashing", + "sp-keyring", + "sp-runtime", + "subxt-metadata", + "url", +] + [[package]] name = "subxt-lightclient" version = "0.34.0" diff --git a/Cargo.toml b/Cargo.toml index 41241ebe0c..9608f9176f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "cli", "codegen", + "core", "lightclient", "testing/substrate-runner", "testing/test-runtime", @@ -82,10 +83,10 @@ proc-macro2 = "1.0.78" quote = "1.0.35" regex = "1.10.3" scale-info = { version = "2.10.0", default-features = false } -scale-value = "0.13.0" -scale-bits = "0.4.0" -scale-decode = "0.10.0" -scale-encode = "0.5.0" +scale-value = { version = "0.13.0", default-features = false } +scale-bits = { version = "0.4.0", default-features = false } +scale-decode = { version = "0.10.0", default-features = false } +scale-encode = { version = "0.5.0", default-features = false } serde = { version = "1.0.196" } serde_json = { version = "1.0.113" } syn = { version = "2.0.15", features = ["full", "extra-traits"] } diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000000..98947ffc65 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "subxt-core" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +publish = true + +license.workspace = true +readme = "README.md" +repository.workspace = true +documentation.workspace = true +homepage.workspace = true +description = "Sign extrinsics to be submitted by Subxt" +keywords = ["parity", "subxt", "extrinsic", "no-std"] + +[features] +# default = ["std"] +std = [] +substrate-compat = ["sp-core", "sp-runtime"] + +[dependencies] + +codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] } +scale-info = { workspace = true, default-features = false, features = ["bit-vec"] } +scale-value = { workspace = true, default-features = false } +scale-bits = { workspace = true, default-features = false } +scale-decode = { workspace = true, default-features = false, features = ["derive", "primitive-types"] } +scale-encode = { workspace = true, default-features = false, features = ["derive", "primitive-types", "bits"] } +frame-metadata = { workspace = true, default-features = false } +subxt-metadata = { workspace = true, default-features = false } +derivative = { workspace = true, features = ["use_core"] } +derive_more = { workspace = true } +hex = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["raw_value"] } + +# For ss58 encoding AccountId32 to serialize them properly: +base58 = { workspace = true } +blake2 = { workspace = true } + +# Provides some deserialization, types like U256/H256 and hashing impls like twox/blake256: +impl-serde = { workspace = true } +primitive-types = { workspace = true, default-features = false, features = ["codec", "serde_no_std", "scale-info"] } +sp-core-hashing = { workspace = true } + +# For parsing urls to disallow insecure schemes +url = { workspace = true } + +# Included if the "substrate-compat" feature is enabled. +sp-core = { workspace = true, optional = true } +sp-runtime = { workspace = true, optional = true } +cfg-if = { workspace = true } + +[dev-dependencies] +bitvec = { workspace = true } +codec = { workspace = true, features = ["derive", "bit-vec"] } +sp-core = { workspace = true } +sp-keyring = { workspace = true } +sp-runtime = { workspace = true } + + +[package.metadata.docs.rs] +defalt-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.playground] +defalt-features = true diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000000..745b0fea12 --- /dev/null +++ b/core/README.md @@ -0,0 +1,3 @@ +# Subxt-Core + +This library provides core functionality using in `subxt` and `subxt-signer`. It should be no-std compatible. \ No newline at end of file diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs new file mode 100644 index 0000000000..919154b836 --- /dev/null +++ b/core/src/client/mod.rs @@ -0,0 +1,51 @@ +use derivative::Derivative; + +use crate::{config::Config, metadata::Metadata}; + +/// the base for a client should be: +/// - runtime version +/// - genesis hash +/// - metadata + +#[derive(Derivative)] +#[derivative(Debug(bound = ""))] +pub struct MinimalClient { + pub genesis_hash: C::Hash, + pub runtime_version: RuntimeVersion, + pub metadata: Metadata, + marker: core::marker::PhantomData, +} + +impl MinimalClient { + 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 { + /// Version of the runtime specification. A full-node will not attempt to use its native + /// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`, + /// `spec_version` and `authoring_version` are the same between Wasm and native. + pub spec_version: u32, + + /// All existing dispatches are fully compatible when this number doesn't change. If this + /// number changes, then `spec_version` must change, also. + /// + /// This number must change when an existing dispatchable (module ID, dispatch ID) is changed, + /// either through an alteration in its user-level semantics, a parameter + /// added/removed/changed, a dispatchable being removed, a module being removed, or a + /// dispatchable/module changing its index. + /// + /// It need *not* change when a new module is added or when a dispatchable is added. + pub transaction_version: u32, +} diff --git a/core/src/config/default_extrinsic_params.rs b/core/src/config/default_extrinsic_params.rs new file mode 100644 index 0000000000..dce83853bf --- /dev/null +++ b/core/src/config/default_extrinsic_params.rs @@ -0,0 +1,144 @@ +// 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, 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 = signed_extensions::AnyOf< + T, + ( + signed_extensions::CheckSpecVersion, + signed_extensions::CheckTxVersion, + signed_extensions::CheckNonce, + signed_extensions::CheckGenesis, + signed_extensions::CheckMortality, + signed_extensions::ChargeAssetTxPayment, + signed_extensions::ChargeTransactionPayment, + ), +>; + +/// A builder that outputs the set of [`super::ExtrinsicParams::OtherParams`] 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 { + /// `None` means the tx will be immortal. + mortality: Option>, + /// `None` means we'll use the native token. + tip_of_asset_id: Option, + tip: u128, + tip_of: u128, +} + +struct Mortality { + /// 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 Default for DefaultExtrinsicParamsBuilder { + fn default() -> Self { + Self { + mortality: None, + tip: 0, + tip_of: 0, + tip_of_asset_id: None, + } + } +} + +impl DefaultExtrinsicParamsBuilder { + /// 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 + } + + /// 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) -> as ExtrinsicParams>::OtherParams { + 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); + + ( + (), + (), + (), + (), + check_mortality_params, + charge_asset_tx_params, + charge_transaction_params, + ) + } +} diff --git a/core/src/config/extrinsic_params.rs b/core/src/config/extrinsic_params.rs new file mode 100644 index 0000000000..79d7837e5b --- /dev/null +++ b/core/src/config/extrinsic_params.rs @@ -0,0 +1,94 @@ +// 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 crate::client::MinimalClient; + +use super::Config; +use crate::prelude::*; +use core::fmt::Debug; +use string::String; +use vec::Vec; + +use derive_more::Display; + +/// An error that can be emitted when trying to construct an instance of [`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}")] + #[cfg(feature = "std")] + Custom(CustomExtrinsicParamsError), +} + +/// A custom error. +#[cfg(feature = "std")] +pub type CustomExtrinsicParamsError = Box; + +impl From for ExtrinsicParamsError { + fn from(value: core::convert::Infallible) -> Self { + match value {} + } +} + +#[cfg(feature = "std")] +impl From for ExtrinsicParamsError { + fn from(value: CustomExtrinsicParamsError) -> Self { + ExtrinsicParamsError::Custom(value) + } +} + +/// 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: 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 OtherParams; + + /// Construct a new instance of our [`ExtrinsicParams`]. + fn new( + nonce: u64, + client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result; +} + +/// 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) {} + + /// 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) {} +} diff --git a/core/src/config/mod.rs b/core/src/config/mod.rs new file mode 100644 index 0000000000..f14849df6e --- /dev/null +++ b/core/src/config/mod.rs @@ -0,0 +1,154 @@ +// 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 a [`Config`] type, which is used to define various +//! types that are important in order to speak to a particular chain. +//! [`SubstrateConfig`] provides a default set of these types suitable for the +//! default Substrate node implementation, and [`PolkadotConfig`] for a +//! Polkadot node. + +mod default_extrinsic_params; +mod extrinsic_params; + +pub mod polkadot; +pub mod 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, ExtrinsicParamsError}; +pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder}; +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; + + /// The signature type. + type Signature: Debug + Encode; + + /// The hashing system (algorithm) being used in the runtime (e.g. Blake2). + type Hasher: Debug + Hasher; + + /// The block header. + type Header: Debug + Header + Sync + Send + DeserializeOwned; + + /// This type defines the extrinsic extra and additional parameters. + type ExtrinsicParams: ExtrinsicParams; + + /// 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 OtherParamsFor = <::ExtrinsicParams as ExtrinsicParams>::OtherParams; + +/// 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 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: &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; + /// 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) -> ::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 Header for T + where + ::Number: Into, + { + type Number = T::Number; + type Hasher = T::Hashing; + + fn number(&self) -> Self::Number { + *self.number() + } + } + + impl Hasher for T { + type Output = T::Output; + + fn hash(s: &[u8]) -> Self::Output { + ::hash(s) + } + } + } +} diff --git a/core/src/config/polkadot.rs b/core/src/config/polkadot.rs new file mode 100644 index 0000000000..04c3d70f27 --- /dev/null +++ b/core/src/config/polkadot.rs @@ -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 super::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 = ::Hash; + type AccountId = ::AccountId; + type Address = MultiAddress; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = PolkadotExtrinsicParams; + type AssetId = u32; +} + +/// A struct representing the signed extra and additional parameters required +/// to construct a transaction for a polkadot node. +pub type PolkadotExtrinsicParams = DefaultExtrinsicParams; + +/// A builder which leads to [`PolkadotExtrinsicParams`] being constructed. +/// This is what you provide to methods like `sign_and_submit()`. +pub type PolkadotExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; diff --git a/core/src/config/signed_extensions.rs b/core/src/config/signed_extensions.rs new file mode 100644 index 0000000000..9d05c4cb53 --- /dev/null +++ b/core/src/config/signed_extensions.rs @@ -0,0 +1,500 @@ +// 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, ExtrinsicParamsEncoder, ExtrinsicParamsError}; +use super::Config; +use crate::client::MinimalClient; +use crate::prelude::*; +use crate::utils::Era; +use borrow::ToOwned; +use boxed::Box; +use codec::{Compact, Encode}; +use collections::BTreeMap; +use core::fmt::Debug; +use derivative::Derivative; +use scale_decode::DecodeAsType; +use scale_info::PortableRegistry; +use vec::Vec; + +/// 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: ExtrinsicParams { + /// 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 ExtrinsicParams for CheckSpecVersion { + type OtherParams = (); + + fn new( + _nonce: u64, + client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckSpecVersion(client.runtime_version().spec_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckSpecVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckSpecVersion { + type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckSpecVersion" + } +} + +/// The [`CheckNonce`] signed extension. +pub struct CheckNonce(Compact); + +impl ExtrinsicParams for CheckNonce { + type OtherParams = (); + + fn new( + nonce: u64, + _client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckNonce(Compact(nonce))) + } +} + +impl ExtrinsicParamsEncoder for CheckNonce { + fn encode_extra_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckNonce { + type Decoded = u64; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckNonce" + } +} + +/// The [`CheckTxVersion`] signed extension. +pub struct CheckTxVersion(u32); + +impl ExtrinsicParams for CheckTxVersion { + type OtherParams = (); + + fn new( + _nonce: u64, + client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckTxVersion(client.runtime_version().transaction_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckTxVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckTxVersion { + type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckTxVersion" + } +} + +/// The [`CheckGenesis`] signed extension. +pub struct CheckGenesis(T::Hash); + +impl ExtrinsicParams for CheckGenesis { + type OtherParams = (); + + fn new( + _nonce: u64, + client: &MinimalClient, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckGenesis(client.genesis_hash())) + } +} + +impl ExtrinsicParamsEncoder for CheckGenesis { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckGenesis { + type Decoded = (); + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckGenesis" + } +} + +/// The [`CheckMortality`] signed extension. +pub struct CheckMortality { + era: Era, + checkpoint: T::Hash, +} + +/// Parameters to configure the [`CheckMortality`] signed extension. +pub struct CheckMortalityParams { + era: Era, + checkpoint: Option, +} + +impl Default for CheckMortalityParams { + fn default() -> Self { + Self { + era: Default::default(), + checkpoint: Default::default(), + } + } +} + +impl CheckMortalityParams { + /// 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 { + CheckMortalityParams { + era: Era::mortal(period, block_number), + checkpoint: Some(block_hash), + } + } + /// An immortal transaction. + pub fn immortal() -> Self { + CheckMortalityParams { + era: Era::Immortal, + checkpoint: None, + } + } +} + +impl ExtrinsicParams for CheckMortality { + type OtherParams = CheckMortalityParams; + + fn new( + _nonce: u64, + client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + Ok(CheckMortality { + era: other_params.era, + checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()), + }) + } +} + +impl ExtrinsicParamsEncoder for CheckMortality { + fn encode_extra_to(&self, v: &mut Vec) { + self.era.encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + self.checkpoint.encode_to(v); + } +} + +impl SignedExtension for CheckMortality { + type Decoded = Era; + fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool { + identifier == "CheckMortality" + } +} + +/// The [`ChargeAssetTxPayment`] signed extension. +#[derive(Derivative, DecodeAsType)] +#[derivative(Clone(bound = "T::AssetId: Clone"), Debug(bound = "T::AssetId: Debug"))] +#[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")] +pub struct ChargeAssetTxPayment { + tip: Compact, + asset_id: Option, +} + +impl ChargeAssetTxPayment { + /// 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 { + tip: u128, + asset_id: Option, +} + +impl Default for ChargeAssetTxPaymentParams { + fn default() -> Self { + ChargeAssetTxPaymentParams { + tip: Default::default(), + asset_id: Default::default(), + } + } +} + +impl ChargeAssetTxPaymentParams { + /// 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 ExtrinsicParams for ChargeAssetTxPayment { + type OtherParams = ChargeAssetTxPaymentParams; + + fn new( + _nonce: u64, + _client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + Ok(ChargeAssetTxPayment { + tip: Compact(other_params.tip), + asset_id: other_params.asset_id, + }) + } +} + +impl ExtrinsicParamsEncoder for ChargeAssetTxPayment { + fn encode_extra_to(&self, v: &mut Vec) { + (self.tip, &self.asset_id).encode_to(v); + } +} + +impl SignedExtension for ChargeAssetTxPayment { + 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, +} + +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 ExtrinsicParams for ChargeTransactionPayment { + type OtherParams = ChargeTransactionPaymentParams; + + fn new( + _nonce: u64, + _client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + Ok(ChargeTransactionPayment { + tip: Compact(other_params.tip), + }) + } +} + +impl ExtrinsicParamsEncoder for ChargeTransactionPayment { + fn encode_extra_to(&self, v: &mut Vec) { + self.tip.encode_to(v); + } +} + +impl SignedExtension 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 { + params: Vec>, + _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 ExtrinsicParams for AnyOf + where + T: Config, + $($ident: SignedExtension,)+ + { + type OtherParams = ($($ident::OtherParams,)+); + + fn new( + nonce: u64, + client: &MinimalClient, + other_params: Self::OtherParams, + ) -> Result { + 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 = BTreeMap::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(nonce, client, other_params.$index)?; + let boxed_ext: Box = 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 ExtrinsicParamsEncoder for AnyOf + where + T: Config, + $($ident: SignedExtension,)+ + { + fn encode_extra_to(&self, v: &mut Vec) { + for ext in &self.params { + ext.encode_extra_to(v); + } + } + fn encode_additional_to(&self, v: &mut Vec) { + 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, + } +} diff --git a/core/src/config/substrate.rs b/core/src/config/substrate.rs new file mode 100644 index 0000000000..b8e930c882 --- /dev/null +++ b/core/src/config/substrate.rs @@ -0,0 +1,342 @@ +// 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 crate::prelude::*; +pub use crate::utils::{AccountId32, MultiAddress, MultiSignature}; +use codec::{Decode, Encode}; +pub use primitive_types::{H256, U256}; +use serde::{Deserialize, Serialize}; +use string::String; +use vec::Vec; + +/// 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; + type Signature = MultiSignature; + type Hasher = BlakeTwo256; + type Header = SubstrateHeader; + type ExtrinsicParams = SubstrateExtrinsicParams; + 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 = DefaultExtrinsicParams; + +/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed. +/// This is what you provide to methods like `sign_and_submit()`. +pub type SubstrateExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; + +/// 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_core_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 + TryFrom, 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 Header for SubstrateHeader +where + N: Copy + Into + Into + TryFrom + Encode, + H: Hasher + Encode, + SubstrateHeader: 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, +} + +/// 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), + + /// 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), + + /// Put a Seal on it. This is only used by native code, and is never seen + /// by runtimes. + Seal(ConsensusEngineId, Vec), + + /// Some other thing. Unsupported and experimental. + Other(Vec), + + /// 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 { + 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(input: &mut I) -> Result { + let item_type: DigestItemType = Decode::decode(input)?; + match item_type { + DigestItemType::PreRuntime => { + let vals: (ConsensusEngineId, Vec) = Decode::decode(input)?; + Ok(Self::PreRuntime(vals.0, vals.1)) + } + DigestItemType::Consensus => { + let vals: (ConsensusEngineId, Vec) = Decode::decode(input)?; + Ok(Self::Consensus(vals.0, vals.1)) + } + DigestItemType::Seal => { + let vals: (ConsensusEngineId, Vec) = 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(&self, seq: S) -> Result + where + S: serde::Serializer, + { + self.using_encoded(|bytes| impl_serde::serialize::serialize(bytes, seq)) + } +} + +impl<'a> serde::Deserialize<'a> for DigestItem { + fn deserialize(de: D) -> Result + 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>(val: &T, s: S) -> Result +where + S: serde::Serializer, +{ + let u256: U256 = (*val).into(); + serde::Serialize::serialize(&u256, s) +} + +fn deserialize_number<'a, D, T: TryFrom>(d: D) -> Result +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 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 for NumberOrHex { + fn from(n: u128) -> Self { + NumberOrHex::Hex(n.into()) + } +} + +impl From for NumberOrHex { + fn from(n: U256) -> Self { + NumberOrHex::Hex(n) + } +} + +/// A quick helper to encode some bytes to hex. +fn to_hex(bytes: impl AsRef<[u8]>) -> String { + format!("0x{}", hex::encode(bytes.as_ref())) +} + +#[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 = + 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 = + serde_json::from_str(numeric_block_number_json).expect("valid block header"); + assert_eq!(header.number(), 4); + } +} diff --git a/core/src/dynamic.rs b/core/src/dynamic.rs new file mode 100644 index 0000000000..e0260b1920 --- /dev/null +++ b/core/src/dynamic.rs @@ -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 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; + +// // 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, +// } + +// impl DecodeWithMetadata for DecodedValueThunk { +// fn decode_with_metadata( +// bytes: &mut &[u8], +// type_id: u32, +// metadata: &Metadata, +// ) -> Result { +// 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 { +// 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 { +// let val = DecodedValue::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(&self) -> Result { +// T::decode_as_type( +// &mut &self.scale_bytes[..], +// self.type_id, +// self.metadata.types(), +// ) +// } +// } diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000000..fe618ae167 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,25 @@ +// 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. + +//! # Subxt-core +//! +//! `#[no_std]` compatible core crate for subxt. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod client; +pub mod config; +pub mod dynamic; +pub mod metadata; +pub mod prelude; +pub mod tx; +pub mod utils; + +pub use config::{ + BlockHash, Config, ExtrinsicParams, ExtrinsicParamsEncoder, PolkadotConfig, + PolkadotExtrinsicParams, SubstrateConfig, SubstrateExtrinsicParams, +}; + +#[macro_use] +mod macros; diff --git a/core/src/macros.rs b/core/src/macros.rs new file mode 100644 index 0000000000..1a61ee9354 --- /dev/null +++ b/core/src/macros.rs @@ -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}; diff --git a/core/src/metadata/decode_encode_traits.rs b/core/src/metadata/decode_encode_traits.rs new file mode 100644 index 0000000000..4b9089a001 --- /dev/null +++ b/core/src/metadata/decode_encode_traits.rs @@ -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 crate::prelude::*; +use 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; +} + +impl DecodeWithMetadata for T { + fn decode_with_metadata( + bytes: &mut &[u8], + type_id: u32, + metadata: &Metadata, + ) -> Result { + 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, + ) -> Result<(), scale_encode::Error>; +} + +impl 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, + ) -> Result<(), scale_encode::Error> { + self.encode_as_type_to(type_id, metadata.types(), bytes)?; + Ok(()) + } +} diff --git a/core/src/metadata/metadata_type.rs b/core/src/metadata/metadata_type.rs new file mode 100644 index 0000000000..24b052c2ad --- /dev/null +++ b/core/src/metadata/metadata_type.rs @@ -0,0 +1,127 @@ +// 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::prelude::*; +use borrow::ToOwned; +use derive_more::Display; +use string::String; +use sync::Arc; + +/// A cheaply clone-able representation of the runtime metadata received from a node. +#[derive(Clone, Debug)] +pub struct Metadata { + inner: Arc, +} + +impl core::ops::Deref for Metadata { + type Target = subxt_metadata::Metadata; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Metadata { + pub(crate) 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 { + 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 { + 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 { + self.runtime_api_trait_by_name(name) + .ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned())) + } +} + +impl From for Metadata { + fn from(md: subxt_metadata::Metadata) -> Self { + Metadata::new(md) + } +} + +impl TryFrom for Metadata { + type Error = subxt_metadata::TryFromError; + fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result { + subxt_metadata::Metadata::try_from(value).map(Metadata::from) + } +} + +impl codec::Decode for Metadata { + fn decode(input: &mut I) -> Result { + subxt_metadata::Metadata::decode(input).map(Metadata::new) + } +} + +/// 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), +} diff --git a/core/src/metadata/mod.rs b/core/src/metadata/mod.rs new file mode 100644 index 0000000000..a6ef00ae6f --- /dev/null +++ b/core/src/metadata/mod.rs @@ -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; + +// Expose metadata types under a sub module in case somebody needs to reference them: +pub use subxt_metadata as types; diff --git a/core/src/prelude.rs b/core/src/prelude.rs new file mode 100644 index 0000000000..007bd44665 --- /dev/null +++ b/core/src/prelude.rs @@ -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. + +#[cfg(not(feature = "std"))] +extern crate alloc; + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "std")] { + #[allow(unused)] + pub use std::{ + any, + borrow, + boxed, + cmp, + collections, + fmt, + format, + hash, + marker, + mem, + num, + ops, + string, + sync, + time, + vec, + rc, + iter, + }; + } else { + #[allow(unused)] + pub use alloc::{ + borrow, + boxed, + collections, + format, + string, + sync, + vec, + rc + }; + #[allow(unused)] + pub use core::{ + any, + cmp, + fmt, + hash, + marker, + mem, + num, + ops, + time, + iter, + }; + } +} diff --git a/core/src/tx/mod.rs b/core/src/tx/mod.rs new file mode 100644 index 0000000000..293cbd33c1 --- /dev/null +++ b/core/src/tx/mod.rs @@ -0,0 +1,2 @@ +pub mod signer; +pub mod tx_payload; diff --git a/core/src/tx/signer.rs b/core/src/tx/signer.rs new file mode 100644 index 0000000000..444aa46477 --- /dev/null +++ b/core/src/tx/signer.rs @@ -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 { + /// 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 { + account_id: T::AccountId, + signer: Pair, + } + + impl PairSigner + 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. + ::Signer: From, + T::AccountId: From, + { + /// Creates a new [`Signer`] from an [`sp_core::Pair`]. + pub fn new(signer: Pair) -> Self { + let account_id = + ::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 Signer for PairSigner + where + T: Config, + Pair: PairT, + Pair::Signature: Into, + { + 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() + } + } +} diff --git a/core/src/tx/tx_payload.rs b/core/src/tx/tx_payload.rs new file mode 100644 index 0000000000..6b65d8e434 --- /dev/null +++ b/core/src/tx/tx_payload.rs @@ -0,0 +1,192 @@ +// // 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::{ +// dynamic::Value, +// error::{Error, MetadataError}, +// metadata::Metadata, +// }; +// use codec::Encode; +// use scale_encode::EncodeAsFields; +// use scale_value::{Composite, ValueDef, Variant}; +// use core::{borrow::Cow, sync::Arc}; + +// /// 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) -> 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, 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> { +// 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)] +// pub struct Payload { +// pallet_name: Cow<'static, str>, +// call_name: Cow<'static, str>, +// call_data: CallData, +// validation_hash: Option<[u8; 32]>, +// } + +// /// A boxed transaction payload. +// // Dev Note: Arc used to enable easy cloning (given that we can't have dyn Clone). +// pub type BoxedPayload = Payload>; + +// /// The type of a payload typically used for dynamic transaction payloads. +// pub type DynamicPayload = Payload>; + +// impl Payload { +// /// Create a new [`Payload`]. +// pub fn new( +// pallet_name: impl Into, +// call_name: impl Into, +// 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), +// } +// } + +// /// Box the payload. +// pub fn boxed(self) -> BoxedPayload +// where +// CallData: EncodeAsFields + Send + Sync + 'static, +// { +// BoxedPayload { +// pallet_name: self.pallet_name, +// call_name: self.call_name, +// call_data: Arc::new(self.call_data), +// validation_hash: self.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> { +// /// 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 TxPayload for Payload { +// fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> 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)?; +// Ok(()) +// } + +// fn validation_details(&self) -> Option> { +// 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, +// call_name: impl Into, +// call_data: impl Into>, +// ) -> DynamicPayload { +// Payload::new(pallet_name, call_name, call_data.into()) +// } diff --git a/core/src/utils/account_id.rs b/core/src/utils/account_id.rs new file mode 100644 index 0000000000..7a7ef7949f --- /dev/null +++ b/core/src/utils/account_id.rs @@ -0,0 +1,225 @@ +// 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 crate::prelude::*; +use codec::{Decode, Encode}; +use derive_more::Display; +use serde::{Deserialize, Serialize}; +use string::String; +use vec::Vec; + +/// 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 { + 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(Display, Clone, Copy, Eq, PartialEq, Debug)] +#[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 { + 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(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_ss58check()) + } +} + +impl<'de> Deserialize<'de> for AccountId32 { + fn deserialize(deserializer: D) -> Result + 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 { + 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 for AccountId32 { + fn from(value: sp_runtime::AccountId32) -> Self { + Self(value.into()) + } + } + impl From for AccountId32 { + fn from(value: sp_core::sr25519::Public) -> Self { + let acc: sp_runtime::AccountId32 = value.into(); + acc.into() + } + } + impl From 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 + ); + } + } +} diff --git a/core/src/utils/bits.rs b/core/src/utils/bits.rs new file mode 100644 index 0000000000..5774cd9fcf --- /dev/null +++ b/core/src/utils/bits.rs @@ -0,0 +1,268 @@ +// 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 crate::prelude::*; +use codec::{Compact, Input}; +use core::marker::PhantomData; +use scale_bits::{ + scale::format::{Format, OrderFormat, StoreFormat}, + Bits, +}; +use scale_decode::IntoVisitor; +use vec::Vec; + +/// 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() -> 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 { + bits: Bits, + _marker: PhantomData<(Store, Order)>, +} + +impl DecodedBits { + /// 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 core::iter::FromIterator for DecodedBits { + fn from_iter>(iter: T) -> Self { + DecodedBits { + bits: Bits::from_iter(iter), + _marker: PhantomData, + } + } +} + +impl codec::Decode for DecodedBits { + fn decode(input: &mut I) -> Result { + /// Equivalent of `BitSlice::MAX_BITS` on 32bit machine. + const ARCH32BIT_BITSLICE_MAX_BITS: u32 = 0x1fff_ffff; + + let Compact(bits) = >::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 ) + // 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::())?; + let bits = decoder.collect::, _>>()?; + let bits = Bits::from_iter(bits); + + Ok(DecodedBits { + bits, + _marker: PhantomData, + }) + } +} + +impl codec::Encode for DecodedBits { + fn size_hint(&self) -> usize { + self.bits.size_hint() + } + + fn encoded_size(&self) -> usize { + self.bits.encoded_size() + } + + fn encode(&self) -> Vec { + scale_bits::encode_using_format(self.bits.iter(), bit_format::()) + } +} + +#[doc(hidden)] +pub struct DecodedBitsVisitor(core::marker::PhantomData<(S, O)>); +impl scale_decode::Visitor for DecodedBitsVisitor { + type Value<'scale, 'info> = DecodedBits; + type Error = scale_decode::Error; + + fn unchecked_decode_as_type<'scale, 'info>( + self, + input: &mut &'scale [u8], + type_id: scale_decode::visitor::TypeId, + types: &'info scale_info::PortableRegistry, + ) -> scale_decode::visitor::DecodeAsTypeResult< + Self, + Result, Self::Error>, + > { + let res = scale_decode::visitor::decode_with_visitor( + input, + type_id.0, + types, + Bits::into_visitor(), + ) + .map(|bits| DecodedBits { + bits, + _marker: PhantomData, + }); + scale_decode::visitor::DecodeAsTypeResult::Decoded(res) + } +} +impl scale_decode::IntoVisitor for DecodedBits { + type Visitor = DecodedBitsVisitor; + fn into_visitor() -> Self::Visitor { + DecodedBitsVisitor(PhantomData) + } +} + +impl scale_encode::EncodeAsType for DecodedBits { + fn encode_as_type_to( + &self, + type_id: u32, + types: &scale_info::PortableRegistry, + out: &mut Vec, + ) -> 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, + ) where + BitVec::Order>: codec::Encode + codec::Decode, + { + let input: Vec<_> = input.into_iter().copied().collect(); + + let decoded_bits = DecodedBits::::from_iter(input.clone()); + let bitvec = BitVec::::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::::decode(&mut &decoded_bits_encoded[..]) + .expect("SCALE-encoding DecodedBits to roundtrip"); + let bitvec_decoded = + BitVec::::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::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + scales_like_bitvec_and_roundtrips::(test_case); + } + } +} diff --git a/core/src/utils/era.rs b/core/src/utils/era.rs new file mode 100644 index 0000000000..c98ebe58f6 --- /dev/null +++ b/core/src/utils/era.rs @@ -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(&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(input: &mut I) -> Result { + 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()) + } + } + } +} diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs new file mode 100644 index 0000000000..f8e74fafbb --- /dev/null +++ b/core/src/utils/mod.rs @@ -0,0 +1,99 @@ +// 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 codec::{Compact, Decode, Encode}; +use derivative::Derivative; +use url::Url; + +pub use account_id::AccountId32; +use borrow::ToOwned; +pub use era::Era; +pub use multi_address::MultiAddress; +pub use multi_signature::MultiSignature; +pub use static_type::Static; +pub use unchecked_extrinsic::UncheckedExtrinsic; +use vec::Vec; +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)] +pub struct Encoded(pub Vec); + +impl codec::Encode for Encoded { + fn encode(&self) -> Vec { + self.0.to_owned() + } +} + +/// Decodes a compact encoded value from the beginning of the provided bytes, +/// returning the value and any remaining bytes. +pub(crate) fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> { + let cursor = &mut &*bytes; + let val = >::decode(cursor)?; + Ok((val.0, *cursor)) +} + +/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost. +/// +/// Returns an error if the the string could not be parsed into a URL. +pub fn url_is_secure(url: &str) -> Result { + let url = Url::parse(url)?; + + let secure_scheme = url.scheme() == "https" || url.scheme() == "wss"; + let is_localhost = url.host().is_some_and(|e| match e { + url::Host::Domain(e) => e == "localhost", + url::Host::Ipv4(e) => e.is_loopback(), + url::Host::Ipv6(e) => e.is_loopback(), + }); + + Ok(secure_scheme || is_localhost) +} + +use crate::prelude::*; + +/// 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(Derivative, Encode, Decode, scale_info::TypeInfo)] +#[derivative( + Clone(bound = ""), + PartialEq(bound = ""), + Debug(bound = ""), + Eq(bound = ""), + Default(bound = ""), + Hash(bound = "") +)] +#[scale_info(skip_type_params(T))] +#[doc(hidden)] +pub struct PhantomDataSendSync(core::marker::PhantomData); + +impl PhantomDataSendSync { + pub(crate) fn new() -> Self { + Self(core::marker::PhantomData) + } +} + +unsafe impl Send for PhantomDataSendSync {} +unsafe impl Sync for PhantomDataSendSync {} + +/// 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 = Vec<(K, V)>; diff --git a/core/src/utils/multi_address.rs b/core/src/utils/multi_address.rs new file mode 100644 index 0000000000..c742c34937 --- /dev/null +++ b/core/src/utils/multi_address.rs @@ -0,0 +1,74 @@ +// 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 crate::prelude::*; +use codec::{Decode, Encode}; +use vec::Vec; + +/// 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 { + /// 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), + /// It's a 32 byte representation. + Address32([u8; 32]), + /// Its a 20 byte representation. + Address20([u8; 20]), +} + +impl From for MultiAddress { + 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 From for MultiAddress { + fn from(value: sp_runtime::AccountId32) -> Self { + let val: AccountId32 = value.into(); + val.into() + } + } + + impl From> for MultiAddress + where + Id: Into, + { + fn from(value: sp_runtime::MultiAddress) -> 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), + } + } + } +} diff --git a/core/src/utils/multi_signature.rs b/core/src/utils/multi_signature.rs new file mode 100644 index 0000000000..4ed9ea4bd5 --- /dev/null +++ b/core/src/utils/multi_signature.rs @@ -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 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 for MultiSignature { + fn from(value: sp_core::ed25519::Signature) -> Self { + let sig: sp_runtime::MultiSignature = value.into(); + sig.into() + } + } + + impl From for MultiSignature { + fn from(value: sp_core::sr25519::Signature) -> Self { + let sig: sp_runtime::MultiSignature = value.into(); + sig.into() + } + } + + impl From for MultiSignature { + fn from(value: sp_core::ecdsa::Signature) -> Self { + let sig: sp_runtime::MultiSignature = value.into(); + sig.into() + } + } +} diff --git a/core/src/utils/static_type.rs b/core/src/utils/static_type.rs new file mode 100644 index 0000000000..9484706b85 --- /dev/null +++ b/core/src/utils/static_type.rs @@ -0,0 +1,80 @@ +// 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::prelude::*; +use codec::{Decode, Encode}; +use scale_decode::{visitor::DecodeAsTypeResult, IntoVisitor, Visitor}; +use scale_encode::EncodeAsType; +use 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(pub T); + +impl EncodeAsType for Static { + fn encode_as_type_to( + &self, + _type_id: u32, + _types: &scale_decode::PortableRegistry, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + self.0.encode_to(out); + Ok(()) + } +} + +pub struct StaticDecodeAsTypeVisitor(core::marker::PhantomData); + +impl Visitor for StaticDecodeAsTypeVisitor { + type Value<'scale, 'info> = Static; + type Error = scale_decode::Error; + + fn unchecked_decode_as_type<'scale, 'info>( + self, + input: &mut &'scale [u8], + _type_id: scale_decode::visitor::TypeId, + _types: &'info scale_info::PortableRegistry, + ) -> DecodeAsTypeResult, 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 IntoVisitor for Static { + type Visitor = StaticDecodeAsTypeVisitor; + fn into_visitor() -> Self::Visitor { + StaticDecodeAsTypeVisitor(core::marker::PhantomData) + } +} + +// Make it easy to convert types into Static where required. +impl From for Static { + fn from(value: T) -> Self { + Static(value) + } +} + +// Static is just a marker type and should be as transparent as possible: +impl core::ops::Deref for Static { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for Static { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/core/src/utils/unchecked_extrinsic.rs b/core/src/utils/unchecked_extrinsic.rs new file mode 100644 index 0000000000..89744ac12a --- /dev/null +++ b/core/src/utils/unchecked_extrinsic.rs @@ -0,0 +1,136 @@ +// 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 super::{Encoded, Static}; +use crate::prelude::*; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType, IntoVisitor, Visitor}; +use vec::Vec; + +/// The unchecked extrinsic from substrate. +#[derive(Clone, Debug, Eq, PartialEq, Encode)] +pub struct UncheckedExtrinsic( + Static, + #[codec(skip)] PhantomData<(Address, Call, Signature, Extra)>, +); + +impl UncheckedExtrinsic { + /// Construct a new [`UncheckedExtrinsic`]. + pub fn new(bytes: Vec) -> 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 Decode + for UncheckedExtrinsic +{ + fn decode(input: &mut I) -> Result { + // 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 = Decode::decode(input)?; + Ok(UncheckedExtrinsic::new(xt_vec)) + } +} + +impl scale_encode::EncodeAsType + for UncheckedExtrinsic +{ + fn encode_as_type_to( + &self, + type_id: u32, + types: &scale_info::PortableRegistry, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + self.0.encode_as_type_to(type_id, types, out) + } +} + +impl From> + for UncheckedExtrinsic +{ + fn from(bytes: Vec) -> Self { + UncheckedExtrinsic::new(bytes) + } +} + +impl From> + for Vec +{ + fn from(bytes: UncheckedExtrinsic) -> Self { + bytes.0 .0 .0 + } +} + +pub struct UncheckedExtrinsicDecodeAsTypeVisitor( + PhantomData<(Address, Call, Signature, Extra)>, +); + +impl Visitor + for UncheckedExtrinsicDecodeAsTypeVisitor +{ + type Value<'scale, 'info> = UncheckedExtrinsic; + type Error = scale_decode::Error; + + fn unchecked_decode_as_type<'scale, 'info>( + self, + input: &mut &'scale [u8], + type_id: scale_decode::visitor::TypeId, + types: &'info scale_info::PortableRegistry, + ) -> DecodeAsTypeResult, Self::Error>> { + DecodeAsTypeResult::Decoded(Self::Value::decode_as_type(input, type_id.0, types)) + } +} + +impl IntoVisitor + for UncheckedExtrinsic +{ + type Visitor = UncheckedExtrinsicDecodeAsTypeVisitor; + + fn into_visitor() -> Self::Visitor { + UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + #[test] + fn unchecked_extrinsic_encoding() { + // A tx is basically some bytes with a compact length prefix; ie an encoded vec: + let tx_bytes = vec![1u8, 2, 3].encode(); + + let unchecked_extrinsic = UncheckedExtrinsic::<(), (), (), ()>::new(tx_bytes.clone()); + let encoded_tx_bytes = unchecked_extrinsic.encode(); + + // The encoded representation must not alter the provided bytes. + assert_eq!(tx_bytes, encoded_tx_bytes); + + // However, for decoding we expect to be able to read the extrinsic from the wire + // which would be length prefixed. + let decoded_tx = UncheckedExtrinsic::<(), (), (), ()>::decode(&mut &tx_bytes[..]).unwrap(); + let decoded_tx_bytes = decoded_tx.bytes(); + let encoded_tx_bytes = decoded_tx.encode(); + + assert_eq!(decoded_tx_bytes, encoded_tx_bytes); + // Ensure we can decode the tx and fetch only the tx bytes. + assert_eq!(vec![1, 2, 3], encoded_tx_bytes); + } +} diff --git a/core/src/utils/wrapper_opaque.rs b/core/src/utils/wrapper_opaque.rs new file mode 100644 index 0000000000..8ca9ca1758 --- /dev/null +++ b/core/src/utils/wrapper_opaque.rs @@ -0,0 +1,245 @@ +// 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 crate::prelude::*; +use codec::{Compact, Decode, DecodeAll, Encode}; +use derivative::Derivative; +use scale_decode::{IntoVisitor, Visitor}; +use scale_encode::EncodeAsType; +use vec::Vec; + +/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec`. +/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec`. 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`, but we need a custom [`EncodeAsType`] +// and [`Visitor`] implementation to encode and decode based on TypeInfo. +#[derive(Derivative, Encode, Decode)] +#[derivative( + Debug(bound = ""), + Clone(bound = ""), + PartialEq(bound = ""), + Eq(bound = ""), + Default(bound = ""), + Hash(bound = "") +)] +pub struct WrapperKeepOpaque { + data: Vec, + _phantom: PhantomDataSendSync, +} + +impl WrapperKeepOpaque { + /// Try to decode the wrapped type from the inner `data`. + /// + /// Returns `None` if the decoding failed. + pub fn try_decode(&self) -> Option + 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) -> 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 EncodeAsType for WrapperKeepOpaque { + fn encode_as_type_to( + &self, + type_id: u32, + types: &scale_info::PortableRegistry, + out: &mut Vec, + ) -> Result<(), scale_encode::Error> { + use scale_encode::error::{Error, ErrorKind, Kind}; + + let Some(ty) = types.resolve(type_id) else { + return Err(Error::new(ErrorKind::TypeNotFound(type_id))); + }; + + // Do a basic check that the target shape lines up. + let scale_info::TypeDef::Composite(_) = &ty.type_def else { + return Err(Error::new(ErrorKind::WrongShape { + actual: Kind::Struct, + expected: type_id, + })); + }; + + // Check that the name also lines up. + if ty.path.ident().as_deref() != Some("WrapperKeepOpaque") { + return Err(Error::new(ErrorKind::WrongShape { + actual: Kind::Struct, + expected: type_id, + })); + } + + // Just blat the bytes out. + self.data.encode_to(out); + Ok(()) + } +} + +pub struct WrapperKeepOpaqueVisitor(core::marker::PhantomData); +impl Visitor for WrapperKeepOpaqueVisitor { + type Value<'scale, 'info> = WrapperKeepOpaque; + type Error = scale_decode::Error; + + fn visit_composite<'scale, 'info>( + self, + value: &mut scale_decode::visitor::types::Composite<'scale, 'info>, + _type_id: scale_decode::visitor::TypeId, + ) -> Result, Self::Error> { + use scale_decode::error::{Error, ErrorKind}; + + if value.path().ident().as_deref() != Some("WrapperKeepOpaque") { + return Err(Error::custom_str( + "Type to decode is not 'WrapperTypeKeepOpaque'", + )); + } + 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::::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 IntoVisitor for WrapperKeepOpaque { + type Visitor = WrapperKeepOpaqueVisitor; + fn into_visitor() -> Self::Visitor { + WrapperKeepOpaqueVisitor(core::marker::PhantomData) + } +} + +#[cfg(test)] +mod test { + use scale_decode::DecodeAsType; + + use super::*; + + // Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs + // and used for tests to check that we can work with the expected TypeInfo without needing to import + // the frame_support crate, which has quite a lot of dependencies. + impl scale_info::TypeInfo for WrapperKeepOpaque { + 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::()))]) + .composite( + Fields::unnamed() + .field(|f| f.compact::()) + .field(|f| f.ty::().type_name("T")), + ) + } + } + + /// Given a type definition, return type ID and registry representing it. + fn make_type() -> (u32, scale_info::PortableRegistry) { + let m = scale_info::MetaType::new::(); + 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) + where + T: EncodeAsType + + DecodeAsType + + Encode + + Decode + + PartialEq + + core::fmt::Debug + + scale_info::TypeInfo + + 'static, + { + let (type_id, types) = make_type::(); + + 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])); + } +} diff --git a/testing/no-std-tests/Cargo.lock b/testing/no-std-tests/Cargo.lock index e1d9e0d46e..ceff2e1339 100644 --- a/testing/no-std-tests/Cargo.lock +++ b/testing/no-std-tests/Cargo.lock @@ -32,6 +32,33 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "blake2b_simd" version = "1.0.2" @@ -91,6 +118,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -101,6 +134,52 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -122,14 +201,59 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "frame-metadata" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "frame-metadata" version = "16.0.0" @@ -139,8 +263,15 @@ dependencies = [ "cfg-if", "parity-scale-codec", "scale-info", + "serde", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.7" @@ -151,6 +282,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -161,6 +303,46 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -182,6 +364,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + [[package]] name = "keccak" version = "0.1.5" @@ -222,9 +410,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" dependencies = [ "arrayvec", + "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", + "serde", ] [[package]] @@ -239,6 +429,31 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -277,6 +492,48 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.4.0" @@ -286,16 +543,91 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "scale-bits" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "scale-decode" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" +dependencies = [ + "derive_more", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-decode-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3475108a1b62c7efd1b5c65974f30109a598b2f45f23c9ae030acb9686966db" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-encode" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" +dependencies = [ + "derive_more", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-encode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-encode-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "scale-info" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" dependencies = [ + "bitvec", "cfg-if", "derive_more", "parity-scale-codec", "scale-info-derive", + "serde", ] [[package]] @@ -310,12 +642,59 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scale-value" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58223c7691bf0bd46b43c9aea6f0472d1067f378d574180232358d7c6e0a8089" +dependencies = [ + "derive_more", + "either", + "frame-metadata 15.1.0", + "parity-scale-codec", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", +] + [[package]] name = "semver" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -337,6 +716,12 @@ dependencies = [ "keccak", ] +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + [[package]] name = "sp-core-hashing" version = "15.0.0" @@ -357,6 +742,44 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "subxt-core" +version = "0.34.0" +dependencies = [ + "base58", + "blake2", + "cfg-if", + "derivative", + "derive_more", + "frame-metadata 16.0.0", + "hex", + "impl-serde", + "parity-scale-codec", + "primitive-types", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-core-hashing", + "subxt-metadata", + "url", +] + [[package]] name = "subxt-core-no-std-tests" version = "0.0.0" @@ -364,6 +787,7 @@ dependencies = [ "libc", "libc_alloc", "parity-scale-codec", + "subxt-core", "subxt-metadata", ] @@ -373,7 +797,7 @@ version = "0.34.0" dependencies = [ "cfg-if", "derive_more", - "frame-metadata", + "frame-metadata 16.0.0", "hashbrown", "parity-scale-codec", "scale-info", @@ -402,6 +826,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "0.6.3" @@ -447,18 +892,62 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winnow" version = "0.5.34" @@ -468,6 +957,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/testing/no-std-tests/Cargo.toml b/testing/no-std-tests/Cargo.toml index 29a4bca242..5e6f14c074 100644 --- a/testing/no-std-tests/Cargo.toml +++ b/testing/no-std-tests/Cargo.toml @@ -7,6 +7,7 @@ resolver = "2" [dependencies] subxt-metadata = { path = "../../metadata", default-features = false } +subxt-core = { path = "../../core", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.9", default-features = false, features = ["derive"] } libc = { version = "0.2", default-features = false } libc_alloc = { version = "1.0.6" } diff --git a/testing/no-std-tests/src/main.rs b/testing/no-std-tests/src/main.rs index 0caff7a433..1638514801 100644 --- a/testing/no-std-tests/src/main.rs +++ b/testing/no-std-tests/src/main.rs @@ -47,3 +47,8 @@ fn subxt_metadata_test() { const METADATA: &[u8] = include_bytes!("../../../artifacts/polkadot_metadata_small.scale"); subxt_metadata::Metadata::decode(&mut &METADATA[..]).expect("should be valid metadata"); } + +fn subxt_core_test() { + let url = "wss://mysite.com"; + assert!(subxt_core::utils::url_is_secure(url).unwrap()); +} diff --git a/testing/no-std-tests/tree.txt b/testing/no-std-tests/tree.txt new file mode 100644 index 0000000000..613fae578b --- /dev/null +++ b/testing/no-std-tests/tree.txt @@ -0,0 +1,215 @@ +subxt-core-no-std-tests v0.0.0 (/home/tadeo/code/subxt2/testing/no-std-tests) +├── libc v0.2.152 +├── libc_alloc v1.0.6 +├── parity-scale-codec v3.6.9 +│ ├── arrayvec v0.7.4 +│ ├── byte-slice-cast v1.2.2 +│ ├── impl-trait-for-tuples v0.2.2 (proc-macro) +│ │ ├── proc-macro2 v1.0.78 +│ │ │ └── unicode-ident v1.0.12 +│ │ ├── quote v1.0.35 +│ │ │ └── proc-macro2 v1.0.78 (*) +│ │ └── syn v1.0.109 +│ │ ├── proc-macro2 v1.0.78 (*) +│ │ ├── quote v1.0.35 (*) +│ │ └── unicode-ident v1.0.12 +│ ├── parity-scale-codec-derive v3.6.9 (proc-macro) +│ │ ├── proc-macro-crate v2.0.1 +│ │ │ ├── toml_datetime v0.6.3 +│ │ │ └── toml_edit v0.20.2 +│ │ │ ├── indexmap v2.1.0 +│ │ │ │ ├── equivalent v1.0.1 +│ │ │ │ └── hashbrown v0.14.3 +│ │ │ ├── toml_datetime v0.6.3 +│ │ │ └── winnow v0.5.34 +│ │ ├── proc-macro2 v1.0.78 (*) +│ │ ├── quote v1.0.35 (*) +│ │ └── syn v1.0.109 (*) +│ └── serde v1.0.196 +│ └── serde_derive v1.0.196 (proc-macro) +│ ├── proc-macro2 v1.0.78 (*) +│ ├── quote v1.0.35 (*) +│ └── syn v2.0.48 +│ ├── proc-macro2 v1.0.78 (*) +│ ├── quote v1.0.35 (*) +│ └── unicode-ident v1.0.12 +├── subxt-core v0.34.0 (/home/tadeo/code/subxt2/core) +│ ├── base58 v0.2.0 +│ ├── blake2 v0.10.6 +│ │ └── digest v0.10.7 +│ │ ├── block-buffer v0.10.4 +│ │ │ └── generic-array v0.14.7 +│ │ │ └── typenum v1.17.0 +│ │ │ [build-dependencies] +│ │ │ └── version_check v0.9.4 +│ │ ├── crypto-common v0.1.6 +│ │ │ ├── generic-array v0.14.7 (*) +│ │ │ └── typenum v1.17.0 +│ │ └── subtle v2.5.0 +│ ├── cfg-if v1.0.0 +│ ├── derivative v2.2.0 (proc-macro) +│ │ ├── proc-macro2 v1.0.78 (*) +│ │ ├── quote v1.0.35 (*) +│ │ └── syn v1.0.109 (*) +│ ├── derive_more v0.99.17 (proc-macro) +│ │ ├── convert_case v0.4.0 +│ │ ├── proc-macro2 v1.0.78 (*) +│ │ ├── quote v1.0.35 (*) +│ │ └── syn v1.0.109 (*) +│ │ [build-dependencies] +│ │ └── rustc_version v0.4.0 +│ │ └── semver v1.0.21 +│ ├── frame-metadata v16.0.0 +│ │ ├── cfg-if v1.0.0 +│ │ ├── parity-scale-codec v3.6.9 (*) +│ │ ├── scale-info v2.10.0 +│ │ │ ├── bitvec v1.0.1 +│ │ │ │ ├── funty v2.0.0 +│ │ │ │ ├── radium v0.7.0 +│ │ │ │ ├── tap v1.0.1 +│ │ │ │ └── wyz v0.5.1 +│ │ │ │ └── tap v1.0.1 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── derive_more v0.99.17 (proc-macro) (*) +│ │ │ ├── parity-scale-codec v3.6.9 (*) +│ │ │ ├── scale-info-derive v2.10.0 (proc-macro) +│ │ │ │ ├── proc-macro-crate v1.3.1 +│ │ │ │ │ ├── once_cell v1.19.0 +│ │ │ │ │ └── toml_edit v0.19.15 +│ │ │ │ │ ├── indexmap v2.1.0 (*) +│ │ │ │ │ ├── toml_datetime v0.6.3 +│ │ │ │ │ └── winnow v0.5.34 +│ │ │ │ ├── proc-macro2 v1.0.78 (*) +│ │ │ │ ├── quote v1.0.35 (*) +│ │ │ │ └── syn v1.0.109 (*) +│ │ │ └── serde v1.0.196 (*) +│ │ └── serde v1.0.196 (*) +│ ├── hex v0.4.3 +│ ├── impl-serde v0.4.0 +│ │ └── serde v1.0.196 (*) +│ ├── parity-scale-codec v3.6.9 (*) +│ ├── primitive-types v0.12.2 +│ │ ├── fixed-hash v0.8.0 +│ │ │ ├── byteorder v1.5.0 +│ │ │ ├── rand v0.8.5 +│ │ │ │ ├── libc v0.2.152 +│ │ │ │ ├── rand_chacha v0.3.1 +│ │ │ │ │ ├── ppv-lite86 v0.2.17 +│ │ │ │ │ └── rand_core v0.6.4 +│ │ │ │ │ └── getrandom v0.2.12 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ └── libc v0.2.152 +│ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ ├── rustc-hex v2.1.0 +│ │ │ └── static_assertions v1.1.0 +│ │ ├── impl-codec v0.6.0 +│ │ │ └── parity-scale-codec v3.6.9 (*) +│ │ ├── impl-serde v0.4.0 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ └── uint v0.9.5 +│ │ ├── byteorder v1.5.0 +│ │ ├── crunchy v0.2.2 +│ │ ├── hex v0.4.3 +│ │ └── static_assertions v1.1.0 +│ ├── scale-bits v0.4.0 +│ │ ├── parity-scale-codec v3.6.9 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ └── serde v1.0.196 (*) +│ ├── scale-decode v0.10.0 +│ │ ├── derive_more v0.99.17 (proc-macro) (*) +│ │ ├── parity-scale-codec v3.6.9 (*) +│ │ ├── primitive-types v0.12.2 (*) +│ │ ├── scale-bits v0.4.0 (*) +│ │ ├── scale-decode-derive v0.10.0 (proc-macro) +│ │ │ ├── darling v0.14.4 +│ │ │ │ ├── darling_core v0.14.4 +│ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ ├── ident_case v1.0.1 +│ │ │ │ │ ├── proc-macro2 v1.0.78 (*) +│ │ │ │ │ ├── quote v1.0.35 (*) +│ │ │ │ │ ├── strsim v0.10.0 +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ └── darling_macro v0.14.4 (proc-macro) +│ │ │ │ ├── darling_core v0.14.4 (*) +│ │ │ │ ├── quote v1.0.35 (*) +│ │ │ │ └── syn v1.0.109 (*) +│ │ │ ├── proc-macro-crate v1.3.1 (*) +│ │ │ ├── proc-macro2 v1.0.78 (*) +│ │ │ ├── quote v1.0.35 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ └── smallvec v1.13.1 +│ ├── scale-encode v0.5.0 +│ │ ├── derive_more v0.99.17 (proc-macro) (*) +│ │ ├── parity-scale-codec v3.6.9 (*) +│ │ ├── primitive-types v0.12.2 (*) +│ │ ├── scale-bits v0.4.0 (*) +│ │ ├── scale-encode-derive v0.5.0 (proc-macro) +│ │ │ ├── darling v0.14.4 (*) +│ │ │ ├── proc-macro-crate v1.3.1 (*) +│ │ │ ├── proc-macro2 v1.0.78 (*) +│ │ │ ├── quote v1.0.35 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ └── smallvec v1.13.1 +│ ├── scale-info v2.10.0 (*) +│ ├── scale-value v0.13.0 +│ │ ├── derive_more v0.99.17 (proc-macro) (*) +│ │ ├── either v1.9.0 +│ │ ├── frame-metadata v15.1.0 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── parity-scale-codec v3.6.9 (*) +│ │ │ └── scale-info v2.10.0 (*) +│ │ ├── parity-scale-codec v3.6.9 (*) +│ │ ├── scale-bits v0.4.0 (*) +│ │ ├── scale-decode v0.10.0 (*) +│ │ ├── scale-encode v0.5.0 (*) +│ │ └── scale-info v2.10.0 (*) +│ ├── serde v1.0.196 (*) +│ ├── serde_json v1.0.113 +│ │ ├── itoa v1.0.10 +│ │ ├── ryu v1.0.16 +│ │ └── serde v1.0.196 (*) +│ ├── sp-core-hashing v15.0.0 +│ │ ├── blake2b_simd v1.0.2 +│ │ │ ├── arrayref v0.3.7 +│ │ │ ├── arrayvec v0.7.4 +│ │ │ └── constant_time_eq v0.3.0 +│ │ ├── byteorder v1.5.0 +│ │ ├── digest v0.10.7 (*) +│ │ ├── sha2 v0.10.8 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── cpufeatures v0.2.12 +│ │ │ └── digest v0.10.7 (*) +│ │ ├── sha3 v0.10.8 +│ │ │ ├── digest v0.10.7 (*) +│ │ │ └── keccak v0.1.5 +│ │ └── twox-hash v1.6.3 +│ │ ├── cfg-if v1.0.0 +│ │ ├── digest v0.10.7 (*) +│ │ └── static_assertions v1.1.0 +│ ├── subxt-metadata v0.34.0 (/home/tadeo/code/subxt2/metadata) +│ │ ├── cfg-if v1.0.0 +│ │ ├── derive_more v0.99.17 (proc-macro) (*) +│ │ ├── frame-metadata v16.0.0 (*) +│ │ ├── hashbrown v0.14.3 +│ │ │ ├── ahash v0.8.7 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ ├── once_cell v1.19.0 +│ │ │ │ └── zerocopy v0.7.32 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ └── allocator-api2 v0.2.16 +│ │ ├── parity-scale-codec v3.6.9 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ └── sp-core-hashing v15.0.0 (*) +│ └── url v2.5.0 +│ ├── form_urlencoded v1.2.1 +│ │ └── percent-encoding v2.3.1 +│ ├── idna v0.5.0 +│ │ ├── unicode-bidi v0.3.15 +│ │ └── unicode-normalization v0.1.22 +│ │ └── tinyvec v1.6.0 +│ │ └── tinyvec_macros v0.1.1 +│ └── percent-encoding v2.3.1 +└── subxt-metadata v0.34.0 (/home/tadeo/code/subxt2/metadata) (*)