diff --git a/Cargo.lock b/Cargo.lock index f6cdbcf0e0..6c2fd6f688 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4544,6 +4544,7 @@ dependencies = [ "cfg-if", "derivative", "derive_more", + "either", "frame-metadata 16.0.0", "hex", "impl-serde", diff --git a/Cargo.toml b/Cargo.toml index 14160571fc..1ecc821c31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ console_error_panic_hook = "0.1.7" darling = "0.20.3" derivative = "2.2.0" derive_more = "0.99.17" -either = "1.9.0" +either = { version = "1.9.0", default-features = false } frame-metadata = { version = "16.0.0", default-features = false } futures = { version = "0.3.30", default-features = false, features = ["std"] } getrandom = { version = "0.2", default-features = false } diff --git a/core/Cargo.toml b/core/Cargo.toml index 1121a682cc..8435c04209 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -48,6 +48,7 @@ sp-core-hashing = { workspace = true } sp-core = { workspace = true, optional = true } sp-runtime = { workspace = true, optional = true } cfg-if = { workspace = true } +either = { workspace = true } [dev-dependencies] bitvec = { workspace = true } diff --git a/core/src/dynamic.rs b/core/src/dynamic.rs index 160864331d..a1a8aeaa22 100644 --- a/core/src/dynamic.rs +++ b/core/src/dynamic.rs @@ -18,7 +18,7 @@ use vec::Vec; pub type DecodedValue = scale_value::Value; // // Submit dynamic transactions. -// pub use crate::tx::dynamic as tx; +pub use crate::tx::dynamic as tx; // // Lookup constants dynamically. // pub use crate::constants::dynamic as constant; diff --git a/core/src/error.rs b/core/src/error.rs new file mode 100644 index 0000000000..8ca8c6db6d --- /dev/null +++ b/core/src/error.rs @@ -0,0 +1,99 @@ +use derive_more::{Display, From}; + +#[derive(Debug, Display, From)] +pub enum Error { + #[display(fmt = "Metadata Error: {_0}")] + Metadata(MetadataError), + #[display(fmt = "Storage Error: {_0}")] + Storage(StorageAddressError), + /// Error decoding to a [`crate::dynamic::Value`]. + #[display(fmt = "Error decoding into dynamic value: {_0}")] + Decode(scale_decode::Error), + /// Error encoding from a [`crate::dynamic::Value`]. + #[display(fmt = "Error encoding from dynamic value: {_0}")] + Encode(scale_encode::Error), +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +/// Something went wrong trying to access details in the metadata. +#[derive(Clone, Debug, PartialEq, Display)] +#[non_exhaustive] +pub enum MetadataError { + /// The DispatchError type isn't available in the metadata + #[display(fmt = "The DispatchError type isn't available")] + DispatchErrorNotFound, + /// Type not found in metadata. + #[display(fmt = "Type with ID {_0} not found")] + TypeNotFound(u32), + /// Pallet not found (index). + #[display(fmt = "Pallet with index {_0} not found")] + PalletIndexNotFound(u8), + /// Pallet not found (name). + #[display(fmt = "Pallet with name {_0} not found")] + PalletNameNotFound(String), + /// Variant not found. + #[display(fmt = "Variant with index {_0} not found")] + VariantIndexNotFound(u8), + /// Constant not found. + #[display(fmt = "Constant with name {_0} not found")] + ConstantNameNotFound(String), + /// Call not found. + #[display(fmt = "Call with name {_0} not found")] + CallNameNotFound(String), + /// Runtime trait not found. + #[display(fmt = "Runtime trait with name {_0} not found")] + RuntimeTraitNotFound(String), + /// Runtime method not found. + #[display(fmt = "Runtime method with name {_0} not found")] + RuntimeMethodNotFound(String), + /// Call type not found in metadata. + #[display(fmt = "Call type not found in pallet with index {_0}")] + CallTypeNotFoundInPallet(u8), + /// Event type not found in metadata. + #[display(fmt = "Event type not found in pallet with index {_0}")] + EventTypeNotFoundInPallet(u8), + /// Storage details not found in metadata. + #[display(fmt = "Storage details not found in pallet with name {_0}")] + StorageNotFoundInPallet(String), + /// Storage entry not found. + #[display(fmt = "Storage entry {_0} not found")] + StorageEntryNotFound(String), + /// The generated interface used is not compatible with the node. + #[display(fmt = "The generated code is not compatible with the node")] + IncompatibleCodegen, + /// Custom value not found. + #[display(fmt = "Custom value with name {_0} not found")] + CustomValueNameNotFound(String), +} + +#[cfg(feature = "std")] +impl std::error::Error for MetadataError {} + +/// Something went wrong trying to encode a storage address. +#[derive(Clone, Debug, Display)] +#[non_exhaustive] +pub enum StorageAddressError { + /// Storage map type must be a composite type. + #[display(fmt = "Storage map type must be a composite type")] + MapTypeMustBeTuple, + /// Storage lookup does not have the expected number of keys. + #[display(fmt = "Storage lookup requires {expected} keys but got {actual} keys")] + WrongNumberOfKeys { + /// The actual number of keys needed, based on the metadata. + actual: usize, + /// The number of keys provided in the storage address. + expected: usize, + }, + /// This storage entry in the metadata does not have the correct number of hashers to fields. + #[display( + fmt = "Storage entry in metadata does not have the correct number of hashers to fields" + )] + WrongNumberOfHashers { + /// The number of hashers in the metadata for this storage entry. + hashers: usize, + /// The number of fields in the metadata for this storage entry. + fields: usize, + }, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 910e4204c7..20d03f0dea 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -11,11 +11,16 @@ pub mod client; pub mod config; pub mod dynamic; +mod error; pub mod metadata; pub mod prelude; +pub mod signer; +pub mod storage; pub mod tx; pub mod utils; +pub use error::{Error, MetadataError, StorageAddressError}; + pub use config::{ BlockHash, Config, ExtrinsicParams, ExtrinsicParamsEncoder, PolkadotConfig, PolkadotExtrinsicParams, SubstrateConfig, SubstrateExtrinsicParams, diff --git a/core/src/metadata/metadata_type.rs b/core/src/metadata/metadata_type.rs index 18e4d8843d..48d822189a 100644 --- a/core/src/metadata/metadata_type.rs +++ b/core/src/metadata/metadata_type.rs @@ -2,10 +2,8 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::prelude::*; +use crate::{prelude::*, MetadataError}; 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. @@ -74,57 +72,3 @@ impl codec::Decode for Metadata { 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), -} - -#[cfg(feature = "std")] -impl std::error::Error for MetadataError {} diff --git a/core/src/metadata/mod.rs b/core/src/metadata/mod.rs index 0ba35ec5ec..a6ef00ae6f 100644 --- a/core/src/metadata/mod.rs +++ b/core/src/metadata/mod.rs @@ -8,7 +8,7 @@ mod decode_encode_traits; mod metadata_type; pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata}; -pub use metadata_type::{Metadata, MetadataError}; +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/tx/signer.rs b/core/src/signer.rs similarity index 100% rename from core/src/tx/signer.rs rename to core/src/signer.rs diff --git a/core/src/storage/mod.rs b/core/src/storage/mod.rs new file mode 100644 index 0000000000..e37eb0383a --- /dev/null +++ b/core/src/storage/mod.rs @@ -0,0 +1,51 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! Types associated with accessing and working with storage items. + +mod storage_address; +/// Types representing an address which describes where a storage +/// entry lives and how to properly decode it. +pub mod address { + pub use super::storage_address::{ + dynamic, make_static_storage_map_key, Address, DynamicAddress, StaticStorageMapKey, + StorageAddress, Yes, + }; +} + +use crate::Error; + +// For consistency with other modules, also expose +// the basic address stuff at the root of the module. +pub use storage_address::{dynamic, Address, DynamicAddress, StorageAddress}; + +use crate::Metadata; +/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name +/// and append those bytes to the output. +pub(crate) fn write_storage_address_root_bytes( + addr: &Address, + out: &mut Vec, +) { + out.extend(sp_core_hashing::twox_128(addr.pallet_name().as_bytes())); + out.extend(sp_core_hashing::twox_128(addr.entry_name().as_bytes())); +} + +/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent +/// a lookup in a storage map at that location. +pub(crate) fn storage_address_bytes( + addr: &Address, + metadata: &Metadata, +) -> Result, Error> { + let mut bytes = Vec::new(); + write_storage_address_root_bytes(addr, &mut bytes); + addr.append_entry_bytes(metadata, &mut bytes)?; + Ok(bytes) +} + +/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`]. +pub(crate) fn storage_address_root_bytes(addr: &Address) -> Vec { + let mut bytes = Vec::new(); + write_storage_address_root_bytes(addr, &mut bytes); + bytes +} diff --git a/core/src/storage/storage_address.rs b/core/src/storage/storage_address.rs new file mode 100644 index 0000000000..925b0a012e --- /dev/null +++ b/core/src/storage/storage_address.rs @@ -0,0 +1,273 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use crate::{ + dynamic::DecodedValueThunk, + error::StorageAddressError, + utils::{Encoded, Static}, + MetadataError, +}; + +use crate::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata}; +use crate::Error; +use derivative::Derivative; +use scale_info::TypeDef; +use std::borrow::Cow; +use subxt_metadata::{StorageEntryType, StorageHasher}; + +/// This represents a storage address. Anything implementing this trait +/// can be used to fetch and iterate over storage entries. +pub trait StorageAddress { + /// The target type of the value that lives at this address. + type Target: DecodeWithMetadata; + /// Can an entry be fetched from this address? + /// Set this type to [`Yes`] to enable the corresponding calls to be made. + type IsFetchable; + /// Can a default entry be obtained from this address? + /// Set this type to [`Yes`] to enable the corresponding calls to be made. + type IsDefaultable; + /// Can this address be iterated over? + /// Set this type to [`Yes`] to enable the corresponding calls to be made. + type IsIterable; + + /// The name of the pallet that the entry lives under. + fn pallet_name(&self) -> &str; + + /// The name of the entry in a given pallet that the item is at. + fn entry_name(&self) -> &str; + + /// Output the non-prefix bytes; that is, any additional bytes that need + /// to be appended to the key to dig into maps. + fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec) -> Result<(), Error>; + + /// An optional hash which, if present, will be checked against + /// the node metadata to confirm that the return type matches what + /// we are expecting. + fn validation_hash(&self) -> Option<[u8; 32]> { + None + } +} + +/// Used to signal whether a [`StorageAddress`] can be iterated, +/// fetched and returned with a default value in the type system. +pub struct Yes; + +/// A concrete storage address. This can be created from static values (ie those generated +/// via the `subxt` macro) or dynamic values via [`dynamic`]. +#[derive(Derivative)] +#[derivative( + Clone(bound = "StorageKey: Clone"), + Debug(bound = "StorageKey: std::fmt::Debug") +)] +pub struct Address { + pallet_name: Cow<'static, str>, + entry_name: Cow<'static, str>, + storage_entry_keys: Vec, + validation_hash: Option<[u8; 32]>, + _marker: std::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>, +} + +/// A typical storage address constructed at runtime rather than via the `subxt` macro; this +/// has no restriction on what it can be used for (since we don't statically know). +pub type DynamicAddress = Address; + +impl + Address +where + StorageKey: EncodeWithMetadata, + ReturnTy: DecodeWithMetadata, +{ + /// Create a new [`Address`] to use to access a storage entry. + pub fn new( + pallet_name: impl Into, + entry_name: impl Into, + storage_entry_keys: Vec, + ) -> Self { + Self { + pallet_name: Cow::Owned(pallet_name.into()), + entry_name: Cow::Owned(entry_name.into()), + storage_entry_keys: storage_entry_keys.into_iter().collect(), + validation_hash: None, + _marker: std::marker::PhantomData, + } + } + + /// Create a new [`Address`] using static strings for the pallet and call name. + /// This is only expected to be used from codegen. + #[doc(hidden)] + pub fn new_static( + pallet_name: &'static str, + entry_name: &'static str, + storage_entry_keys: Vec, + hash: [u8; 32], + ) -> Self { + Self { + pallet_name: Cow::Borrowed(pallet_name), + entry_name: Cow::Borrowed(entry_name), + storage_entry_keys: storage_entry_keys.into_iter().collect(), + validation_hash: Some(hash), + _marker: std::marker::PhantomData, + } + } + + /// Do not validate this storage entry prior to accessing it. + pub fn unvalidated(self) -> Self { + Self { + validation_hash: None, + ..self + } + } + + /// Return bytes representing the root of this storage entry (ie a hash of + /// the pallet and entry name). Use [`crate::storage::StorageClient::address_bytes()`] + /// to obtain the bytes representing the entire address. + pub fn to_root_bytes(&self) -> Vec { + super::storage_address_root_bytes(self) + } +} + +impl StorageAddress + for Address +where + StorageKey: EncodeWithMetadata, + ReturnTy: DecodeWithMetadata, +{ + type Target = ReturnTy; + type IsFetchable = Fetchable; + type IsDefaultable = Defaultable; + type IsIterable = Iterable; + + fn pallet_name(&self) -> &str { + &self.pallet_name + } + + fn entry_name(&self) -> &str { + &self.entry_name + } + + fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec) -> Result<(), Error> { + let pallet = metadata.pallet_by_name_err(self.pallet_name())?; + let storage = pallet + .storage() + .ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?; + let entry = storage + .entry_by_name(self.entry_name()) + .ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?; + + match entry.entry_type() { + StorageEntryType::Plain(_) => { + if !self.storage_entry_keys.is_empty() { + Err(StorageAddressError::WrongNumberOfKeys { + expected: 0, + actual: self.storage_entry_keys.len(), + } + .into()) + } else { + Ok(()) + } + } + StorageEntryType::Map { + hashers, key_ty, .. + } => { + let ty = metadata + .types() + .resolve(*key_ty) + .ok_or(MetadataError::TypeNotFound(*key_ty))?; + + // If the provided keys are empty, the storage address must be + // equal to the storage root address. + if self.storage_entry_keys.is_empty() { + return Ok(()); + } + + // If the key is a tuple, we encode each value to the corresponding tuple type. + // If the key is not a tuple, encode a single value to the key type. + let type_ids = match &ty.type_def { + TypeDef::Tuple(tuple) => { + either::Either::Left(tuple.fields.iter().map(|f| f.id)) + } + _other => either::Either::Right(std::iter::once(*key_ty)), + }; + + if type_ids.len() < self.storage_entry_keys.len() { + // Provided more keys than fields. + return Err(StorageAddressError::WrongNumberOfKeys { + expected: type_ids.len(), + actual: self.storage_entry_keys.len(), + } + .into()); + } + + if hashers.len() == 1 { + // One hasher; hash a tuple of all SCALE encoded bytes with the one hash function. + let mut input = Vec::new(); + let iter = self.storage_entry_keys.iter().zip(type_ids); + for (key, type_id) in iter { + key.encode_with_metadata(type_id, metadata, &mut input)?; + } + hash_bytes(&input, &hashers[0], bytes); + Ok(()) + } else if hashers.len() >= type_ids.len() { + let iter = self.storage_entry_keys.iter().zip(type_ids).zip(hashers); + // A hasher per field; encode and hash each field independently. + for ((key, type_id), hasher) in iter { + let mut input = Vec::new(); + key.encode_with_metadata(type_id, metadata, &mut input)?; + hash_bytes(&input, hasher, bytes); + } + Ok(()) + } else { + // Provided more fields than hashers. + Err(StorageAddressError::WrongNumberOfHashers { + hashers: hashers.len(), + fields: type_ids.len(), + } + .into()) + } + } + } + } + + fn validation_hash(&self) -> Option<[u8; 32]> { + self.validation_hash + } +} + +/// A static storage key; this is some pre-encoded bytes +/// likely provided by the generated interface. +pub type StaticStorageMapKey = Static; + +// Used in codegen to construct the above. +#[doc(hidden)] +pub fn make_static_storage_map_key(t: T) -> StaticStorageMapKey { + Static(Encoded(t.encode())) +} + +/// Construct a new dynamic storage lookup. +pub fn dynamic( + pallet_name: impl Into, + entry_name: impl Into, + storage_entry_keys: Vec, +) -> DynamicAddress { + DynamicAddress::new(pallet_name, entry_name, storage_entry_keys) +} + +/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly. +fn hash_bytes(input: &[u8], hasher: &StorageHasher, bytes: &mut Vec) { + match hasher { + StorageHasher::Identity => bytes.extend(input), + StorageHasher::Blake2_128 => bytes.extend(sp_core_hashing::blake2_128(input)), + StorageHasher::Blake2_128Concat => { + bytes.extend(sp_core_hashing::blake2_128(input)); + bytes.extend(input); + } + StorageHasher::Blake2_256 => bytes.extend(sp_core_hashing::blake2_256(input)), + StorageHasher::Twox128 => bytes.extend(sp_core_hashing::twox_128(input)), + StorageHasher::Twox256 => bytes.extend(sp_core_hashing::twox_256(input)), + StorageHasher::Twox64Concat => { + bytes.extend(sp_core_hashing::twox_64(input)); + bytes.extend(input); + } + } +} diff --git a/core/src/tx/mod.rs b/core/src/tx/mod.rs index 293cbd33c1..d9a7c839db 100644 --- a/core/src/tx/mod.rs +++ b/core/src/tx/mod.rs @@ -1,2 +1,199 @@ -pub mod signer; -pub mod tx_payload; +// 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::prelude::*; +use crate::Error; +use crate::MetadataError; +use crate::{ + dynamic::Value, + metadata::{self, Metadata}, +}; +use borrow::Cow; +use borrow::ToOwned; +use codec::Encode; +use scale_encode::EncodeAsFields; +use scale_value::{Composite, ValueDef, Variant}; +use string::String; +use sync::Arc; +use vec::Vec; + +/// 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) + .expect("The fields are valid types from the metadata, qed;"); + 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/tx/tx_payload.rs b/core/src/tx/tx_payload.rs deleted file mode 100644 index 6b65d8e434..0000000000 --- a/core/src/tx/tx_payload.rs +++ /dev/null @@ -1,192 +0,0 @@ -// // 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/subxt/src/tx/signer.rs b/subxt/src/tx/signer.rs deleted file mode 100644 index 444aa46477..0000000000 --- a/subxt/src/tx/signer.rs +++ /dev/null @@ -1,100 +0,0 @@ -// 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/subxt/src/tx/tx_payload.rs b/subxt/src/tx/tx_payload.rs deleted file mode 100644 index 1a7cdeb941..0000000000 --- a/subxt/src/tx/tx_payload.rs +++ /dev/null @@ -1,192 +0,0 @@ -// 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}, -}; -use codec::Encode; -use scale_encode::EncodeAsFields; -use scale_value::{Composite, ValueDef, Variant}; -use std::{borrow::Cow, sync::Arc}; -use subxt_core::metadata::Metadata; - -/// 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()) -}