add storage address and custom error type to core crate

This commit is contained in:
Tadeo hepperle
2024-02-01 18:19:26 +01:00
parent aed00e52f8
commit 97aad71569
15 changed files with 633 additions and 546 deletions
Generated
+1
View File
@@ -4544,6 +4544,7 @@ dependencies = [
"cfg-if",
"derivative",
"derive_more",
"either",
"frame-metadata 16.0.0",
"hex",
"impl-serde",
+1 -1
View File
@@ -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 }
+1
View File
@@ -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 }
+1 -1
View File
@@ -18,7 +18,7 @@ use vec::Vec;
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
// // 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;
+99
View File
@@ -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,
},
}
+5
View File
@@ -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,
+1 -57
View File
@@ -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 {}
+1 -1
View File
@@ -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;
+51
View File
@@ -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<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
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<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub(crate) fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
+273
View File
@@ -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<u8>) -> Result<(), Error>;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// 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<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
storage_entry_keys: Vec<StorageKey>,
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<StorageKey> = Address<StorageKey, DecodedValueThunk, Yes, Yes, Yes>;
impl<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
where
StorageKey: EncodeWithMetadata,
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`Address`] to use to access a storage entry.
pub fn new(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Vec<StorageKey>,
) -> 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<StorageKey>,
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<u8> {
super::storage_address_root_bytes(self)
}
}
impl<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
for Address<StorageKey, ReturnTy, Fetchable, Defaultable, Iterable>
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<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(self.pallet_name())?;
let storage = pallet
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
let entry = storage
.entry_by_name(self.entry_name())
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
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<Encoded>;
// Used in codegen to construct the above.
#[doc(hidden)]
pub fn make_static_storage_map_key<T: codec::Encode>(t: T) -> StaticStorageMapKey {
Static(Encoded(t.encode()))
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<StorageKey: EncodeWithMetadata>(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Vec<StorageKey>,
) -> DynamicAddress<StorageKey> {
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<u8>) {
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);
}
}
}
+199 -2
View File
@@ -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<u8>) -> Result<(), Error>;
/// Encode call data and return the output. This is a convenience
/// wrapper around [`TxPayload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
None
}
}
pub struct ValidationDetails<'a> {
/// The pallet name.
pub pallet_name: &'a str,
/// The call name.
pub call_name: &'a str,
/// A hash (this is generated at compile time in our codegen)
/// to compare against the runtime code.
pub hash: [u8; 32],
}
/// A transaction payload containing some generic `CallData`.
#[derive(Clone, Debug)]
pub struct Payload<CallData> {
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<Arc<dyn EncodeAsFields + Send + Sync + 'static>>;
/// The type of a payload typically used for dynamic transaction payloads.
pub type DynamicPayload = Payload<Composite<()>>;
impl<CallData> Payload<CallData> {
/// Create a new [`Payload`].
pub fn new(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: CallData,
) -> Self {
Payload {
pallet_name: Cow::Owned(pallet_name.into()),
call_name: Cow::Owned(call_name.into()),
call_data,
validation_hash: None,
}
}
/// Create a new [`Payload`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
Payload {
pallet_name: Cow::Borrowed(pallet_name),
call_name: Cow::Borrowed(call_name),
call_data,
validation_hash: Some(validation_hash),
}
}
/// 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<Composite<()>> {
/// Convert the dynamic `Composite` payload into a [`Value`].
/// This is useful if you want to use this as an argument for a
/// larger dynamic call that wants to use this as a nested call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.call_data,
}),
};
Value::unnamed_variant(self.pallet_name, [call])
}
}
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
let pallet_index = pallet.index();
let call_index = call.index;
pallet_index.encode_to(out);
call_index.encode_to(out);
let mut fields = call
.fields
.iter()
.map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref()));
self.call_data
.encode_as_fields_to(&mut fields, metadata.types(), out)
.expect("The fields are valid types from the metadata, qed;");
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| ValidationDetails {
pallet_name: &self.pallet_name,
call_name: &self.call_name,
hash,
})
}
}
/// Construct a transaction at runtime; essentially an alias to [`Payload::new()`]
/// which provides a [`Composite`] value for the call data.
pub fn dynamic(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: impl Into<Composite<()>>,
) -> DynamicPayload {
Payload::new(pallet_name, call_name, call_data.into())
}
-192
View File
@@ -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<u8>) -> Result<(), Error>;
// /// Encode call data and return the output. This is a convenience
// /// wrapper around [`TxPayload::encode_call_data_to`].
// fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
// let mut v = Vec::new();
// self.encode_call_data_to(metadata, &mut v)?;
// Ok(v)
// }
// /// Returns the details needed to validate the call, which
// /// include a statically generated hash, the pallet name,
// /// and the call name.
// fn validation_details(&self) -> Option<ValidationDetails<'_>> {
// None
// }
// }
// pub struct ValidationDetails<'a> {
// /// The pallet name.
// pub pallet_name: &'a str,
// /// The call name.
// pub call_name: &'a str,
// /// A hash (this is generated at compile time in our codegen)
// /// to compare against the runtime code.
// pub hash: [u8; 32],
// }
// /// A transaction payload containing some generic `CallData`.
// #[derive(Clone, Debug)]
// pub struct Payload<CallData> {
// 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<Arc<dyn EncodeAsFields + Send + Sync + 'static>>;
// /// The type of a payload typically used for dynamic transaction payloads.
// pub type DynamicPayload = Payload<Composite<()>>;
// impl<CallData> Payload<CallData> {
// /// Create a new [`Payload`].
// pub fn new(
// pallet_name: impl Into<String>,
// call_name: impl Into<String>,
// call_data: CallData,
// ) -> Self {
// Payload {
// pallet_name: Cow::Owned(pallet_name.into()),
// call_name: Cow::Owned(call_name.into()),
// call_data,
// validation_hash: None,
// }
// }
// /// Create a new [`Payload`] using static strings for the pallet and call name.
// /// This is only expected to be used from codegen.
// #[doc(hidden)]
// pub fn new_static(
// pallet_name: &'static str,
// call_name: &'static str,
// call_data: CallData,
// validation_hash: [u8; 32],
// ) -> Self {
// Payload {
// pallet_name: Cow::Borrowed(pallet_name),
// call_name: Cow::Borrowed(call_name),
// call_data,
// validation_hash: Some(validation_hash),
// }
// }
// /// 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<Composite<()>> {
// /// Convert the dynamic `Composite` payload into a [`Value`].
// /// This is useful if you want to use this as an argument for a
// /// larger dynamic call that wants to use this as a nested call.
// pub fn into_value(self) -> Value<()> {
// let call = Value {
// context: (),
// value: ValueDef::Variant(Variant {
// name: self.call_name.into_owned(),
// values: self.call_data,
// }),
// };
// Value::unnamed_variant(self.pallet_name, [call])
// }
// }
// impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
// fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
// let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
// let call = pallet
// .call_variant_by_name(&self.call_name)
// .ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
// let pallet_index = pallet.index();
// let call_index = call.index;
// pallet_index.encode_to(out);
// call_index.encode_to(out);
// let mut fields = call
// .fields
// .iter()
// .map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref()));
// self.call_data
// .encode_as_fields_to(&mut fields, metadata.types(), out)?;
// Ok(())
// }
// fn validation_details(&self) -> Option<ValidationDetails<'_>> {
// self.validation_hash.map(|hash| ValidationDetails {
// pallet_name: &self.pallet_name,
// call_name: &self.call_name,
// hash,
// })
// }
// }
// /// Construct a transaction at runtime; essentially an alias to [`Payload::new()`]
// /// which provides a [`Composite`] value for the call data.
// pub fn dynamic(
// pallet_name: impl Into<String>,
// call_name: impl Into<String>,
// call_data: impl Into<Composite<()>>,
// ) -> DynamicPayload {
// Payload::new(pallet_name, call_name, call_data.into())
// }
-100
View File
@@ -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<T: Config> {
/// Return the "from" account ID.
fn account_id(&self) -> T::AccountId;
/// Return the "from" address.
fn address(&self) -> T::Address;
/// Takes a signer payload for an extrinsic, and returns a signature based on it.
///
/// Some signers may fail, for instance because the hardware on which the keys are located has
/// refused the operation.
fn sign(&self, signer_payload: &[u8]) -> T::Signature;
}
cfg_substrate_compat! {
pub use pair_signer::PairSigner;
}
// A signer suitable for substrate based chains. This provides compatibility with Substrate
// packages like sp_keyring and such, and so relies on sp_core and sp_runtime to be included.
#[cfg(feature = "substrate-compat")]
mod pair_signer {
use super::Signer;
use crate::Config;
use sp_core::Pair as PairT;
use sp_runtime::{
traits::{IdentifyAccount, Verify},
AccountId32 as SpAccountId32, MultiSignature as SpMultiSignature,
};
/// A [`Signer`] implementation that can be constructed from an [`sp_core::Pair`].
#[derive(Clone, Debug)]
pub struct PairSigner<T: Config, Pair> {
account_id: T::AccountId,
signer: Pair,
}
impl<T, Pair> PairSigner<T, Pair>
where
T: Config,
Pair: PairT,
// We go via an `sp_runtime::MultiSignature`. We can probably generalise this
// by implementing some of these traits on our built-in MultiSignature and then
// requiring them on all T::Signatures, to avoid any go-between.
<SpMultiSignature as Verify>::Signer: From<Pair::Public>,
T::AccountId: From<SpAccountId32>,
{
/// Creates a new [`Signer`] from an [`sp_core::Pair`].
pub fn new(signer: Pair) -> Self {
let account_id =
<SpMultiSignature as Verify>::Signer::from(signer.public()).into_account();
Self {
account_id: account_id.into(),
signer,
}
}
/// Returns the [`sp_core::Pair`] implementation used to construct this.
pub fn signer(&self) -> &Pair {
&self.signer
}
/// Return the account ID.
pub fn account_id(&self) -> &T::AccountId {
&self.account_id
}
}
impl<T, Pair> Signer<T> for PairSigner<T, Pair>
where
T: Config,
Pair: PairT,
Pair::Signature: Into<T::Signature>,
{
fn account_id(&self) -> T::AccountId {
self.account_id.clone()
}
fn address(&self) -> T::Address {
self.account_id.clone().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.signer.sign(signer_payload).into()
}
}
}
-192
View File
@@ -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<u8>) -> Result<(), Error>;
/// Encode call data and return the output. This is a convenience
/// wrapper around [`TxPayload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
None
}
}
pub struct ValidationDetails<'a> {
/// The pallet name.
pub pallet_name: &'a str,
/// The call name.
pub call_name: &'a str,
/// A hash (this is generated at compile time in our codegen)
/// to compare against the runtime code.
pub hash: [u8; 32],
}
/// A transaction payload containing some generic `CallData`.
#[derive(Clone, Debug)]
pub struct Payload<CallData> {
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<Arc<dyn EncodeAsFields + Send + Sync + 'static>>;
/// The type of a payload typically used for dynamic transaction payloads.
pub type DynamicPayload = Payload<Composite<()>>;
impl<CallData> Payload<CallData> {
/// Create a new [`Payload`].
pub fn new(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: CallData,
) -> Self {
Payload {
pallet_name: Cow::Owned(pallet_name.into()),
call_name: Cow::Owned(call_name.into()),
call_data,
validation_hash: None,
}
}
/// Create a new [`Payload`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
Payload {
pallet_name: Cow::Borrowed(pallet_name),
call_name: Cow::Borrowed(call_name),
call_data,
validation_hash: Some(validation_hash),
}
}
/// 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<Composite<()>> {
/// Convert the dynamic `Composite` payload into a [`Value`].
/// This is useful if you want to use this as an argument for a
/// larger dynamic call that wants to use this as a nested call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.call_data,
}),
};
Value::unnamed_variant(self.pallet_name, [call])
}
}
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
let pallet_index = pallet.index();
let call_index = call.index;
pallet_index.encode_to(out);
call_index.encode_to(out);
let mut fields = call
.fields
.iter()
.map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref()));
self.call_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| ValidationDetails {
pallet_name: &self.pallet_name,
call_name: &self.call_name,
hash,
})
}
}
/// Construct a transaction at runtime; essentially an alias to [`Payload::new()`]
/// which provides a [`Composite`] value for the call data.
pub fn dynamic(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: impl Into<Composite<()>>,
) -> DynamicPayload {
Payload::new(pallet_name, call_name, call_data.into())
}