Merge v0.50.x to master (#2127)

* v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)

* WIP integrating new frame-decode and working out new storage APIS

* WIP: first pass adding new storage things to subxt-core

* Second pass over Address type and start impl in Subxt

* WIP new storage APIs

* WIP New storage APIs roughly completed, lots of errors still

* Remove PlainorMap enum; plain and map values now use same struct to simplify usage

* Begin 'fixing' errors

* WIP splitting errors and tidying payload/address traits

* Get subxt-core compiling

* Small fixes in subxt-core and remove metadata mod

* subxt-core: cargo check --all-targets passes

* Fix test

* WIP starting to update subxt from subxt-core changes

* WIP splitting up subxt errors into smaller variants

* WIP errors: add DispatchError errors

* Port new Storage APIs to subxt-core

* cargo check -p subxt passes

* Quick-fix errors in subxt-cli (explore subcommand)

* fmt

* Finish fixing codegen up and start fixing examples

* get Subxt examples compiling and bytes_at for constants

* Add some arcs to limit lifetimes in subxt/subxt-core storage APIs

* A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt

* Update codegen test

* cargo check --all-targets passing

* cargo check --features 'unstable-light-client' passing

* clippy

* Remove unused dep in subxt

* use published frame-decode

* fix wasm-example

* Add new tx extension to fix daily tests

* Remove unused subxt_core::dynamic::DecodedValue type

* Update book to match changes

* Update docs to fix more broken bits

* Add missing docs

* fmt

* allow larger result errs for now

* Add missing alloc imports in subxt-core

* Fix doc tests and fix bug getting constant info

* Fix V14 -> Metadata transform for storage & constants

* Fix parachain example

* Fix FFI example

* BlockLength decodes t ostruct, not u128

* use fetch/iter shorthands rather than entry in most storage tests

* Fix some integration tests

* Fix Runtime codegen tests

* Expose the dynamic custom_value selecter and use in a UI test

* Update codegen metadata

* Tidy CLI storage query and support (str,str) as a storage address

* Add (str,str) as valid constant address too

* Show string tuple in constants example

* Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them

* clippy

* [v0.50] update scale-info-legacy and frame-decode to latest (#2119)

* bump scale-info-legacy and frame-decode to latest

* Remove something we don't need in this PR

* Fully remove unused for now dep

* [v0.50] Convert historic metadata to subxt::Metadata (#2120)

* First pass converting historic metadatas to our subxt::Metadata type

* use published frame-decode

* fmt and rename legacy metadata macro

* Enable legacy feature where needed in subxt_metadata so it compiles on its own

* Use cargo hack more in CI and fix subxt-metadata features

* Add tests for metadata conversion (need to optimise; some too expensive right now

* Address performance and equality issues in metadata conversion testing

* fmt

* fmt all

* clippy

* Fix a doc link

* Test codegen and fixes to make it work

* Remove local frame-decode patch

* bump frame-decode to latest

* [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124)

* Allow visiting extrinsic fields

* fmt

* Don't use local scale-decode dep

* Clippy and tidy

* Extend 'subxt codegen' CLI to work with legacy metadatas

* Simplify historic extrinsics example now that AccountId32s have paths/names

* clippy

* clippy

* clippy..

* Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime

* Try to fix flaky test

* Add custom value decode to extrinsics example

* Remove useless else branch ra thought I needed

* Simplify examples

* Prep to release v0.0.5 (#2126)
This commit is contained in:
James Wilson
2025-11-22 10:44:03 +00:00
committed by GitHub
parent 586b814ecd
commit 8203679cbd
158 changed files with 13736 additions and 16451 deletions
@@ -7,7 +7,9 @@ use crate::config::transaction_extensions::{
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
};
use crate::dynamic::Value;
use crate::{Metadata, config::Config, error::Error};
use crate::error::ExtrinsicError;
use crate::{Metadata, config::Config};
use alloc::borrow::ToOwned;
use frame_decode::extrinsics::ExtrinsicExtensions;
use scale_decode::DecodeAsType;
@@ -50,7 +52,7 @@ impl<'a, T: Config> ExtrinsicTransactionExtensions<'a, T> {
/// Searches through all signed extensions to find a specific one.
/// If the Signed Extension is not found `Ok(None)` is returned.
/// If the Signed Extension is found but decoding failed `Err(_)` is returned.
pub fn find<S: TransactionExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
pub fn find<S: TransactionExtension<T>>(&self) -> Result<Option<S::Decoded>, ExtrinsicError> {
for ext in self.iter() {
match ext.as_signed_extension::<S>() {
// We found a match; return it:
@@ -117,12 +119,16 @@ impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
}
/// Signed Extension as a [`scale_value::Value`]
pub fn value(&self) -> Result<Value<u32>, Error> {
pub fn value(&self) -> Result<Value<u32>, ExtrinsicError> {
let value = scale_value::scale::decode_as_type(
&mut &self.bytes[..],
self.ty_id,
self.metadata.types(),
)?;
)
.map_err(|e| ExtrinsicError::CouldNotDecodeTransactionExtension {
name: self.identifier.to_owned(),
error: e.into(),
})?;
Ok(value)
}
@@ -131,15 +137,19 @@ impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
/// decode with.
pub fn as_signed_extension<S: TransactionExtension<T>>(
&self,
) -> Result<Option<S::Decoded>, Error> {
) -> Result<Option<S::Decoded>, ExtrinsicError> {
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
return Ok(None);
}
self.as_type::<S::Decoded>().map(Some)
}
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
fn as_type<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())
.map_err(|e| ExtrinsicError::CouldNotDecodeTransactionExtension {
name: self.identifier.to_owned(),
error: e,
})?;
Ok(value)
}
}
+82 -96
View File
@@ -2,19 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::BlockError;
use crate::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
use crate::{
Metadata,
config::{Config, HashFor, Hasher},
error::{Error, MetadataError},
error::{ExtrinsicDecodeErrorAt, ExtrinsicDecodeErrorAtReason, ExtrinsicError},
};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::ops::Deref;
use frame_decode::extrinsics::Extrinsic;
use scale_decode::DecodeAsType;
use subxt_metadata::PalletMetadata;
use scale_decode::{DecodeAsFields, DecodeAsType};
pub use crate::blocks::StaticExtrinsic;
@@ -30,7 +27,10 @@ impl<T: Config> Extrinsics<T> {
/// Instantiate a new [`Extrinsics`] object, given a vector containing
/// each extrinsic hash (in the form of bytes) and some metadata that
/// we'll use to decode them.
pub fn decode_from(extrinsics: Vec<Vec<u8>>, metadata: Metadata) -> Result<Self, Error> {
pub fn decode_from(
extrinsics: Vec<Vec<u8>>,
metadata: Metadata,
) -> Result<Self, ExtrinsicDecodeErrorAt> {
let hasher = T::Hasher::new(&metadata);
let extrinsics = extrinsics
.into_iter()
@@ -39,29 +39,25 @@ impl<T: Config> Extrinsics<T> {
let cursor = &mut &*bytes;
// Try to decode the extrinsic.
let decoded_info = frame_decode::extrinsics::decode_extrinsic(
cursor,
metadata.deref(),
metadata.types(),
)
.map_err(|error| BlockError::ExtrinsicDecodeError {
extrinsic_index,
error,
})?
.into_owned();
let decoded_info =
frame_decode::extrinsics::decode_extrinsic(cursor, &metadata, metadata.types())
.map_err(|error| ExtrinsicDecodeErrorAt {
extrinsic_index,
error: ExtrinsicDecodeErrorAtReason::DecodeError(error),
})?
.into_owned();
// We didn't consume all bytes, so decoding probably failed.
if !cursor.is_empty() {
return Err(BlockError::LeftoverBytes {
return Err(ExtrinsicDecodeErrorAt {
extrinsic_index,
num_leftover_bytes: cursor.len(),
}
.into());
error: ExtrinsicDecodeErrorAtReason::LeftoverBytes(cursor.to_vec()),
});
}
Ok(Arc::new((decoded_info, bytes)))
})
.collect::<Result<_, Error>>()?;
.collect::<Result<_, ExtrinsicDecodeErrorAt>>()?;
Ok(Self {
extrinsics,
@@ -106,7 +102,7 @@ impl<T: Config> Extrinsics<T> {
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<E: StaticExtrinsic>(
&self,
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, Error>> {
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, ExtrinsicError>> {
self.iter().filter_map(|details| {
match details.as_extrinsic::<E>() {
// Failed to decode extrinsic:
@@ -120,18 +116,22 @@ impl<T: Config> Extrinsics<T> {
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the first extrinsic found which decodes to the provided `E` type.
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
pub fn find_first<E: StaticExtrinsic>(
&self,
) -> Result<Option<FoundExtrinsic<T, E>>, ExtrinsicError> {
self.find::<E>().next().transpose()
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
pub fn find_last<E: StaticExtrinsic>(
&self,
) -> Result<Option<FoundExtrinsic<T, E>>, ExtrinsicError> {
self.find::<E>().last().transpose()
}
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, ExtrinsicError> {
Ok(self.find::<E>().next().transpose()?.is_some())
}
}
@@ -264,61 +264,63 @@ where
}
/// The index of the extrinsic variant that the extrinsic originated from.
pub fn variant_index(&self) -> u8 {
pub fn call_index(&self) -> u8 {
self.decoded_info().call_index()
}
/// The name of the pallet from whence the extrinsic originated.
pub fn pallet_name(&self) -> Result<&str, Error> {
Ok(self.extrinsic_metadata()?.pallet.name())
pub fn pallet_name(&self) -> &str {
self.decoded_info().pallet_name()
}
/// The name of the call (ie the name of the variant that it corresponds to).
pub fn variant_name(&self) -> Result<&str, Error> {
Ok(&self.extrinsic_metadata()?.variant.name)
}
/// Fetch the metadata for this extrinsic.
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails<'_>, Error> {
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
let variant = pallet
.call_variant_by_index(self.variant_index())
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
Ok(ExtrinsicMetadataDetails { pallet, variant })
pub fn call_name(&self) -> &str {
self.decoded_info().call_name()
}
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
/// type which represents the named or unnamed fields that were present in the extrinsic.
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, ExtrinsicError> {
let bytes = &mut self.field_bytes();
let extrinsic_metadata = self.extrinsic_metadata()?;
let mut fields = extrinsic_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let mut fields = self.decoded_info().call_data().map(|d| {
let name = if d.name().is_empty() {
None
} else {
Some(d.name())
};
scale_decode::Field::new(*d.ty(), name)
});
let decoded =
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| {
ExtrinsicError::CannotDecodeFields {
extrinsic_index: self.index as usize,
error: e,
}
})?;
Ok(decoded)
}
/// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields.
/// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
let extrinsic_metadata = self.extrinsic_metadata()?;
if extrinsic_metadata.pallet.name() == E::PALLET
&& extrinsic_metadata.variant.name == E::CALL
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, ExtrinsicError> {
if self.decoded_info().pallet_name() == E::PALLET
&& self.decoded_info().call_name() == E::CALL
{
let mut fields = extrinsic_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let mut fields = self.decoded_info().call_data().map(|d| {
let name = if d.name().is_empty() {
None
} else {
Some(d.name())
};
scale_decode::Field::new(*d.ty(), name)
});
let decoded =
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())
.map_err(|e| ExtrinsicError::CannotDecodeFields {
extrinsic_index: self.index as usize,
error: e,
})?;
Ok(Some(decoded))
} else {
Ok(None)
@@ -328,12 +330,16 @@ where
/// Attempt to decode these [`ExtrinsicDetails`] into an outer call enum type (which includes
/// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
/// type for this is exposed via static codegen as a root level `Call` type.
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
let decoded = E::decode_as_type(
&mut &self.call_bytes()[..],
self.metadata.outer_enums().call_enum_ty(),
self.metadata.types(),
)?;
)
.map_err(|e| ExtrinsicError::CannotDecodeIntoRootExtrinsic {
extrinsic_index: self.index as usize,
error: e,
})?;
Ok(decoded)
}
@@ -351,14 +357,6 @@ pub struct FoundExtrinsic<T: Config, E> {
pub value: E,
}
/// Details for the given extrinsic plucked from the metadata.
pub struct ExtrinsicMetadataDetails<'a> {
/// Metadata for the pallet that the extrinsic belongs to.
pub pallet: PalletMetadata<'a>,
/// Metadata for the variant which describes the pallet extrinsics.
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
#[cfg(test)]
mod tests {
use super::*;
@@ -488,7 +486,7 @@ mod tests {
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
Metadata::from(metadata)
metadata
}
#[test]
@@ -496,7 +494,7 @@ mod tests {
let metadata = metadata();
// Except our metadata to contain the registered types.
let pallet = metadata.pallet_by_index(0).expect("pallet exists");
let pallet = metadata.pallet_by_call_index(0).expect("pallet exists");
let extrinsic = pallet
.call_variant_by_index(2)
.expect("metadata contains the RuntimeCall enum with this pallet");
@@ -513,12 +511,10 @@ mod tests {
let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![]], metadata);
assert_matches!(
result.err(),
Some(crate::Error::Block(
crate::error::BlockError::ExtrinsicDecodeError {
extrinsic_index: 0,
error: _
}
))
Some(crate::error::ExtrinsicDecodeErrorAt {
extrinsic_index: 0,
error: _
})
);
}
@@ -533,12 +529,12 @@ mod tests {
assert_matches!(
result.err(),
Some(crate::Error::Block(
crate::error::BlockError::ExtrinsicDecodeError {
extrinsic_index: 0,
error: ExtrinsicDecodeError::VersionNotSupported(3),
}
))
Some(crate::error::ExtrinsicDecodeErrorAt {
extrinsic_index: 0,
error: ExtrinsicDecodeErrorAtReason::DecodeError(
ExtrinsicDecodeError::VersionNotSupported(3)
),
})
);
}
@@ -611,20 +607,10 @@ mod tests {
assert_eq!(extrinsic.index(), 0);
assert_eq!(extrinsic.pallet_index(), 0);
assert_eq!(
extrinsic
.pallet_name()
.expect("Valid metadata contains pallet name"),
"Test"
);
assert_eq!(extrinsic.pallet_name(), "Test");
assert_eq!(extrinsic.variant_index(), 2);
assert_eq!(
extrinsic
.variant_name()
.expect("Valid metadata contains variant name"),
"TestCall"
);
assert_eq!(extrinsic.call_index(), 2);
assert_eq!(extrinsic.call_name(), "TestCall");
// Decode the extrinsic to the root enum.
let decoded_extrinsic = extrinsic
+10 -11
View File
@@ -15,7 +15,7 @@
//!
//! use subxt_macro::subxt;
//! use subxt_core::blocks;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//! use subxt_core::config::PolkadotConfig;
//! use alloc::vec;
//!
@@ -28,7 +28,7 @@
//!
//! // Some metadata we'd like to use to help us decode extrinsics:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Some extrinsics we'd like to decode:
//! let ext_bytes = vec![
@@ -45,14 +45,14 @@
//!
//! // We can iterate over them and decode various details out of them.
//! for ext in exts.iter() {
//! println!("Pallet: {}", ext.pallet_name().unwrap());
//! println!("Call: {}", ext.variant_name().unwrap());
//! println!("Pallet: {}", ext.pallet_name());
//! println!("Call: {}", ext.call_name());
//! }
//!
//! # let ext_details: Vec<_> = exts.iter()
//! # .map(|ext| {
//! # let pallet = ext.pallet_name().unwrap().to_string();
//! # let call = ext.variant_name().unwrap().to_string();
//! # let pallet = ext.pallet_name().to_string();
//! # let call = ext.call_name().to_string();
//! # (pallet, call)
//! # })
//! # .collect();
@@ -70,14 +70,13 @@ mod static_extrinsic;
use crate::Metadata;
use crate::config::Config;
use crate::error::Error;
use crate::error::ExtrinsicDecodeErrorAt;
pub use crate::error::ExtrinsicError;
use alloc::vec::Vec;
pub use crate::error::BlockError;
pub use extrinsic_transaction_extensions::{
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
};
pub use extrinsics::{ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic};
pub use extrinsics::{ExtrinsicDetails, Extrinsics, FoundExtrinsic};
pub use static_extrinsic::StaticExtrinsic;
/// Instantiate a new [`Extrinsics`] object, given a vector containing each extrinsic hash (in the
@@ -87,6 +86,6 @@ pub use static_extrinsic::StaticExtrinsic;
pub fn decode_from<T: Config>(
extrinsics: Vec<Vec<u8>>,
metadata: Metadata,
) -> Result<Extrinsics<T>, Error> {
) -> Result<Extrinsics<T>, ExtrinsicDecodeErrorAt> {
Extrinsics::decode_from(extrinsics, metadata)
}
+1 -1
View File
@@ -5,8 +5,8 @@
//! A couple of client types that we use elsewhere.
use crate::{
Metadata,
config::{Config, HashFor},
metadata::Metadata,
};
use derive_where::derive_where;
+47 -13
View File
@@ -4,17 +4,16 @@
//! Construct addresses to access constants with.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use alloc::borrow::Cow;
use alloc::string::String;
use derive_where::derive_where;
use scale_decode::DecodeAsType;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait Address {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
type Target: DecodeAsType;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
@@ -30,22 +29,54 @@ pub trait Address {
}
}
// Any reference to an address is a valid address.
impl<A: Address + ?Sized> Address for &'_ A {
type Target = A::Target;
fn pallet_name(&self) -> &str {
A::pallet_name(*self)
}
fn constant_name(&self) -> &str {
A::constant_name(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
A::validation_hash(*self)
}
}
// (str, str) and similar are valid addresses.
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
type Target = scale_value::Value;
fn pallet_name(&self) -> &str {
self.0.as_ref()
}
fn constant_name(&self) -> &str {
self.1.as_ref()
}
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents the address of a constant.
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct DefaultAddress<ReturnTy> {
pub struct StaticAddress<ReturnTy> {
pallet_name: Cow<'static, str>,
constant_name: Cow<'static, str>,
constant_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<ReturnTy>,
}
/// The type of address used by our static codegen.
pub type StaticAddress<ReturnTy> = DefaultAddress<ReturnTy>;
/// The type of address typically used to return dynamic constant values.
pub type DynamicAddress = DefaultAddress<DecodedValueThunk>;
/// A dynamic lookup address to access a constant.
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy>;
impl<ReturnTy> DefaultAddress<ReturnTy> {
/// Create a new [`DefaultAddress`] to use to look up a constant.
impl<ReturnTy> StaticAddress<ReturnTy> {
/// Create a new [`StaticAddress`] to use to look up a constant.
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
@@ -55,7 +86,7 @@ impl<ReturnTy> DefaultAddress<ReturnTy> {
}
}
/// Create a new [`DefaultAddress`] that will be validated
/// Create a new [`StaticAddress`] that will be validated
/// against node metadata using the hash given.
#[doc(hidden)]
pub fn new_static(
@@ -82,7 +113,7 @@ impl<ReturnTy> DefaultAddress<ReturnTy> {
}
}
impl<ReturnTy: DecodeWithMetadata> Address for DefaultAddress<ReturnTy> {
impl<ReturnTy: DecodeAsType> Address for StaticAddress<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
@@ -99,6 +130,9 @@ impl<ReturnTy: DecodeWithMetadata> Address for DefaultAddress<ReturnTy> {
}
/// Construct a new dynamic constant lookup.
pub fn dynamic(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> DynamicAddress {
pub fn dynamic<ReturnTy: DecodeAsType>(
pallet_name: impl Into<String>,
constant_name: impl Into<String>,
) -> DynamicAddress<ReturnTy> {
DynamicAddress::new(pallet_name, constant_name)
}
+44 -20
View File
@@ -12,7 +12,7 @@
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::constants;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -23,7 +23,7 @@
//!
//! // Some metadata we'd like to access constants in:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // We can use a static address to obtain some constant:
//! let address = polkadot::constants().balances().existential_deposit();
@@ -40,26 +40,32 @@
pub mod address;
use crate::Metadata;
use crate::error::ConstantError;
use address::Address;
use alloc::borrow::ToOwned;
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
use alloc::string::ToString;
use alloc::vec::Vec;
use frame_decode::constants::ConstantTypeInfo;
use scale_decode::IntoVisitor;
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
/// that the shape of the constant value is the same as the shape expected by the static address.
///
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
/// shape of the constant value), this just returns `Ok(())`
pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), ConstantError> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = metadata
.pallet_by_name_err(address.pallet_name())?
.pallet_by_name(address.pallet_name())
.ok_or_else(|| ConstantError::PalletNameNotFound(address.pallet_name().to_string()))?
.constant_hash(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
.ok_or_else(|| ConstantError::ConstantNameNotFound {
pallet_name: address.pallet_name().to_string(),
constant_name: address.constant_name().to_owned(),
})?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
return Err(ConstantError::IncompatibleCodegen);
}
}
Ok(())
@@ -67,19 +73,37 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
/// Fetch a constant out of the metadata given a constant address. If the `address` has been
/// statically generated, this will validate that the constant shape is as expected, too.
pub fn get<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<Addr::Target, Error> {
pub fn get<Addr: Address>(
address: Addr,
metadata: &Metadata,
) -> Result<Addr::Target, ConstantError> {
// 1. Validate constant shape if hash given:
validate(address, metadata)?;
validate(&address, metadata)?;
// 2. Attempt to decode the constant into the type given:
let constant = metadata
.pallet_by_name_err(address.pallet_name())?
.constant_by_name(address.constant_name())
.ok_or_else(|| MetadataError::ConstantNameNotFound(address.constant_name().to_owned()))?;
let value = <Addr::Target as DecodeWithMetadata>::decode_with_metadata(
&mut constant.value(),
constant.ty(),
let constant = frame_decode::constants::decode_constant(
address.pallet_name(),
address.constant_name(),
metadata,
)?;
Ok(value)
metadata.types(),
Addr::Target::into_visitor(),
)
.map_err(ConstantError::CouldNotDecodeConstant)?;
Ok(constant)
}
/// Access the bytes of a constant by the address it is registered under.
pub fn get_bytes<Addr: Address>(
address: Addr,
metadata: &Metadata,
) -> Result<Vec<u8>, ConstantError> {
// 1. Validate custom value shape if hash given:
validate(&address, metadata)?;
// 2. Return the underlying bytes:
let constant = metadata
.constant_info(address.pallet_name(), address.constant_name())
.map_err(|e| ConstantError::ConstantInfoError(e.into_owned()))?;
Ok(constant.bytes.to_vec())
}
+54 -19
View File
@@ -4,22 +4,23 @@
//! Construct addresses to access custom values with.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use alloc::borrow::Cow;
use alloc::string::String;
use derive_where::derive_where;
use scale_decode::DecodeAsType;
/// Use this with [`Address::IsDecodable`].
pub use crate::utils::Yes;
pub use crate::utils::{Maybe, No, NoMaybe};
/// This represents the address of a custom value in the metadata.
/// Anything that implements it can be used to fetch custom values from the metadata.
/// The trait is implemented by [`str`] for dynamic lookup and [`StaticAddress`] for static queries.
pub trait Address {
/// The type of the custom value.
type Target: DecodeWithMetadata;
type Target: DecodeAsType;
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
/// Should be `()` for custom values, that have an invalid type id.
type IsDecodable;
/// Should be `No` for custom values, that have an invalid type id.
type IsDecodable: NoMaybe;
/// the name (key) by which the custom value can be accessed in the metadata.
fn name(&self) -> &str;
@@ -30,9 +31,24 @@ pub trait Address {
}
}
// Any reference to an address is a valid address
impl<A: Address + ?Sized> Address for &'_ A {
type Target = A::Target;
type IsDecodable = A::IsDecodable;
fn name(&self) -> &str {
A::name(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
A::validation_hash(*self)
}
}
// Support plain strings for looking up custom values.
impl Address for str {
type Target = DecodedValueThunk;
type IsDecodable = Yes;
type Target = scale_value::Value;
type IsDecodable = Maybe;
fn name(&self) -> &str {
self
@@ -42,19 +58,31 @@ impl Address for str {
/// A static address to a custom value.
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct StaticAddress<ReturnTy, IsDecodable> {
name: &'static str,
name: Cow<'static, str>,
hash: Option<[u8; 32]>,
phantom: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
marker: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
}
/// A dynamic address to a custom value.
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy, Maybe>;
impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
#[doc(hidden)]
/// Creates a new StaticAddress.
pub fn new_static(name: &'static str, hash: [u8; 32]) -> StaticAddress<ReturnTy, IsDecodable> {
StaticAddress::<ReturnTy, IsDecodable> {
name,
pub fn new_static(name: &'static str, hash: [u8; 32]) -> Self {
Self {
name: Cow::Borrowed(name),
hash: Some(hash),
phantom: core::marker::PhantomData,
marker: core::marker::PhantomData,
}
}
/// Create a new [`StaticAddress`]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into().into(),
hash: None,
marker: core::marker::PhantomData,
}
}
@@ -63,20 +91,27 @@ impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
Self {
name: self.name,
hash: None,
phantom: self.phantom,
marker: self.marker,
}
}
}
impl<R: DecodeWithMetadata, Y> Address for StaticAddress<R, Y> {
type Target = R;
type IsDecodable = Y;
impl<Target: DecodeAsType, IsDecodable: NoMaybe> Address for StaticAddress<Target, IsDecodable> {
type Target = Target;
type IsDecodable = IsDecodable;
fn name(&self) -> &str {
self.name
&self.name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.hash
}
}
/// Construct a new dynamic custom value lookup.
pub fn dynamic<ReturnTy: DecodeAsType>(
custom_value_name: impl Into<String>,
) -> DynamicAddress<ReturnTy> {
DynamicAddress::new(custom_value_name)
}
+32 -28
View File
@@ -12,7 +12,7 @@
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::custom_values;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -23,7 +23,7 @@
//!
//! // Some metadata we'd like to access custom values in:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // At the moment, we don't expect to see any custom values in the metadata
//! // for Polkadot, so this will return an error:
@@ -32,61 +32,64 @@
pub mod address;
use crate::utils::Yes;
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
use crate::utils::Maybe;
use crate::{Metadata, error::CustomValueError};
use address::Address;
use alloc::vec::Vec;
use frame_decode::custom_values::CustomValueTypeInfo;
use scale_decode::IntoVisitor;
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
pub fn validate<Addr: Address + ?Sized>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), CustomValueError> {
if let Some(actual_hash) = address.validation_hash() {
let custom = metadata.custom();
let custom_value = custom
.get(address.name())
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().into()))?;
.ok_or_else(|| CustomValueError::NotFound(address.name().into()))?;
let expected_hash = custom_value.hash();
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
return Err(CustomValueError::IncompatibleCodegen);
}
}
if metadata.custom().get(address.name()).is_none() {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
/// or a static address from the generated static interface to get a value of a static type returned.
pub fn get<Addr: Address<IsDecodable = Yes> + ?Sized>(
address: &Addr,
pub fn get<Addr: Address<IsDecodable = Maybe>>(
address: Addr,
metadata: &Metadata,
) -> Result<Addr::Target, Error> {
) -> Result<Addr::Target, CustomValueError> {
// 1. Validate custom value shape if hash given:
validate(address, metadata)?;
validate(&address, metadata)?;
// 2. Attempt to decode custom value:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
let value = <Addr::Target as DecodeWithMetadata>::decode_with_metadata(
&mut custom_value.bytes(),
custom_value.type_id(),
let value = frame_decode::custom_values::decode_custom_value(
address.name(),
metadata,
)?;
metadata.types(),
Addr::Target::into_visitor(),
)
.map_err(CustomValueError::CouldNotDecodeCustomValue)?;
Ok(value)
}
/// Access the bytes of a custom value by the address it is registered under.
pub fn get_bytes<Addr: Address + ?Sized>(
address: &Addr,
pub fn get_bytes<Addr: Address>(
address: Addr,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
) -> Result<Vec<u8>, CustomValueError> {
// 1. Validate custom value shape if hash given:
validate(address, metadata)?;
validate(&address, metadata)?;
// 2. Return the underlying bytes:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
Ok(custom_value.bytes().to_vec())
let custom_value = metadata
.custom_value_info(address.name())
.map_err(|e| CustomValueError::NotFound(e.not_found))?;
Ok(custom_value.bytes.to_vec())
}
#[cfg(test)]
@@ -154,7 +157,7 @@ mod tests {
};
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
Metadata::from(metadata)
metadata
}
#[test]
@@ -162,8 +165,9 @@ mod tests {
let metadata = mock_metadata();
assert!(custom_values::get("Invalid Address", &metadata).is_err());
let person_decoded_value_thunk = custom_values::get("Mr. Robot", &metadata).unwrap();
let person: Person = person_decoded_value_thunk.as_type().unwrap();
let person_addr = custom_values::address::dynamic::<Person>("Mr. Robot");
let person = custom_values::get(&person_addr, &metadata).unwrap();
assert_eq!(
person,
Person {
+2 -62
View File
@@ -5,17 +5,8 @@
//! This module provides the entry points to create dynamic
//! transactions, storage and constant lookups.
use crate::metadata::{DecodeWithMetadata, Metadata};
use alloc::vec::Vec;
use scale_decode::DecodeAsType;
pub use scale_value::{At, Value};
/// A [`scale_value::Value`] type endowed with contextual information
/// regarding what type was used to decode each part of it. This implements
/// [`crate::metadata::DecodeWithMetadata`], and is used as a return type
/// for dynamic requests.
pub type DecodedValue = scale_value::Value<u32>;
// Submit dynamic transactions.
pub use crate::tx::payload::dynamic as tx;
@@ -31,56 +22,5 @@ pub use crate::runtime_api::payload::dynamic as runtime_api_call;
// Execute View Function API function call dynamically.
pub use crate::view_functions::payload::dynamic as view_function_call;
/// This is the result of making a dynamic request to a node. From this,
/// we can return the raw SCALE bytes that we were handed back, or we can
/// complete the decoding of the bytes into a [`DecodedValue`] type.
pub struct DecodedValueThunk {
type_id: u32,
metadata: Metadata,
scale_bytes: Vec<u8>,
}
impl DecodeWithMetadata for DecodedValueThunk {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self, scale_decode::Error> {
let mut v = Vec::with_capacity(bytes.len());
v.extend_from_slice(bytes);
*bytes = &[];
Ok(DecodedValueThunk {
type_id,
metadata: metadata.clone(),
scale_bytes: v,
})
}
}
impl DecodedValueThunk {
/// Return the SCALE encoded bytes handed back from the node.
pub fn into_encoded(self) -> Vec<u8> {
self.scale_bytes
}
/// Return the SCALE encoded bytes handed back from the node without taking ownership of them.
pub fn encoded(&self) -> &[u8] {
&self.scale_bytes
}
/// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type.
pub fn to_value(&self) -> Result<DecodedValue, scale_decode::Error> {
let val = scale_value::scale::decode_as_type(
&mut &*self.scale_bytes,
self.type_id,
self.metadata.types(),
)?;
Ok(val)
}
/// decode the `DecodedValueThunk` into a concrete type.
pub fn as_type<T: DecodeAsType>(&self) -> Result<T, scale_decode::Error> {
T::decode_as_type(
&mut &self.scale_bytes[..],
self.type_id,
self.metadata.types(),
)
}
}
/// Obtain a custom value from the metadata.
pub use crate::custom_values::address::dynamic as custom_value;
+248 -156
View File
@@ -6,53 +6,238 @@
use alloc::boxed::Box;
use alloc::string::String;
use subxt_metadata::StorageHasher;
use alloc::vec::Vec;
use thiserror::Error as DeriveError;
/// The error emitted when something goes wrong.
#[derive(Debug, DeriveError)]
#[allow(missing_docs)]
pub enum Error {
/// Codec error.
#[error("Codec error: {0}")]
Codec(codec::Error),
/// Metadata error.
#[error(transparent)]
Metadata(#[from] MetadataError),
/// Storage address error.
StorageError(#[from] StorageError),
#[error(transparent)]
StorageAddress(#[from] StorageAddressError),
/// Error decoding to a [`crate::dynamic::Value`].
#[error("Error decoding into dynamic value: {0}")]
Decode(#[from] scale_decode::Error),
/// Error encoding from a [`crate::dynamic::Value`].
#[error("Error encoding from dynamic value: {0}")]
Encode(#[from] scale_encode::Error),
/// Error constructing an extrinsic.
#[error("Error constructing transaction: {0}")]
Extrinsic(#[from] ExtrinsicError),
/// Block body error.
#[error("Error working with block_body: {0}")]
Block(#[from] BlockError),
#[error(transparent)]
Constant(#[from] ConstantError),
#[error(transparent)]
CustomValue(#[from] CustomValueError),
#[error(transparent)]
RuntimeApi(#[from] RuntimeApiError),
#[error(transparent)]
ViewFunction(#[from] ViewFunctionError),
#[error(transparent)]
Events(#[from] EventsError),
}
impl From<scale_decode::visitor::DecodeError> for Error {
fn from(err: scale_decode::visitor::DecodeError) -> Error {
Error::Decode(err.into())
}
}
// TODO: when `codec::Error` implements `core::Error`
// remove this impl and replace it by thiserror #[from]
impl From<codec::Error> for Error {
fn from(err: codec::Error) -> Error {
Error::Codec(err)
}
}
/// Block error
#[derive(Debug, DeriveError)]
pub enum BlockError {
/// Leftover bytes found after decoding the extrinsic.
#[non_exhaustive]
#[allow(missing_docs)]
pub enum EventsError {
#[error("Can't decode event: can't decode phase: {0}")]
CannotDecodePhase(codec::Error),
#[error("Can't decode event: can't decode pallet index: {0}")]
CannotDecodePalletIndex(codec::Error),
#[error("Can't decode event: can't decode variant index: {0}")]
CannotDecodeVariantIndex(codec::Error),
#[error("Can't decode event: can't find pallet with index {0}")]
CannotFindPalletWithIndex(u8),
#[error(
"Can't decode event: can't find variant with index {variant_index} in pallet {pallet_name}"
)]
CannotFindVariantWithIndex {
pallet_name: String,
variant_index: u8,
},
#[error("Can't decode field {field_name:?} in event {pallet_name}.{event_name}: {reason}")]
CannotDecodeFieldInEvent {
pallet_name: String,
event_name: String,
field_name: String,
reason: scale_decode::visitor::DecodeError,
},
#[error("Can't decode event topics: {0}")]
CannotDecodeEventTopics(codec::Error),
#[error("Can't decode the fields of event {pallet_name}.{event_name}: {reason}")]
CannotDecodeEventFields {
pallet_name: String,
event_name: String,
reason: scale_decode::Error,
},
#[error("Can't decode event {pallet_name}.{event_name} to Event enum: {reason}")]
CannotDecodeEventEnum {
pallet_name: String,
event_name: String,
reason: scale_decode::Error,
},
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ViewFunctionError {
#[error("The static View Function address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find View Function: pallet {0} not found")]
PalletNotFound(String),
#[error("Can't find View Function {function_name} in pallet {pallet_name}")]
ViewFunctionNotFound {
pallet_name: String,
function_name: String,
},
#[error("Failed to encode View Function inputs: {0}")]
CouldNotEncodeInputs(frame_decode::view_functions::ViewFunctionInputsEncodeError),
#[error("Failed to decode View Function: {0}")]
CouldNotDecodeResponse(frame_decode::view_functions::ViewFunctionDecodeError<u32>),
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum RuntimeApiError {
#[error("The static Runtime API address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Runtime API trait not found: {0}")]
TraitNotFound(String),
#[error("Runtime API method {method_name} not found in trait {trait_name}")]
MethodNotFound {
trait_name: String,
method_name: String,
},
#[error("Failed to encode Runtime API inputs: {0}")]
CouldNotEncodeInputs(frame_decode::runtime_apis::RuntimeApiInputsEncodeError),
#[error("Failed to decode Runtime API: {0}")]
CouldNotDecodeResponse(frame_decode::runtime_apis::RuntimeApiDecodeError<u32>),
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum CustomValueError {
#[error("The static custom value address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("The custom value '{0}' was not found")]
NotFound(String),
#[error("Failed to decode custom value: {0}")]
CouldNotDecodeCustomValue(frame_decode::custom_values::CustomValueDecodeError<u32>),
}
/// Something went wrong working with a constant.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ConstantError {
#[error("The static constant address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find constant: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error(
"Constant '{constant_name}' not found in pallet {pallet_name} in the live chain metadata"
)]
ConstantNameNotFound {
pallet_name: String,
constant_name: String,
},
#[error("Failed to decode constant: {0}")]
CouldNotDecodeConstant(frame_decode::constants::ConstantDecodeError<u32>),
#[error("Cannot obtain constant information from metadata: {0}")]
ConstantInfoError(frame_decode::constants::ConstantInfoError<'static>),
}
/// Something went wrong trying to encode or decode a storage address.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageError {
#[error("The static storage address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find storage value: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error(
"Storage entry '{entry_name}' not found in pallet {pallet_name} in the live chain metadata"
)]
StorageEntryNotFound {
pallet_name: String,
entry_name: String,
},
#[error("Cannot obtain storage information from metadata: {0}")]
StorageInfoError(frame_decode::storage::StorageInfoError<'static>),
#[error("Cannot encode storage key: {0}")]
StorageKeyEncodeError(frame_decode::storage::StorageKeyEncodeError),
#[error("Cannot create a key to iterate over a plain entry")]
CannotIterPlainEntry {
pallet_name: String,
entry_name: String,
},
#[error(
"Wrong number of key parts provided to iterate a storage address. We expected at most {max_expected} key parts but got {got} key parts"
)]
WrongNumberOfKeyPartsProvidedForIterating { max_expected: usize, got: usize },
#[error(
"Wrong number of key parts provided to fetch a storage address. We expected {expected} key parts but got {got} key parts"
)]
WrongNumberOfKeyPartsProvidedForFetching { expected: usize, got: usize },
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageKeyError {
#[error("Can't decode the storage key: {error}")]
StorageKeyDecodeError {
bytes: Vec<u8>,
error: frame_decode::storage::StorageKeyDecodeError<u32>,
},
#[error("Can't decode the values from the storage key: {0}")]
CannotDecodeValuesInKey(frame_decode::storage::StorageKeyValueDecodeError),
#[error(
"Cannot decode storage key: there were leftover bytes, indicating that the decoding failed"
)]
LeftoverBytes { bytes: Vec<u8> },
#[error("Can't decode a single value from the storage key part at index {index}: {error}")]
CannotDecodeValueInKey {
index: usize,
error: scale_decode::Error,
},
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageValueError {
#[error("Cannot decode storage value: {0}")]
CannotDecode(frame_decode::storage::StorageValueDecodeError<u32>),
#[error(
"Cannot decode storage value: there were leftover bytes, indicating that the decoding failed"
)]
LeftoverBytes { bytes: Vec<u8> },
}
/// An error that can be encountered when constructing a transaction.
#[derive(Debug, DeriveError)]
#[allow(missing_docs)]
pub enum ExtrinsicError {
#[error("The extrinsic payload is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find extrinsic: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error("Can't find extrinsic: call name {call_name} doesn't exist in pallet {pallet_name}")]
CallNameNotFound {
pallet_name: String,
call_name: String,
},
#[error("Can't encode the extrinsic call data: {0}")]
CannotEncodeCallData(scale_encode::Error),
#[error("Subxt does not support the extrinsic versions expected by the chain")]
UnsupportedVersion,
#[error("Cannot construct the required transaction extensions: {0}")]
Params(#[from] ExtrinsicParamsError),
#[error("Cannot decode transaction extension '{name}': {error}")]
CouldNotDecodeTransactionExtension {
/// The extension name.
name: String,
/// The decode error.
error: scale_decode::Error,
},
#[error(
"After decoding the extrinsic at index {extrinsic_index}, {num_leftover_bytes} bytes were left, suggesting that decoding may have failed"
)]
@@ -62,140 +247,49 @@ pub enum BlockError {
/// Number of bytes leftover after decoding the extrinsic.
num_leftover_bytes: usize,
},
/// Something went wrong decoding the extrinsic.
#[error("Failed to decode extrinsic at index {extrinsic_index}: {error}")]
ExtrinsicDecodeError {
/// Index of the extrinsic that failed to decode.
#[error("{0}")]
ExtrinsicDecodeErrorAt(#[from] ExtrinsicDecodeErrorAt),
#[error("Failed to decode the fields of an extrinsic at index {extrinsic_index}: {error}")]
CannotDecodeFields {
/// Index of the extrinsic whose fields we could not decode
extrinsic_index: usize,
/// The decode error.
error: ExtrinsicDecodeError,
error: scale_decode::Error,
},
#[error("Failed to decode the extrinsic at index {extrinsic_index} to a root enum: {error}")]
CannotDecodeIntoRootExtrinsic {
/// Index of the extrinsic that we failed to decode
extrinsic_index: usize,
/// The decode error.
error: scale_decode::Error,
},
}
/// An alias for [`frame_decode::extrinsics::ExtrinsicDecodeError`].
///
pub type ExtrinsicDecodeError = frame_decode::extrinsics::ExtrinsicDecodeError;
/// Something went wrong trying to access details in the metadata.
#[derive(Clone, Debug, PartialEq, DeriveError)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum MetadataError {
/// The DispatchError type isn't available in the metadata
#[error("The DispatchError type isn't available")]
DispatchErrorNotFound,
/// Type not found in metadata.
#[error("Type with ID {0} not found")]
TypeNotFound(u32),
/// Pallet not found (index).
#[error("Pallet with index {0} not found")]
PalletIndexNotFound(u8),
/// Pallet not found (name).
#[error("Pallet with name {0} not found")]
PalletNameNotFound(String),
/// Variant not found.
#[error("Variant with index {0} not found")]
VariantIndexNotFound(u8),
/// Constant not found.
#[error("Constant with name {0} not found")]
ConstantNameNotFound(String),
/// Call not found.
#[error("Call with name {0} not found")]
CallNameNotFound(String),
/// Runtime trait not found.
#[error("Runtime trait with name {0} not found")]
RuntimeTraitNotFound(String),
/// Runtime method not found.
#[error("Runtime method with name {0} not found")]
RuntimeMethodNotFound(String),
/// View Function not found.
#[error("View Function with query ID {} not found", hex::encode(.0))]
ViewFunctionNotFound([u8; 32]),
/// Call type not found in metadata.
#[error("Call type not found in pallet with index {0}")]
CallTypeNotFoundInPallet(u8),
/// Event type not found in metadata.
#[error("Event type not found in pallet with index {0}")]
EventTypeNotFoundInPallet(u8),
/// Storage details not found in metadata.
#[error("Storage details not found in pallet with name {0}")]
StorageNotFoundInPallet(String),
/// Storage entry not found.
#[error("Storage entry {0} not found")]
StorageEntryNotFound(String),
/// The generated interface used is not compatible with the node.
#[error("The generated code is not compatible with the node")]
IncompatibleCodegen,
/// Custom value not found.
#[error("Custom value with name {0} not found")]
CustomValueNameNotFound(String),
#[allow(missing_docs)]
#[error("Cannot decode extrinsic at index {extrinsic_index}: {error}")]
pub struct ExtrinsicDecodeErrorAt {
pub extrinsic_index: usize,
pub error: ExtrinsicDecodeErrorAtReason,
}
/// Something went wrong trying to encode or decode a storage address.
#[derive(Clone, Debug, DeriveError)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageAddressError {
/// Storage lookup does not have the expected number of keys.
#[error("Storage lookup requires {expected} keys but more keys have been provided.")]
TooManyKeys {
/// The number of keys provided in the storage address.
expected: usize,
},
/// This storage entry in the metadata does not have the correct number of hashers to fields.
#[error("Storage entry in metadata does not have the correct number of hashers to fields")]
WrongNumberOfHashers {
/// The number of hashers in the metadata for this storage entry.
hashers: usize,
/// The number of fields in the metadata for this storage entry.
fields: usize,
},
/// We weren't given enough bytes to decode the storage address/key.
#[error("Not enough remaining bytes to decode the storage address/key")]
NotEnoughBytes,
/// We have leftover bytes after decoding the storage address.
#[error("We have leftover bytes after decoding the storage address")]
TooManyBytes,
/// The bytes of a storage address are not the expected address for decoding the storage keys of the address.
#[error(
"Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata"
)]
UnexpectedAddressBytes,
/// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose.
#[error(
"An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher"
)]
HasherCannotReconstructKey {
/// Type id of the key's type.
ty_id: u32,
/// The invalid hasher that caused this error.
hasher: StorageHasher,
},
}
/// An error that can be encountered when constructing a transaction.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
pub enum ExtrinsicError {
/// Transaction version not supported by Subxt.
#[error("Subxt does not support the extrinsic versions expected by the chain")]
UnsupportedVersion,
/// Issue encoding transaction extensions.
#[error("Cannot construct the required transaction extensions: {0}")]
Params(#[from] ExtrinsicParamsError),
}
impl From<ExtrinsicParamsError> for Error {
fn from(value: ExtrinsicParamsError) -> Self {
Error::Extrinsic(value.into())
}
#[allow(missing_docs)]
pub enum ExtrinsicDecodeErrorAtReason {
#[error("{0}")]
DecodeError(frame_decode::extrinsics::ExtrinsicDecodeError),
#[error("Leftover bytes")]
LeftoverBytes(Vec<u8>),
}
/// An error that can be emitted when trying to construct an instance of [`crate::config::ExtrinsicParams`],
/// encode data from the instance, or match on signed extensions.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ExtrinsicParamsError {
/// 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).
#[error("Cannot find type id '{type_id} in the metadata (context: {context})")]
MissingTypeId {
/// Type ID.
@@ -203,10 +297,8 @@ pub enum ExtrinsicParamsError {
/// 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.
#[error("The chain expects a signed extension with the name {0}, but we did not provide one")]
UnknownTransactionExtension(String),
/// Some custom error.
#[error("Error constructing extrinsic parameters: {0}")]
Custom(Box<dyn core::error::Error + Send + Sync + 'static>),
}
+63 -27
View File
@@ -10,7 +10,8 @@
//! use subxt_macro::subxt;
//! use subxt_core::config::PolkadotConfig;
//! use subxt_core::events;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//! use subxt_core::dynamic::Value;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -21,7 +22,7 @@
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../artifacts/polkadot_metadata_full.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Some bytes representing events (located in System.Events storage):
//! let event_bytes = hex::decode("1c00000000000000a2e9b53d5517020000000100000000000310c96d901d0102000000020000000408d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a030000000000000000000000000000020000000402d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48102700000000000000000000000000000000020000000407be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25fbeea5a030000000000000000000000000000020000002100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a03000000000000000000000000000000000000000000000000000000000000020000000000426df03e00000000").unwrap();
@@ -34,10 +35,11 @@
//! let ev = ev.unwrap();
//! println!("Index: {}", ev.index());
//! println!("Name: {}.{}", ev.pallet_name(), ev.variant_name());
//! println!("Fields: {:?}", ev.field_values().unwrap());
//! println!("Fields: {:?}", ev.decode_as_fields::<Value>().unwrap());
//! }
//! ```
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use codec::{Compact, Decode, Encode};
@@ -46,9 +48,9 @@ use scale_decode::{DecodeAsFields, DecodeAsType};
use subxt_metadata::PalletMetadata;
use crate::{
Error, Metadata,
Metadata,
config::{Config, HashFor},
error::MetadataError,
error::EventsError,
};
/// Create a new [`Events`] instance from the given bytes.
@@ -148,7 +150,7 @@ impl<T: Config> Events<T> {
// use of it with our `FilterEvents` stuff.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
) -> impl Iterator<Item = Result<EventDetails<T>, EventsError>> + Send + Sync + 'static {
// The event bytes ignoring the compact encoded length on the front:
let event_bytes = self.event_bytes.clone();
let metadata = self.metadata.clone();
@@ -184,25 +186,25 @@ impl<T: Config> Events<T> {
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return only those which should decode to the provided `Ev` type.
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
self.iter()
.filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return the first event found which decodes to the provided `Ev` type.
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().next().transpose()
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return the last event found which decodes to the provided `Ev` type.
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().last().transpose()
}
/// Find an event that decodes to the type provided. Returns true if it was found.
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, EventsError> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
@@ -246,23 +248,29 @@ impl<T: Config> EventDetails<T> {
all_bytes: Arc<[u8]>,
start_idx: usize,
index: u32,
) -> Result<EventDetails<T>, Error> {
) -> Result<EventDetails<T>, EventsError> {
let input = &mut &all_bytes[start_idx..];
let phase = Phase::decode(input)?;
let phase = Phase::decode(input).map_err(EventsError::CannotDecodePhase)?;
let event_start_idx = all_bytes.len() - input.len();
let pallet_index = u8::decode(input)?;
let variant_index = u8::decode(input)?;
let pallet_index = u8::decode(input).map_err(EventsError::CannotDecodePalletIndex)?;
let variant_index = u8::decode(input).map_err(EventsError::CannotDecodeVariantIndex)?;
let event_fields_start_idx = all_bytes.len() - input.len();
// Get metadata for the event:
let event_pallet = metadata.pallet_by_index_err(pallet_index)?;
let event_pallet = metadata
.pallet_by_event_index(pallet_index)
.ok_or_else(|| EventsError::CannotFindPalletWithIndex(pallet_index))?;
let event_variant = event_pallet
.event_variant_by_index(variant_index)
.ok_or(MetadataError::VariantIndexNotFound(variant_index))?;
.ok_or_else(|| EventsError::CannotFindVariantWithIndex {
pallet_name: event_pallet.name().to_string(),
variant_index,
})?;
tracing::debug!(
"Decoding Event '{}::{}'",
event_pallet.name(),
@@ -278,14 +286,23 @@ impl<T: Config> EventDetails<T> {
metadata.types(),
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(scale_decode::Error::from)?;
.map_err(|e| EventsError::CannotDecodeFieldInEvent {
pallet_name: event_pallet.name().to_string(),
event_name: event_variant.name.clone(),
field_name: field_metadata
.name
.clone()
.unwrap_or("<unknown>".to_string()),
reason: e,
})?;
}
// the end of the field bytes.
let event_fields_end_idx = all_bytes.len() - input.len();
// topics come after the event data in EventRecord.
let topics = Vec::<HashFor<T>>::decode(input)?;
let topics =
Vec::<HashFor<T>>::decode(input).map_err(EventsError::CannotDecodeEventTopics)?;
// what bytes did we skip over in total, including topics.
let end_idx = all_bytes.len() - input.len();
@@ -342,7 +359,7 @@ impl<T: Config> EventDetails<T> {
pub fn event_metadata(&self) -> EventMetadataDetails<'_> {
let pallet = self
.metadata
.pallet_by_index(self.pallet_index())
.pallet_by_event_index(self.pallet_index())
.expect("event pallet to be found; we did this already during decoding");
let variant = pallet
.event_variant_by_index(self.variant_index())
@@ -367,7 +384,7 @@ impl<T: Config> EventDetails<T> {
/// Decode and provide the event fields back in the form of a [`scale_value::Composite`]
/// type which represents the named or unnamed fields that were present in the event.
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, EventsError> {
let bytes = &mut self.field_bytes();
let event_metadata = self.event_metadata();
@@ -378,14 +395,20 @@ impl<T: Config> EventDetails<T> {
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let decoded =
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| {
EventsError::CannotDecodeEventFields {
pallet_name: event_metadata.pallet.name().to_string(),
event_name: event_metadata.variant.name.clone(),
reason: e,
}
})?;
Ok(decoded)
}
/// Attempt to decode these [`EventDetails`] into a type representing the event fields.
/// Such types are exposed in the codegen as `pallet_name::events::EventName` types.
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, EventsError> {
let ev_metadata = self.event_metadata();
if ev_metadata.pallet.name() == E::PALLET && ev_metadata.variant.name == E::EVENT {
let mut fields = ev_metadata
@@ -394,7 +417,12 @@ impl<T: Config> EventDetails<T> {
.iter()
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let decoded =
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())
.map_err(|e| EventsError::CannotDecodeEventFields {
pallet_name: E::PALLET.to_string(),
event_name: E::EVENT.to_string(),
reason: e,
})?;
Ok(Some(decoded))
} else {
Ok(None)
@@ -404,14 +432,22 @@ impl<T: Config> EventDetails<T> {
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
/// the pallet and event enum variants as well as the event fields). A compatible
/// type for this is exposed via static codegen as a root level `Event` type.
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, Error> {
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, EventsError> {
let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx];
let decoded = E::decode_as_type(
&mut &bytes[..],
self.metadata.outer_enums().event_enum_ty(),
self.metadata.types(),
)?;
)
.map_err(|e| {
let md = self.event_metadata();
EventsError::CannotDecodeEventEnum {
pallet_name: md.pallet.name().to_string(),
event_name: md.variant.name.clone(),
reason: e,
}
})?;
Ok(decoded)
}
@@ -553,7 +589,7 @@ pub(crate) mod test_utils {
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
Metadata::from(metadata)
metadata
}
/// Build an `Events` object for test purposes, based on the details provided,
@@ -623,7 +659,7 @@ mod tests {
expected: TestRawEventDetails,
) {
let actual_fields_no_context: Vec<_> = actual
.field_values()
.decode_as_fields::<scale_value::Composite<()>>()
.expect("can decode field values (2)")
.into_values()
.map(|value| value.remove_context())
+1 -3
View File
@@ -12,7 +12,6 @@
//! - [`blocks`]: decode and explore block bodies.
//! - [`constants`]: access and validate the constant addresses in some metadata.
//! - [`custom_values`]: access and validate the custom value addresses in some metadata.
//! - [`metadata`]: decode bytes into the metadata used throughout this library.
//! - [`storage`]: construct storage request payloads and decode the results you'd get back.
//! - [`tx`]: construct and sign transactions (extrinsics).
//! - [`runtime_api`]: construct runtime API request payloads and decode the results you'd get back.
@@ -31,7 +30,6 @@ pub mod custom_values;
pub mod dynamic;
pub mod error;
pub mod events;
pub mod metadata;
pub mod runtime_api;
pub mod storage;
pub mod tx;
@@ -40,7 +38,7 @@ pub mod view_functions;
pub use config::Config;
pub use error::Error;
pub use metadata::Metadata;
pub use subxt_metadata::Metadata;
/// Re-exports of some of the key external crates.
pub mod ext {
-52
View File
@@ -1,52 +0,0 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::Metadata;
use alloc::vec::Vec;
/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`].
pub trait DecodeWithMetadata: Sized {
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self, scale_decode::Error>;
}
impl<T: scale_decode::DecodeAsType> DecodeWithMetadata for T {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<T, scale_decode::Error> {
let val = T::decode_as_type(bytes, type_id, metadata.types())?;
Ok(val)
}
}
/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`].
pub trait EncodeWithMetadata {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), scale_encode::Error>;
}
impl<T: scale_encode::EncodeAsType> EncodeWithMetadata for T {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.encode_as_type_to(type_id, metadata.types(), bytes)?;
Ok(())
}
}
-81
View File
@@ -1,81 +0,0 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::error::MetadataError;
use alloc::borrow::ToOwned;
use alloc::sync::Arc;
/// A cheaply clone-able representation of the runtime metadata received from a node.
#[derive(Clone, Debug)]
pub struct Metadata {
inner: Arc<subxt_metadata::Metadata>,
}
impl core::ops::Deref for Metadata {
type Target = subxt_metadata::Metadata;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Metadata {
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
pub fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata<'_>, MetadataError> {
self.pallet_by_name(name)
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
}
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
pub fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata<'_>, MetadataError> {
self.pallet_by_index(index)
.ok_or(MetadataError::PalletIndexNotFound(index))
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
pub fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata<'_>, MetadataError> {
self.runtime_api_trait_by_name(name)
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
}
/// Identical to `metadata.custom().get(name)`, but returns an error if the trait is not found.
pub fn custom_value_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::CustomValueMetadata<'_>, MetadataError> {
self.custom()
.get(name)
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
}
}
impl From<subxt_metadata::Metadata> for Metadata {
fn from(md: subxt_metadata::Metadata) -> Self {
Metadata {
inner: Arc::new(md),
}
}
}
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for Metadata {
type Error = subxt_metadata::TryFromError;
fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
subxt_metadata::Metadata::try_from(value).map(Metadata::from)
}
}
impl codec::Decode for Metadata {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
subxt_metadata::Metadata::decode(input).map(Metadata::from)
}
}
-34
View File
@@ -1,34 +0,0 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! A [`Metadata`] type, which is used through this crate.
//!
//! This can be decoded from the bytes handed back from a node when asking for metadata.
//!
//! # Examples
//!
//! ```rust
//! use subxt_core::metadata;
//!
//! // We need to fetch the bytes from somewhere, and then we can decode them:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! ```
mod decode_encode_traits;
mod metadata_type;
use codec::Decode;
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::Metadata;
/// Attempt to decode some bytes into [`Metadata`], returning an error
/// if decoding fails.
///
/// This is a shortcut for importing [`codec::Decode`] and using the
/// implementation of that on [`Metadata`].
pub fn decode_from(bytes: &[u8]) -> Result<Metadata, codec::Error> {
Metadata::decode(&mut &*bytes)
}
+48 -31
View File
@@ -10,7 +10,7 @@
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::runtime_api;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -21,7 +21,7 @@
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Build a storage query to access account information.
//! let payload = polkadot::apis().metadata().metadata_versions();
@@ -43,61 +43,78 @@
pub mod payload;
use crate::error::{Error, MetadataError};
use crate::metadata::{DecodeWithMetadata, Metadata};
use alloc::borrow::ToOwned;
use crate::Metadata;
use crate::error::RuntimeApiError;
use alloc::format;
use alloc::string::String;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use payload::Payload;
use scale_decode::IntoVisitor;
/// Run the validation logic against some runtime API payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the runtime API in question do not exist at all)
pub fn validate<P: Payload>(payload: &P, metadata: &Metadata) -> Result<(), Error> {
let Some(static_hash) = payload.validation_hash() else {
pub fn validate<P: Payload>(payload: P, metadata: &Metadata) -> Result<(), RuntimeApiError> {
let Some(hash) = payload.validation_hash() else {
return Ok(());
};
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
let Some(api_method) = api_trait.method_by_name(payload.method_name()) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
let trait_name = payload.trait_name();
let method_name = payload.method_name();
let runtime_hash = api_method.hash();
if static_hash != runtime_hash {
return Err(MetadataError::IncompatibleCodegen.into());
let api_trait = metadata
.runtime_api_trait_by_name(trait_name)
.ok_or_else(|| RuntimeApiError::TraitNotFound(trait_name.to_string()))?;
let api_method =
api_trait
.method_by_name(method_name)
.ok_or_else(|| RuntimeApiError::MethodNotFound {
trait_name: trait_name.to_string(),
method_name: method_name.to_string(),
})?;
if hash != api_method.hash() {
Err(RuntimeApiError::IncompatibleCodegen)
} else {
Ok(())
}
Ok(())
}
/// Return the name of the runtime API call from the payload.
pub fn call_name<P: Payload>(payload: &P) -> String {
pub fn call_name<P: Payload>(payload: P) -> String {
format!("{}_{}", payload.trait_name(), payload.method_name())
}
/// Return the encoded call args given a runtime API payload.
pub fn call_args<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, Error> {
payload.encode_args(metadata)
pub fn call_args<P: Payload>(payload: P, metadata: &Metadata) -> Result<Vec<u8>, RuntimeApiError> {
let value = frame_decode::runtime_apis::encode_runtime_api_inputs(
payload.trait_name(),
payload.method_name(),
payload.args(),
metadata,
metadata.types(),
)
.map_err(RuntimeApiError::CouldNotEncodeInputs)?;
Ok(value)
}
/// Decode the value bytes at the location given by the provided runtime API payload.
pub fn decode_value<P: Payload>(
bytes: &mut &[u8],
payload: &P,
payload: P,
metadata: &Metadata,
) -> Result<P::ReturnType, Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(payload.trait_name())?
.method_by_name(payload.method_name())
.ok_or_else(|| MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned()))?;
let val = <P::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
api_method.output_ty(),
) -> Result<P::ReturnType, RuntimeApiError> {
let value = frame_decode::runtime_apis::decode_runtime_api_response(
payload.trait_name(),
payload.method_name(),
bytes,
metadata,
)?;
metadata.types(),
P::ReturnType::into_visitor(),
)
.map_err(RuntimeApiError::CouldNotDecodeResponse)?;
Ok(val)
Ok(value)
}
+58 -86
View File
@@ -6,45 +6,19 @@
//! runtime API calls that can be made.
use alloc::borrow::Cow;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use core::marker::PhantomData;
use derive_where::derive_where;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use frame_decode::runtime_apis::IntoEncodableValues;
use scale_decode::DecodeAsType;
use crate::Error;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::metadata::{DecodeWithMetadata, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
/// This represents a runtime API payload that can be used to call a Runtime API on
/// a chain and decode the response.
pub trait Payload {
/// Type of the arguments.
type ArgsType: IntoEncodableValues;
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
type ReturnType: DecodeAsType;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
@@ -52,16 +26,8 @@ pub trait Payload {
/// The runtime API method name.
fn method_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`Payload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// The input arguments.
fn args(&self) -> &Self::ArgsType;
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -69,29 +35,50 @@ pub trait Payload {
}
}
// Any reference to a payload is a valid payload.
impl<P: Payload + ?Sized> Payload for &'_ P {
type ArgsType = P::ArgsType;
type ReturnType = P::ReturnType;
fn trait_name(&self) -> &str {
P::trait_name(*self)
}
fn method_name(&self) -> &str {
P::method_name(*self)
}
fn args(&self) -> &Self::ArgsType {
P::args(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
P::validation_hash(*self)
}
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
pub struct DefaultPayload<ArgsData, ReturnTy> {
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
pub struct StaticPayload<ArgsType, ReturnType> {
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args_data: ArgsData,
args: ArgsType,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
_marker: PhantomData<ReturnType>,
}
/// A statically generated runtime API payload.
pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
/// A dynamic runtime API payload.
pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
for DefaultPayload<ArgsData, ReturnTy>
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
for StaticPayload<ArgsType, ReturnType>
{
type ReturnType = ReturnTy;
type ArgsType = ArgsType;
type ReturnType = ReturnType;
fn trait_name(&self) -> &str {
&self.trait_name
@@ -101,18 +88,8 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
&self.method_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(&self.trait_name)?
.method_by_name(&self.method_name)
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
let mut fields = api_method
.inputs()
.map(|input| scale_encode::Field::named(input.ty, &input.name));
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
fn args(&self) -> &Self::ArgsType {
&self.args
}
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -120,23 +97,23 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
}
}
impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
/// Create a new [`DefaultPayload`].
impl<ArgsType, ReturnTy> StaticPayload<ArgsType, ReturnTy> {
/// Create a new [`StaticPayload`].
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsData,
args: ArgsType,
) -> Self {
DefaultPayload {
trait_name: Cow::Owned(trait_name.into()),
method_name: Cow::Owned(method_name.into()),
args_data,
StaticPayload {
trait_name: trait_name.into().into(),
method_name: method_name.into().into(),
args,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`DefaultPayload`] using static function name
/// Create a new static [`StaticPayload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
@@ -144,13 +121,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
pub fn new_static(
trait_name: &'static str,
method_name: &'static str,
args_data: ArgsData,
args: ArgsType,
hash: [u8; 32],
) -> DefaultPayload<ArgsData, ReturnTy> {
DefaultPayload {
) -> StaticPayload<ArgsType, ReturnTy> {
StaticPayload {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args_data,
args,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
@@ -173,18 +150,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicPayload`].
pub fn dynamic(
pub fn dynamic<ArgsType, ReturnType>(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicPayload {
DefaultPayload::new(trait_name, method_name, args_data.into())
args_data: ArgsType,
) -> DynamicPayload<ArgsType, ReturnType> {
DynamicPayload::new(trait_name, method_name, args_data)
}
+105 -114
View File
@@ -4,143 +4,125 @@
//! Construct addresses to access storage entries with.
use crate::{
dynamic::DecodedValueThunk,
error::{Error, MetadataError},
metadata::{DecodeWithMetadata, Metadata},
utils::Yes,
};
use derive_where::derive_where;
use alloc::borrow::{Cow, ToOwned};
use crate::utils::{Maybe, YesMaybe};
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues};
use scale_decode::DecodeAsType;
// Re-export types used here:
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageHashersIter, StorageKey};
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
/// A storage address. This allows access to a given storage entry, which can then
/// be iterated over or fetched from by providing the relevant set of keys, or
/// otherwise inspected.
pub trait Address {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The keys type used to construct this address.
type Keys: StorageKey;
/// Can an entry be fetched from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsFetchable;
/// Can a default entry be obtained from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsDefaultable;
/// Can this address be iterated over?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsIterable;
/// All of the keys required to get to an individual value at this address.
/// Keys must always impl [`IntoEncodableValues`], and for iteration must
/// also impl [`frame_decode::storage::IntoDecodableValues`].
type KeyParts: IntoEncodableValues + IntoDecodableValues;
/// Type of the storage value at this location.
type Value: DecodeAsType;
/// Does the address point to a plain value (as opposed to a map)?
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
type IsPlain: YesMaybe;
/// The name of the pallet that the entry lives under.
/// The pallet containing this storage entry.
fn pallet_name(&self) -> &str;
/// The name of the entry in a given pallet that the item is at.
/// The name of the storage entry.
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>;
/// Return a unique hash for this address which can be used to validate it against metadata.
fn validation_hash(&self) -> Option<[u8; 32]>;
}
// Any reference to an address is a valid address.
impl<A: Address + ?Sized> Address for &'_ A {
type KeyParts = A::KeyParts;
type Value = A::Value;
type IsPlain = A::IsPlain;
fn pallet_name(&self) -> &str {
A::pallet_name(*self)
}
fn entry_name(&self) -> &str {
A::entry_name(*self)
}
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
A::validation_hash(*self)
}
}
/// A concrete storage address. This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; Keys)]
pub struct DefaultAddress<Keys: StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> {
/// An address which is generated by the static APIs.
pub struct StaticAddress<KeyParts, Value, IsPlain> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
keys: Keys,
validation_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
marker: core::marker::PhantomData<(KeyParts, Value, IsPlain)>,
}
/// A storage address constructed by the static codegen.
pub type StaticAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable> =
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>;
/// A typical storage address constructed at runtime rather than via the `subxt` macro; this
/// has no restriction on what it can be used for (since we don't statically know).
pub type DynamicAddress<Keys> = DefaultAddress<Keys, DecodedValueThunk, Yes, Yes, Yes>;
impl<Keys: StorageKey> DynamicAddress<Keys> {
/// Creates a new dynamic address. As `Keys` you can use a `Vec<scale_value::Value>`
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>, keys: Keys) -> Self {
impl<KeyParts, Value, IsPlain> Clone for StaticAddress<KeyParts, Value, IsPlain> {
fn clone(&self) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
entry_name: Cow::Owned(entry_name.into()),
keys,
validation_hash: None,
_marker: core::marker::PhantomData,
pallet_name: self.pallet_name.clone(),
entry_name: self.entry_name.clone(),
validation_hash: self.validation_hash,
marker: self.marker,
}
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`Address`] using static strings for the pallet and call name.
impl<KeyParts, Value, IsPlain> core::fmt::Debug for StaticAddress<KeyParts, Value, IsPlain> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("StaticAddress")
.field("pallet_name", &self.pallet_name)
.field("entry_name", &self.entry_name)
.field("validation_hash", &self.validation_hash)
.finish()
}
}
impl<KeyParts, Value, IsPlain> StaticAddress<KeyParts, Value, IsPlain> {
/// Create a new [`StaticAddress`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
entry_name: &'static str,
keys: Keys,
hash: [u8; 32],
) -> Self {
pub fn new_static(pallet_name: &'static str, entry_name: &'static str, hash: [u8; 32]) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
keys,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
marker: core::marker::PhantomData,
}
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
{
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(self) -> Self {
/// Create a new address.
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>) -> Self {
Self {
pallet_name: pallet_name.into().into(),
entry_name: entry_name.into().into(),
validation_hash: None,
..self
marker: core::marker::PhantomData,
}
}
/// Return bytes representing the root of this storage entry (a hash of the pallet and entry name).
pub fn to_root_bytes(&self) -> Vec<u8> {
super::get_address_root_bytes(self)
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(mut self) -> Self {
self.validation_hash = None;
self
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> Address
for DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
impl<KeyParts, Value, IsPlain> Address for StaticAddress<KeyParts, Value, IsPlain>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
KeyParts: IntoEncodableValues + IntoDecodableValues,
Value: DecodeAsType,
IsPlain: YesMaybe,
{
type Target = ReturnTy;
type Keys = Keys;
type IsFetchable = Fetchable;
type IsDefaultable = Defaultable;
type IsIterable = Iterable;
type KeyParts = KeyParts;
type Value = Value;
type IsPlain = IsPlain;
fn pallet_name(&self) -> &str {
&self.pallet_name
@@ -150,31 +132,40 @@ where
&self.entry_name
}
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(self.pallet_name())?;
let storage = pallet
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
let entry = storage
.entry_by_name(self.entry_name())
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
let hashers = StorageHashers::new(entry.entry_type(), metadata.types())?;
self.keys
.encode_storage_key(bytes, &mut hashers.iter(), metadata.types())?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<Keys: StorageKey>(
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
type KeyParts = Vec<scale_value::Value>;
type Value = scale_value::Value;
type IsPlain = Maybe;
fn pallet_name(&self) -> &str {
self.0.as_ref()
}
fn entry_name(&self) -> &str {
self.1.as_ref()
}
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
/// entry *might* be a map and *might* have a default value.
pub type DynamicAddress<KeyParts = Vec<scale_value::Value>, Value = scale_value::Value> =
StaticAddress<KeyParts, Value, Maybe>;
/// Construct a new dynamic storage address. You can define the type of the
/// storage keys and value yourself here, but have no guarantee that they will
/// be correct.
pub fn dynamic<KeyParts: IntoEncodableValues, Value: DecodeAsType>(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Keys,
) -> DynamicAddress<Keys> {
DynamicAddress::new(pallet_name, entry_name, storage_entry_keys)
) -> DynamicAddress<KeyParts, Value> {
DynamicAddress::<KeyParts, Value>::new(pallet_name.into(), entry_name.into())
}
+39 -87
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
@@ -10,7 +10,7 @@
//! use subxt_signer::sr25519::dev;
//! use subxt_macro::subxt;
//! use subxt_core::storage;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -21,45 +21,54 @@
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Build a storage query to access account information.
//! let account = dev::alice().public_key().into();
//! let address = polkadot::storage().system().account(account);
//! let address = polkadot::storage().system().account();
//!
//! // We can validate that the address is compatible with the given metadata.
//! storage::validate(&address, &metadata).unwrap();
//!
//! // Encode the address to bytes. These can be sent to a node to query the value.
//! storage::get_address_bytes(&address, &metadata).unwrap();
//! // We can fetch details about the storage entry associated with this address:
//! let entry = storage::entry(address, &metadata).unwrap();
//!
//! // If we were to obtain a value back from the node at that address, we could
//! // then decode it using the same address and metadata like so:
//! // .. including generating a key to fetch the entry with:
//! let fetch_key = entry.fetch_key((dev::alice().public_key().into(),)).unwrap();
//!
//! // .. or generating a key to iterate over entries with at a given depth:
//! let iter_key = entry.iter_key(()).unwrap();
//!
//! // Given a value, we can decode it:
//! let value_bytes = hex::decode("00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080").unwrap();
//! let value = storage::decode_value(&mut &*value_bytes, &address, &metadata).unwrap();
//! let value = entry.value(value_bytes).decode().unwrap();
//!
//! println!("Alice's account info: {value:?}");
//! ```
mod prefix_of;
mod storage_entry;
mod storage_key;
mod utils;
mod storage_key_value;
mod storage_value;
pub mod address;
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
use crate::{Metadata, error::StorageError};
use address::Address;
use alloc::vec::Vec;
use alloc::string::ToString;
// This isn't a part of the public API, but expose here because it's useful in Subxt.
#[doc(hidden)]
pub use utils::lookup_storage_entry_details;
pub use prefix_of::{EqualOrPrefixOf, PrefixOf};
pub use storage_entry::{StorageEntry, entry};
pub use storage_key::{StorageHasher, StorageKey, StorageKeyPart};
pub use storage_key_value::StorageKeyValue;
pub use storage_value::StorageValue;
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
/// that the shape of the storage value is the same as the shape expected by the static address.
///
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
/// shape of the constant value), this just returns `Ok(())`
pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), StorageError> {
let Some(hash) = address.validation_hash() else {
return Ok(());
};
@@ -67,76 +76,19 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let pallet_metadata = metadata
.pallet_by_name(pallet_name)
.ok_or_else(|| StorageError::PalletNameNotFound(pallet_name.to_string()))?;
let storage_hash = pallet_metadata.storage_hash(entry_name).ok_or_else(|| {
StorageError::StorageEntryNotFound {
pallet_name: pallet_name.to_string(),
entry_name: entry_name.to_string(),
}
})?;
let Some(expected_hash) = pallet_metadata.storage_hash(entry_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
if storage_hash != hash {
Err(StorageError::IncompatibleCodegen)
} else {
Ok(())
}
Ok(())
}
/// Given a storage address and some metadata, this encodes the address into bytes which can be
/// handed to a node to retrieve the corresponding value.
pub fn get_address_bytes<Addr: Address>(
address: &Addr,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
utils::write_storage_address_root_bytes(address, &mut bytes);
address.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Given a storage address and some metadata, this encodes the root of the address (ie the pallet
/// and storage entry part) into bytes. If the entry being addressed is inside a map, this returns
/// the bytes needed to iterate over all of the entries within it.
pub fn get_address_root_bytes<Addr: Address>(address: &Addr) -> Vec<u8> {
let mut bytes = Vec::new();
utils::write_storage_address_root_bytes(address, &mut bytes);
bytes
}
/// Given some storage value that we've retrieved from a node, the address used to retrieve it, and
/// metadata from the node, this function attempts to decode the bytes into the target value specified
/// by the address.
pub fn decode_value<Addr: Address>(
bytes: &mut &[u8],
address: &Addr,
metadata: &Metadata,
) -> Result<Addr::Target, Error> {
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let (_, entry_metadata) =
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
let value_ty_id = match entry_metadata.entry_type() {
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
};
let val = Addr::Target::decode_with_metadata(bytes, value_ty_id, metadata)?;
Ok(val)
}
/// Return the default value at a given storage address if one is available, or an error otherwise.
pub fn default_value<Addr: Address>(
address: &Addr,
metadata: &Metadata,
) -> Result<Addr::Target, Error> {
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let (_, entry_metadata) =
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
let value_ty_id = match entry_metadata.entry_type() {
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
};
let default_bytes = entry_metadata.default_bytes();
let val = Addr::Target::decode_with_metadata(&mut &*default_bytes, value_ty_id, metadata)?;
Ok(val)
}
+195
View File
@@ -0,0 +1,195 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use alloc::vec::Vec;
use frame_decode::helpers::IntoEncodableValues;
use scale_encode::EncodeAsType;
/// For a given set of values that can be used as keys for a storage entry,
/// this is implemented for any prefixes of that set. ie if the keys `(A,B,C)`
/// would access a storage value, then `PrefixOf<(A,B,C)>` is implemented for
/// `(A,B)`, `(A,)` and `()`.
pub trait PrefixOf<Keys>: IntoEncodableValues {}
// If T impls PrefixOf<K>, &T impls PrefixOf<K>.
impl<K, T: PrefixOf<K>> PrefixOf<K> for &T {}
// Impls for tuples up to length 6 (storage maps rarely require more than 2 entries
// so it's very unlikely we'll ever need to go this deep).
impl<A> PrefixOf<(A,)> for () {}
impl<A, B> PrefixOf<(A, B)> for () {}
impl<A, B> PrefixOf<(A, B)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C> PrefixOf<(A, B, C)> for () {}
impl<A, B, C> PrefixOf<(A, B, C)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C> PrefixOf<(A, B, C)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for () {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for () {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C, D) where
(A, B, C, D): IntoEncodableValues
{
}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for () {}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C) where
(A, B, C): IntoEncodableValues
{
}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D) where
(A, B, C, D): IntoEncodableValues
{
}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D, E) where
(A, B, C, D, E): IntoEncodableValues
{
}
// Vecs are prefixes of vecs. The length is not statically known and so
// these would be given dynamically only, leaving the correct length to the user.
impl<T: EncodeAsType> PrefixOf<Vec<T>> for Vec<T> {}
// We don't use arrays in Subxt for storage entry access, but `IntoEncodableValues`
// supports them so let's allow impls which do use them to benefit too.
macro_rules! array_impl {
($n:literal: $($p:literal)+) => {
$(
impl <T: EncodeAsType> PrefixOf<[T; $n]> for [T; $p] {}
)+
}
}
array_impl!(1: 0);
array_impl!(2: 1 0);
array_impl!(3: 2 1 0);
array_impl!(4: 3 2 1 0);
array_impl!(5: 4 3 2 1 0);
array_impl!(6: 5 4 3 2 1 0);
/// This is much like [`PrefixOf`] except that it also includes `Self` as an allowed type,
/// where `Self` must impl [`IntoEncodableValues`] just as every [`PrefixOf<Self>`] does.
pub trait EqualOrPrefixOf<K>: IntoEncodableValues {}
// Tuples
macro_rules! tuple_impl_eq {
($($t:ident)+) => {
// Any T that is a PrefixOf<Keys> impls EqualOrPrefixOf<keys> too
impl <$($t,)+ T: PrefixOf<($($t,)+)>> EqualOrPrefixOf<($($t,)+)> for T {}
// Keys impls EqualOrPrefixOf<Keys>
impl <$($t),+> EqualOrPrefixOf<($($t,)+)> for ($($t,)+) where ($($t,)+): IntoEncodableValues {}
// &'a Keys impls EqualOrPrefixOf<Keys>
impl <'a, $($t),+> EqualOrPrefixOf<($($t,)+)> for &'a ($($t,)+) where ($($t,)+): IntoEncodableValues {}
}
}
tuple_impl_eq!(A);
tuple_impl_eq!(A B);
tuple_impl_eq!(A B C);
tuple_impl_eq!(A B C D);
tuple_impl_eq!(A B C D E);
tuple_impl_eq!(A B C D E F);
// Vec
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for Vec<T> {}
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for &Vec<T> {}
// Arrays
macro_rules! array_impl_eq {
($($n:literal)+) => {
$(
impl <A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for [A; $n] {}
impl <'a, A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for &'a [A; $n] {}
)+
}
}
impl<const N: usize, A, T> EqualOrPrefixOf<[A; N]> for T where T: PrefixOf<[A; N]> {}
array_impl_eq!(1 2 3 4 5 6);
#[cfg(test)]
mod test {
use super::*;
struct Test<Keys: IntoEncodableValues>(core::marker::PhantomData<Keys>);
impl<Keys: IntoEncodableValues> Test<Keys> {
fn new() -> Self {
Test(core::marker::PhantomData)
}
fn accepts_prefix_of<P: PrefixOf<Keys>>(&self, keys: P) {
let _encoder = keys.into_encodable_values();
}
fn accepts_eq_or_prefix_of<P: EqualOrPrefixOf<Keys>>(&self, keys: P) {
let _encoder = keys.into_encodable_values();
}
}
#[test]
fn test_prefix_of() {
// In real life we'd have a struct a bit like this:
let t = Test::<(bool, String, u64)>::new();
// And we'd want to be able to call some method like this:
//// This shouldn't work:
// t.accepts_prefix_of((true, String::from("hi"), 0));
t.accepts_prefix_of(&(true, String::from("hi")));
t.accepts_prefix_of((true, String::from("hi")));
t.accepts_prefix_of((true,));
t.accepts_prefix_of(());
let t = Test::<[u64; 5]>::new();
//// This shouldn't work:
// t.accepts_prefix_of([0,1,2,3,4]);
t.accepts_prefix_of([0, 1, 2, 3]);
t.accepts_prefix_of([0, 1, 2, 3]);
t.accepts_prefix_of([0, 1, 2]);
t.accepts_prefix_of([0, 1]);
t.accepts_prefix_of([0]);
t.accepts_prefix_of([]);
}
#[test]
fn test_eq_or_prefix_of() {
// In real life we'd have a struct a bit like this:
let t = Test::<(bool, String, u64)>::new();
// And we'd want to be able to call some method like this:
t.accepts_eq_or_prefix_of(&(true, String::from("hi"), 0));
t.accepts_eq_or_prefix_of(&(true, String::from("hi")));
t.accepts_eq_or_prefix_of((true,));
t.accepts_eq_or_prefix_of(());
t.accepts_eq_or_prefix_of((true, String::from("hi"), 0));
t.accepts_eq_or_prefix_of((true, String::from("hi")));
t.accepts_eq_or_prefix_of((true,));
t.accepts_eq_or_prefix_of(());
let t = Test::<[u64; 5]>::new();
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
t.accepts_eq_or_prefix_of([0, 1, 2]);
t.accepts_eq_or_prefix_of([0, 1]);
t.accepts_eq_or_prefix_of([0]);
t.accepts_eq_or_prefix_of([]);
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
t.accepts_eq_or_prefix_of([0, 1, 2]);
t.accepts_eq_or_prefix_of([0, 1]);
t.accepts_eq_or_prefix_of([0]);
t.accepts_eq_or_prefix_of([]);
}
}
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{PrefixOf, StorageKeyValue, StorageValue, address::Address};
use crate::error::StorageError;
use crate::utils::YesMaybe;
use alloc::sync::Arc;
use alloc::vec::Vec;
use frame_decode::storage::{IntoEncodableValues, StorageInfo};
use scale_info::PortableRegistry;
use subxt_metadata::Metadata;
/// Create a [`StorageEntry`] to work with a given storage entry.
pub fn entry<'info, Addr: Address>(
address: Addr,
metadata: &'info Metadata,
) -> Result<StorageEntry<'info, Addr>, StorageError> {
super::validate(&address, metadata)?;
use frame_decode::storage::StorageTypeInfo;
let types = metadata.types();
let info = metadata
.storage_info(address.pallet_name(), address.entry_name())
.map_err(|e| StorageError::StorageInfoError(e.into_owned()))?;
Ok(StorageEntry(Arc::new(StorageEntryInner {
address,
info: Arc::new(info),
types,
})))
}
/// This represents a single storage entry (be it a plain value or map).
pub struct StorageEntry<'info, Addr>(Arc<StorageEntryInner<'info, Addr>>);
impl<'info, Addr> Clone for StorageEntry<'info, Addr> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
struct StorageEntryInner<'info, Addr> {
address: Addr,
info: Arc<StorageInfo<'info, u32>>,
types: &'info PortableRegistry,
}
impl<'info, Addr: Address> StorageEntry<'info, Addr> {
/// Name of the pallet containing this storage entry.
pub fn pallet_name(&self) -> &str {
self.0.address.pallet_name()
}
/// Name of the storage entry.
pub fn entry_name(&self) -> &str {
self.0.address.entry_name()
}
/// Is the storage entry a plain value?
pub fn is_plain(&self) -> bool {
self.0.info.keys.is_empty()
}
/// Is the storage entry a map?
pub fn is_map(&self) -> bool {
!self.is_plain()
}
/// Instantiate a [`StorageKeyValue`] for this entry.
///
/// It is expected that the bytes are obtained by iterating key/value pairs at this address.
pub fn key_value(
&self,
key_bytes: impl Into<Arc<[u8]>>,
value_bytes: Vec<u8>,
) -> StorageKeyValue<'info, Addr> {
StorageKeyValue::new(
self.0.info.clone(),
self.0.types,
key_bytes.into(),
value_bytes,
)
}
/// Instantiate a [`StorageValue`] for this entry.
///
/// It is expected that the bytes are obtained by fetching a value at this address.
pub fn value(&self, bytes: Vec<u8>) -> StorageValue<'info, Addr::Value> {
StorageValue::new(self.0.info.clone(), self.0.types, bytes)
}
/// Return the default [`StorageValue`] for this storage entry, if there is one.
pub fn default_value(&self) -> Option<StorageValue<'info, Addr::Value>> {
self.0.info.default_value.as_deref().map(|default_bytes| {
StorageValue::new(self.0.info.clone(), self.0.types, default_bytes.to_vec())
})
}
/// The keys for plain storage values are always 32 byte hashes.
pub fn key_prefix(&self) -> [u8; 32] {
frame_decode::storage::encode_storage_key_prefix(
self.0.address.pallet_name(),
self.0.address.entry_name(),
)
}
// This has a less "strict" type signature and so is just used under the hood.
fn key<Keys: IntoEncodableValues>(&self, key_parts: Keys) -> Result<Vec<u8>, StorageError> {
let key = frame_decode::storage::encode_storage_key_with_info(
self.0.address.pallet_name(),
self.0.address.entry_name(),
key_parts,
&self.0.info,
self.0.types,
)
.map_err(StorageError::StorageKeyEncodeError)?;
Ok(key)
}
/// This constructs a key suitable for fetching a value at the given map storage address. This will error
/// if we can see that the wrong number of key parts are provided.
pub fn fetch_key(&self, key_parts: Addr::KeyParts) -> Result<Vec<u8>, StorageError> {
if key_parts.num_encodable_values() != self.0.info.keys.len() {
Err(StorageError::WrongNumberOfKeyPartsProvidedForFetching {
expected: self.0.info.keys.len(),
got: key_parts.num_encodable_values(),
})
} else {
self.key(key_parts)
}
}
/// This constructs a key suitable for iterating at the given storage address. This will error
/// if we can see that too many key parts are provided.
pub fn iter_key<Keys: PrefixOf<Addr::KeyParts>>(
&self,
key_parts: Keys,
) -> Result<Vec<u8>, StorageError> {
if Addr::IsPlain::is_yes() {
Err(StorageError::CannotIterPlainEntry {
pallet_name: self.0.address.pallet_name().into(),
entry_name: self.0.address.entry_name().into(),
})
} else if key_parts.num_encodable_values() >= self.0.info.keys.len() {
Err(StorageError::WrongNumberOfKeyPartsProvidedForIterating {
max_expected: self.0.info.keys.len() - 1,
got: key_parts.num_encodable_values(),
})
} else {
self.key(key_parts)
}
}
}
+112 -445
View File
@@ -1,471 +1,138 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::utils::hash_bytes;
use crate::error::{Error, MetadataError, StorageAddressError};
use alloc::vec;
use alloc::vec::Vec;
use scale_decode::{DecodeAsType, visitor::IgnoreVisitor};
use scale_encode::EncodeAsType;
use scale_info::{PortableRegistry, TypeDef};
use scale_value::Value;
use subxt_metadata::{StorageEntryType, StorageHasher};
use crate::error::StorageKeyError;
use alloc::sync::Arc;
use core::marker::PhantomData;
use frame_decode::storage::{IntoDecodableValues, StorageInfo, StorageKey as StorageKeyPartInfo};
use scale_info::PortableRegistry;
/// A collection of storage hashers paired with the type ids of the types they should hash.
/// Can be created for each storage entry in the metadata via [`StorageHashers::new()`].
#[derive(Debug)]
pub struct StorageHashers {
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
pub use frame_decode::storage::StorageHasher;
/// This represents the different parts of a storage key.
pub struct StorageKey<'info, KeyParts> {
info: Arc<StorageKeyPartInfo<u32>>,
types: &'info PortableRegistry,
bytes: Arc<[u8]>,
marker: PhantomData<KeyParts>,
}
impl StorageHashers {
/// Creates new [`StorageHashers`] from a storage entry. Looks at the [`StorageEntryType`] and
/// assigns a hasher to each type id that makes up the key.
pub fn new(storage_entry: &StorageEntryType, types: &PortableRegistry) -> Result<Self, Error> {
let mut hashers_and_ty_ids = vec![];
if let StorageEntryType::Map {
hashers, key_ty, ..
} = storage_entry
{
let ty = types
.resolve(*key_ty)
.ok_or(MetadataError::TypeNotFound(*key_ty))?;
impl<'info, KeyParts: IntoDecodableValues> StorageKey<'info, KeyParts> {
pub(crate) fn new(
info: &StorageInfo<'info, u32>,
types: &'info PortableRegistry,
bytes: Arc<[u8]>,
) -> Result<Self, StorageKeyError> {
let cursor = &mut &*bytes;
let storage_key_info = frame_decode::storage::decode_storage_key_with_info(
cursor, info, types,
)
.map_err(|e| StorageKeyError::StorageKeyDecodeError {
bytes: bytes.to_vec(),
error: e,
})?;
if hashers.len() == 1 {
// If there's exactly 1 hasher, then we have a plain StorageMap. We can't
// break the key down (even if it's a tuple) because the hasher applies to
// the whole key.
hashers_and_ty_ids = vec![(hashers[0], *key_ty)];
} else {
// If there are multiple hashers, then we have a StorageDoubleMap or StorageNMap.
// We expect the key type to be tuple, and we will return a MapEntryKey for each
// key in the tuple.
let hasher_count = hashers.len();
let tuple = match &ty.type_def {
TypeDef::Tuple(tuple) => tuple,
_ => {
return Err(StorageAddressError::WrongNumberOfHashers {
hashers: hasher_count,
fields: 1,
}
.into());
}
};
// We should have the same number of hashers and keys.
let key_count = tuple.fields.len();
if hasher_count != key_count {
return Err(StorageAddressError::WrongNumberOfHashers {
hashers: hasher_count,
fields: key_count,
}
.into());
}
// Collect them together.
hashers_and_ty_ids = tuple
.fields
.iter()
.zip(hashers)
.map(|(field, hasher)| (*hasher, field.id))
.collect();
}
if !cursor.is_empty() {
return Err(StorageKeyError::LeftoverBytes {
bytes: cursor.to_vec(),
});
}
Ok(Self { hashers_and_ty_ids })
}
/// Creates an iterator over the storage hashers and type ids.
pub fn iter(&self) -> StorageHashersIter<'_> {
StorageHashersIter {
hashers: self,
idx: 0,
}
}
}
/// An iterator over all type ids of the key and the respective hashers.
/// See [`StorageHashers::iter()`].
#[derive(Debug)]
pub struct StorageHashersIter<'a> {
hashers: &'a StorageHashers,
idx: usize,
}
impl StorageHashersIter<'_> {
fn next_or_err(&mut self) -> Result<(StorageHasher, u32), Error> {
self.next().ok_or_else(|| {
StorageAddressError::TooManyKeys {
expected: self.hashers.hashers_and_ty_ids.len(),
}
.into()
Ok(StorageKey {
info: Arc::new(storage_key_info),
types,
bytes,
marker: PhantomData,
})
}
}
impl Iterator for StorageHashersIter<'_> {
type Item = (StorageHasher, u32);
/// Attempt to decode the values contained within this storage key. The target type is
/// given by the storage address used to access this entry. To decode into a custom type,
/// use [`Self::parts()`] or [`Self::part()`] and decode each part.
pub fn decode(&self) -> Result<KeyParts, StorageKeyError> {
let values =
frame_decode::storage::decode_storage_key_values(&self.bytes, &self.info, self.types)
.map_err(StorageKeyError::CannotDecodeValuesInKey)?;
fn next(&mut self) -> Option<Self::Item> {
let item = self.hashers.hashers_and_ty_ids.get(self.idx).copied()?;
self.idx += 1;
Some(item)
Ok(values)
}
/// Iterate over the parts of this storage key. Each part of a storage key corresponds to a
/// single value that has been hashed.
pub fn parts(&self) -> impl ExactSizeIterator<Item = StorageKeyPart<'info>> {
let parts_len = self.info.parts().len();
(0..parts_len).map(move |index| StorageKeyPart {
index,
info: self.info.clone(),
types: self.types,
bytes: self.bytes.clone(),
})
}
/// Return the part of the storage key at the provided index, or `None` if the index is out of bounds.
pub fn part(&self, index: usize) -> Option<StorageKeyPart<'info>> {
if index < self.parts().len() {
Some(StorageKeyPart {
index,
info: self.info.clone(),
types: self.types,
bytes: self.bytes.clone(),
})
} else {
None
}
}
}
impl ExactSizeIterator for StorageHashersIter<'_> {
fn len(&self) -> usize {
self.hashers.hashers_and_ty_ids.len() - self.idx
}
/// This represents a part of a storage key.
pub struct StorageKeyPart<'info> {
index: usize,
info: Arc<StorageKeyPartInfo<u32>>,
types: &'info PortableRegistry,
bytes: Arc<[u8]>,
}
/// This trait should be implemented by anything that can be used as one or multiple storage keys.
pub trait StorageKey {
/// Encodes the storage key into some bytes
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error>;
/// Attempts to decode the StorageKey given some bytes and a set of hashers and type IDs that they are meant to represent.
/// The bytes passed to `decode` should start with:
/// - 1. some fixed size hash (for all hashers except `Identity`)
/// - 2. the plain key value itself (for `Identity`, `Blake2_128Concat` and `Twox64Concat` hashers)
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static;
}
/// Implement `StorageKey` for `()` which can be used for keyless storage entries,
/// or to otherwise just ignore some entry.
impl StorageKey for () {
fn encode_storage_key(
&self,
_bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
_types: &PortableRegistry,
) -> Result<(), Error> {
_ = hashers.next_or_err();
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error> {
let (hasher, ty_id) = match hashers.next_or_err() {
Ok((hasher, ty_id)) => (hasher, ty_id),
Err(_) if bytes.is_empty() => return Ok(()),
Err(err) => return Err(err),
impl<'info> StorageKeyPart<'info> {
/// Get the raw bytes for this part of the storage key.
pub fn bytes(&self) -> &[u8] {
let part = &self.info[self.index];
let hash_range = part.hash_range();
let value_range = part.value().map(|v| v.range()).unwrap_or(core::ops::Range {
start: hash_range.end,
end: hash_range.end,
});
let combined_range = core::ops::Range {
start: hash_range.start,
end: value_range.end,
};
consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
Ok(())
}
}
/// A storage key used as part of the static codegen.
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]
pub struct StaticStorageKey<K> {
key: K,
}
impl<K> StaticStorageKey<K> {
/// Creates a new static storage key.
pub fn new(key: K) -> Self {
StaticStorageKey { key }
}
}
impl<K: Clone> StaticStorageKey<K> {
/// Returns the decoded storage key.
pub fn into_key(self) -> K {
self.key
}
}
impl<K: EncodeAsType + DecodeAsType> StorageKey for StaticStorageKey<K> {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
let (hasher, ty_id) = hashers.next_or_err()?;
let encoded_value = self.key.encode_as_type(ty_id, types)?;
hash_bytes(&encoded_value, hasher, bytes);
Ok(())
&self.bytes[combined_range]
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
let (hasher, ty_id) = hashers.next_or_err()?;
let key_bytes = consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
/// Get the hasher that was used to construct this part of the storage key.
pub fn hasher(&self) -> StorageHasher {
self.info[self.index].hasher()
}
// if the hasher had no key appended, we can't decode it into a `StaticStorageKey`.
let Some(key_bytes) = key_bytes else {
return Err(StorageAddressError::HasherCannotReconstructKey { ty_id, hasher }.into());
/// For keys that were produced using "concat" or "identity" hashers, the value
/// is available as a part of the key hash, allowing us to decode it into anything
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
/// different hasher, this will return `None`.
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
let part_info = &self.info[self.index];
let Some(value_info) = part_info.value() else {
return Ok(None);
};
// Decode and return the key.
let key = K::decode_as_type(&mut &*key_bytes, ty_id, types)?;
let key = StaticStorageKey { key };
Ok(key)
}
}
impl StorageKey for Vec<scale_value::Value> {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
for value in self.iter() {
let (hasher, ty_id) = hashers.next_or_err()?;
let encoded_value = value.encode_as_type(ty_id, types)?;
hash_bytes(&encoded_value, hasher, bytes);
}
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
let mut result: Vec<scale_value::Value> = vec![];
for (hasher, ty_id) in hashers.by_ref() {
match consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)? {
Some(value_bytes) => {
let value =
scale_value::scale::decode_as_type(&mut &*value_bytes, ty_id, types)?;
result.push(value.remove_context());
}
None => {
result.push(Value::unnamed_composite([]));
}
}
}
// We've consumed all of the hashers, so we expect to also consume all of the bytes:
if !bytes.is_empty() {
return Err(StorageAddressError::TooManyBytes.into());
}
Ok(result)
}
}
// Skip over the hash bytes (including any key at the end), returning bytes
// representing the key if one exists, or None if the hasher has no key appended.
fn consume_hash_returning_key_bytes<'a>(
bytes: &mut &'a [u8],
hasher: StorageHasher,
ty_id: u32,
types: &PortableRegistry,
) -> Result<Option<&'a [u8]>, Error> {
// Strip the bytes off for the actual hash, consuming them.
let bytes_to_strip = hasher.len_excluding_key();
if bytes.len() < bytes_to_strip {
return Err(StorageAddressError::NotEnoughBytes.into());
}
*bytes = &bytes[bytes_to_strip..];
// Now, find the bytes representing the key, consuming them.
let before_key = *bytes;
if hasher.ends_with_key() {
scale_decode::visitor::decode_with_visitor(
bytes,
ty_id,
types,
IgnoreVisitor::<PortableRegistry>::new(),
)
.map_err(|err| Error::Decode(err.into()))?;
// Return the key bytes, having advanced the input cursor past them.
let key_bytes = &before_key[..before_key.len() - bytes.len()];
Ok(Some(key_bytes))
} else {
// There are no key bytes, so return None.
Ok(None)
}
}
/// Generates StorageKey implementations for tuples
macro_rules! impl_tuples {
($($ty:ident $n:tt),+) => {{
impl<$($ty: StorageKey),+> StorageKey for ($( $ty ),+) {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
$( self.$n.encode_storage_key(bytes, hashers, types)?; )+
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
Ok( ( $( $ty::decode_storage_key(bytes, hashers, types)?, )+ ) )
}
}
}};
}
#[rustfmt::skip]
const _: () = {
impl_tuples!(A 0, B 1);
impl_tuples!(A 0, B 1, C 2);
impl_tuples!(A 0, B 1, C 2, D 3);
impl_tuples!(A 0, B 1, C 2, D 3, E 4);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
};
#[cfg(test)]
mod tests {
use codec::Encode;
use scale_info::{PortableRegistry, Registry, TypeInfo, meta_type};
use subxt_metadata::StorageHasher;
use crate::utils::Era;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use super::{StaticStorageKey, StorageKey};
struct KeyBuilder {
registry: Registry,
bytes: Vec<u8>,
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
}
impl KeyBuilder {
fn new() -> KeyBuilder {
KeyBuilder {
registry: Registry::new(),
bytes: vec![],
hashers_and_ty_ids: vec![],
}
}
fn add<T: TypeInfo + Encode + 'static>(mut self, value: T, hasher: StorageHasher) -> Self {
let id = self.registry.register_type(&meta_type::<T>()).id;
self.hashers_and_ty_ids.push((hasher, id));
for _i in 0..hasher.len_excluding_key() {
self.bytes.push(0);
}
value.encode_to(&mut self.bytes);
self
}
fn build(self) -> (PortableRegistry, Vec<u8>, Vec<(StorageHasher, u32)>) {
(self.registry.into(), self.bytes, self.hashers_and_ty_ids)
}
}
#[test]
fn storage_key_decoding_fuzz() {
let hashers = [
StorageHasher::Blake2_128,
StorageHasher::Blake2_128Concat,
StorageHasher::Blake2_256,
StorageHasher::Identity,
StorageHasher::Twox128,
StorageHasher::Twox256,
StorageHasher::Twox64Concat,
];
let key_preserving_hashers = [
StorageHasher::Blake2_128Concat,
StorageHasher::Identity,
StorageHasher::Twox64Concat,
];
type T4A = (
(),
StaticStorageKey<u32>,
StaticStorageKey<String>,
StaticStorageKey<Era>,
);
type T4B = (
(),
(StaticStorageKey<u32>, StaticStorageKey<String>),
StaticStorageKey<Era>,
);
type T4C = (
((), StaticStorageKey<u32>),
(StaticStorageKey<String>, StaticStorageKey<Era>),
);
let era = Era::Immortal;
for h0 in hashers {
for h1 in key_preserving_hashers {
for h2 in key_preserving_hashers {
for h3 in key_preserving_hashers {
let (types, bytes, hashers_and_ty_ids) = KeyBuilder::new()
.add((), h0)
.add(13u32, h1)
.add("Hello", h2)
.add(era, h3)
.build();
let hashers = super::StorageHashers { hashers_and_ty_ids };
let keys_a =
T4A::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
let keys_b =
T4B::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
let keys_c =
T4C::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
assert_eq!(keys_a.1.into_key(), 13);
assert_eq!(keys_b.1.0.into_key(), 13);
assert_eq!(keys_c.0.1.into_key(), 13);
assert_eq!(keys_a.2.into_key(), "Hello");
assert_eq!(keys_b.1.1.into_key(), "Hello");
assert_eq!(keys_c.1.0.into_key(), "Hello");
assert_eq!(keys_a.3.into_key(), era);
assert_eq!(keys_b.2.into_key(), era);
assert_eq!(keys_c.1.1.into_key(), era);
}
}
}
}
let value_bytes = &self.bytes[value_info.range()];
let value_ty = *value_info.ty();
let decoded_key_part = T::decode_as_type(&mut &*value_bytes, value_ty, self.types)
.map_err(|e| StorageKeyError::CannotDecodeValueInKey {
index: self.index,
error: e,
})?;
Ok(Some(decoded_key_part))
}
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{Address, StorageKey, StorageValue};
use crate::error::StorageKeyError;
use alloc::sync::Arc;
use alloc::vec::Vec;
use frame_decode::storage::StorageInfo;
use scale_info::PortableRegistry;
/// This represents a storage key/value pair, which is typically returned from
/// iterating over values in some storage map.
#[derive(Debug)]
pub struct StorageKeyValue<'info, Addr: Address> {
key: Arc<[u8]>,
// This contains the storage information already:
value: StorageValue<'info, Addr::Value>,
}
impl<'info, Addr: Address> StorageKeyValue<'info, Addr> {
pub(crate) fn new(
info: Arc<StorageInfo<'info, u32>>,
types: &'info PortableRegistry,
key_bytes: Arc<[u8]>,
value_bytes: Vec<u8>,
) -> Self {
StorageKeyValue {
key: key_bytes,
value: StorageValue::new(info, types, value_bytes),
}
}
/// Get the raw bytes for this storage entry's key.
pub fn key_bytes(&self) -> &[u8] {
&self.key
}
/// Decode the key for this storage entry. This gives back a type from which we can
/// decode specific parts of the key hash (where applicable).
pub fn key(&'_ self) -> Result<StorageKey<'info, Addr::KeyParts>, StorageKeyError> {
StorageKey::new(&self.value.info, self.value.types, self.key.clone())
}
/// Return the storage value.
pub fn value(&self) -> &StorageValue<'info, Addr::Value> {
&self.value
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::error::StorageValueError;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::marker::PhantomData;
use frame_decode::storage::StorageInfo;
use scale_decode::DecodeAsType;
use scale_info::PortableRegistry;
/// This represents a storage value.
#[derive(Debug)]
pub struct StorageValue<'info, Value> {
pub(crate) info: Arc<StorageInfo<'info, u32>>,
pub(crate) types: &'info PortableRegistry,
bytes: Vec<u8>,
marker: PhantomData<Value>,
}
impl<'info, Value: DecodeAsType> StorageValue<'info, Value> {
pub(crate) fn new(
info: Arc<StorageInfo<'info, u32>>,
types: &'info PortableRegistry,
bytes: Vec<u8>,
) -> StorageValue<'info, Value> {
StorageValue {
info,
types,
bytes,
marker: PhantomData,
}
}
/// Get the raw bytes for this storage value.
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Consume this storage value and return the raw bytes.
pub fn into_bytes(self) -> Vec<u8> {
self.bytes.to_vec()
}
/// Decode this storage value into the provided response type.
pub fn decode(&self) -> Result<Value, StorageValueError> {
self.decode_as::<Value>()
}
/// Decode this storage value into an arbitrary type.
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
let cursor = &mut &*self.bytes;
let value = frame_decode::storage::decode_storage_value_with_info(
cursor,
&self.info,
self.types,
T::into_visitor(),
)
.map_err(StorageValueError::CannotDecode)?;
if !cursor.is_empty() {
return Err(StorageValueError::LeftoverBytes {
bytes: cursor.to_vec(),
});
}
Ok(value)
}
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! these utility methods complement the [`Address`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use super::address::Address;
use crate::error::{Error, MetadataError};
use crate::metadata::Metadata;
use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageHasher};
/// Return the root of a given [`Address`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Addr: Address>(addr: &Addr, out: &mut Vec<u8>) {
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_crypto_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_crypto_hashing::twox_64(input));
bytes.extend(input);
}
}
}
/// Return details about the given storage entry.
pub fn lookup_storage_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let storage_metadata = pallet_metadata
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
let storage_entry = storage_metadata
.entry_by_name(entry_name)
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
+37 -23
View File
@@ -13,7 +13,7 @@
//! use subxt_core::config::DefaultExtrinsicParamsBuilder as Params;
//! use subxt_core::tx;
//! use subxt_core::utils::H256;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -26,7 +26,7 @@
//! let state = tx::ClientState::<PolkadotConfig> {
//! metadata: {
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! metadata::decode_from(&metadata_bytes[..]).unwrap()
//! Metadata::decode_from(&metadata_bytes[..]).unwrap()
//! },
//! genesis_hash: {
//! let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
@@ -59,11 +59,12 @@
pub mod payload;
pub mod signer;
use crate::Metadata;
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher};
use crate::error::{Error, ExtrinsicError, MetadataError};
use crate::metadata::Metadata;
use crate::error::ExtrinsicError;
use crate::utils::Encoded;
use alloc::borrow::{Cow, ToOwned};
use alloc::borrow::Cow;
use alloc::string::ToString;
use alloc::vec::Vec;
use codec::{Compact, Encode};
use payload::Payload;
@@ -77,18 +78,28 @@ pub use crate::client::{ClientState, RuntimeVersion};
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
/// the pallet or call in question do not exist at all).
pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), Error> {
if let Some(details) = call.validation_details() {
let expected_hash = metadata
.pallet_by_name_err(details.pallet_name)?
.call_hash(details.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), ExtrinsicError> {
let Some(details) = call.validation_details() else {
return Ok(());
};
if details.hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
let pallet_name = details.pallet_name;
let call_name = details.call_name;
let expected_hash = metadata
.pallet_by_name(pallet_name)
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(pallet_name.to_string()))?
.call_hash(call_name)
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
pallet_name: pallet_name.to_string(),
call_name: call_name.to_string(),
})?;
if details.hash != expected_hash {
Err(ExtrinsicError::IncompatibleCodegen)
} else {
Ok(())
}
Ok(())
}
/// Returns the suggested transaction versions to build for a given chain, or an error
@@ -96,7 +107,7 @@ pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), E
///
/// If the result is [`TransactionVersion::V4`], use the `v4` methods in this module. If it's
/// [`TransactionVersion::V5`], use the `v5` ones.
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Error> {
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, ExtrinsicError> {
let versions = metadata.extrinsic().supported_versions();
if versions.contains(&4) {
@@ -104,7 +115,7 @@ pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Erro
} else if versions.contains(&5) {
Ok(TransactionVersion::V5)
} else {
Err(ExtrinsicError::UnsupportedVersion.into())
Err(ExtrinsicError::UnsupportedVersion)
}
}
@@ -118,7 +129,10 @@ pub enum TransactionVersion {
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
pub fn call_data<Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Vec<u8>, ExtrinsicError> {
let mut bytes = Vec::new();
call.encode_call_data_to(metadata, &mut bytes)?;
Ok(bytes)
@@ -128,7 +142,7 @@ pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<
pub fn create_v4_unsigned<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 4, metadata)
}
@@ -136,7 +150,7 @@ pub fn create_v4_unsigned<T: Config, Call: Payload>(
pub fn create_v5_bare<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 5, metadata)
}
@@ -145,7 +159,7 @@ fn create_unsigned_at_version<T: Config, Call: Payload>(
call: &Call,
tx_version: u8,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, metadata)?;
@@ -176,7 +190,7 @@ pub fn create_v4_signed<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV4<T>, Error> {
) -> Result<PartialTransactionV4<T>, ExtrinsicError> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
@@ -200,7 +214,7 @@ pub fn create_v5_general<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV5<T>, Error> {
) -> Result<PartialTransactionV5<T>, ExtrinsicError> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
+28 -15
View File
@@ -5,12 +5,11 @@
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::Error;
use crate::error::MetadataError;
use crate::metadata::Metadata;
use alloc::borrow::{Cow, ToOwned};
use crate::Metadata;
use crate::error::ExtrinsicError;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use codec::Encode;
@@ -21,11 +20,15 @@ use scale_value::{Composite, Value, ValueDef, Variant};
/// to a node.
pub trait Payload {
/// Encode call data to the provided output.
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicError>;
/// Encode call data and return the output. This is a convenience
/// wrapper around [`Payload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
@@ -46,10 +49,10 @@ macro_rules! boxed_payload {
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
) -> Result<(), ExtrinsicError> {
self.as_ref().encode_call_data_to(metadata, out)
}
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
self.as_ref().encode_call_data(metadata)
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
@@ -164,13 +167,22 @@ impl DefaultPayload<Composite<()>> {
}
impl<CallData: EncodeAsFields> Payload for DefaultPayload<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)?;
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicError> {
let pallet = metadata
.pallet_by_name(&self.pallet_name)
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(self.pallet_name.to_string()))?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
pallet_name: pallet.name().to_string(),
call_name: self.call_name.to_string(),
})?;
let pallet_index = pallet.index();
let pallet_index = pallet.call_index();
let call_index = call.index;
pallet_index.encode_to(out);
@@ -182,7 +194,8 @@ impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
.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)?;
.encode_as_fields_to(&mut fields, metadata.types(), out)
.map_err(ExtrinsicError::CannotEncodeCallData)?;
Ok(())
}
@@ -208,7 +221,7 @@ pub fn dynamic(
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::Metadata;
use crate::Metadata;
use codec::Decode;
use scale_value::Composite;
+2 -3
View File
@@ -13,6 +13,7 @@ mod multi_signature;
mod static_type;
mod unchecked_extrinsic;
mod wrapper_opaque;
mod yesnomaybe;
use alloc::borrow::ToOwned;
use alloc::format;
@@ -30,6 +31,7 @@ pub use primitive_types::{H160, H256, H512};
pub use static_type::Static;
pub use unchecked_extrinsic::UncheckedExtrinsic;
pub use wrapper_opaque::WrapperKeepOpaque;
pub use yesnomaybe::{Maybe, No, NoMaybe, Yes, YesMaybe, YesNo};
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
@@ -73,9 +75,6 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
/// A unit marker struct.
pub struct Yes;
/// A quick helper to encode some bytes to hex.
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
/// A unit marker enum.
pub enum Yes {}
/// A unit marker enum.
pub enum Maybe {}
/// A unit marker enum.
pub enum No {}
/// This is implemented for [`Yes`] and [`No`] and
/// allows us to check at runtime which of these types is present.
pub trait YesNo {
/// [`Yes`]
fn is_yes() -> bool {
false
}
/// [`No`]
fn is_no() -> bool {
false
}
}
impl YesNo for Yes {
fn is_yes() -> bool {
true
}
}
impl YesNo for No {
fn is_no() -> bool {
true
}
}
/// This is implemented for [`Yes`] and [`Maybe`] and
/// allows us to check at runtime which of these types is present.
pub trait YesMaybe {
/// [`Yes`]
fn is_yes() -> bool {
false
}
/// [`Maybe`]
fn is_maybe() -> bool {
false
}
}
impl YesMaybe for Yes {
fn is_yes() -> bool {
true
}
}
impl YesMaybe for Maybe {
fn is_maybe() -> bool {
true
}
}
/// This is implemented for [`No`] and [`Maybe`] and
/// allows us to check at runtime which of these types is present.
pub trait NoMaybe {
/// [`No`]
fn is_no() -> bool {
false
}
/// [`Maybe`]
fn is_maybe() -> bool {
false
}
}
impl NoMaybe for No {
fn is_no() -> bool {
true
}
}
impl NoMaybe for Maybe {
fn is_maybe() -> bool {
true
}
}
+46 -32
View File
@@ -7,28 +7,39 @@
pub mod payload;
use crate::error::{Error, MetadataError};
use crate::metadata::{DecodeWithMetadata, Metadata};
use crate::Metadata;
use crate::error::ViewFunctionError;
use alloc::string::ToString;
use alloc::vec::Vec;
use payload::Payload;
use scale_decode::IntoVisitor;
/// Run the validation logic against some View Function payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the View Function in question do not exist at all)
pub fn validate<P: Payload>(payload: &P, metadata: &Metadata) -> Result<(), Error> {
let Some(static_hash) = payload.validation_hash() else {
pub fn validate<P: Payload>(payload: P, metadata: &Metadata) -> Result<(), ViewFunctionError> {
let Some(hash) = payload.validation_hash() else {
return Ok(());
};
let view_function = metadata
.view_function_by_query_id(payload.query_id())
.ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?;
if static_hash != view_function.hash() {
return Err(MetadataError::IncompatibleCodegen.into());
}
let pallet_name = payload.pallet_name();
let function_name = payload.function_name();
Ok(())
let view_function = metadata
.pallet_by_name(pallet_name)
.ok_or_else(|| ViewFunctionError::PalletNotFound(pallet_name.to_string()))?
.view_function_by_name(function_name)
.ok_or_else(|| ViewFunctionError::ViewFunctionNotFound {
pallet_name: pallet_name.to_string(),
function_name: function_name.to_string(),
})?;
if hash != view_function.hash() {
Err(ViewFunctionError::IncompatibleCodegen)
} else {
Ok(())
}
}
/// The name of the Runtime API call which can execute
@@ -36,34 +47,37 @@ pub const CALL_NAME: &str = "RuntimeViewFunction_execute_view_function";
/// Encode the bytes that will be passed to the "execute_view_function" Runtime API call,
/// to execute the View Function represented by the given payload.
pub fn call_args<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut call_args = Vec::with_capacity(32);
call_args.extend_from_slice(payload.query_id());
pub fn call_args<P: Payload>(
payload: P,
metadata: &Metadata,
) -> Result<Vec<u8>, ViewFunctionError> {
let inputs = frame_decode::view_functions::encode_view_function_inputs(
payload.pallet_name(),
payload.function_name(),
payload.args(),
metadata,
metadata.types(),
)
.map_err(ViewFunctionError::CouldNotEncodeInputs)?;
let mut call_arg_params = Vec::new();
payload.encode_args_to(metadata, &mut call_arg_params)?;
use codec::Encode;
call_arg_params.encode_to(&mut call_args);
Ok(call_args)
Ok(inputs)
}
/// Decode the value bytes at the location given by the provided View Function payload.
pub fn decode_value<P: Payload>(
bytes: &mut &[u8],
payload: &P,
payload: P,
metadata: &Metadata,
) -> Result<P::ReturnType, Error> {
let view_function = metadata
.view_function_by_query_id(payload.query_id())
.ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?;
let val = <P::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
view_function.output_ty(),
) -> Result<P::ReturnType, ViewFunctionError> {
let value = frame_decode::view_functions::decode_view_function_response(
payload.pallet_name(),
payload.function_name(),
bytes,
metadata,
)?;
metadata.types(),
P::ReturnType::into_visitor(),
)
.map_err(ViewFunctionError::CouldNotDecodeResponse)?;
Ok(val)
Ok(value)
}
+80 -68
View File
@@ -5,17 +5,12 @@
//! This module contains the trait and types used to represent
//! View Function calls that can be made.
use alloc::vec::Vec;
use alloc::borrow::Cow;
use alloc::string::String;
use core::marker::PhantomData;
use derive_where::derive_where;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use crate::Error;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::metadata::{DecodeWithMetadata, Metadata};
use frame_decode::view_functions::IntoEncodableValues;
use scale_decode::DecodeAsType;
/// This represents a View Function payload that can call into the runtime of node.
///
@@ -33,24 +28,19 @@ use crate::metadata::{DecodeWithMetadata, Metadata};
///
/// Each argument of the View Function must be scale-encoded.
pub trait Payload {
/// Type of the arguments for this call.
type ArgsType: IntoEncodableValues;
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
type ReturnType: DecodeAsType;
/// The payload target.
fn query_id(&self) -> &[u8; 32];
/// The View Function pallet name.
fn pallet_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// The View Function function name.
fn function_name(&self) -> &str;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`Payload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// The arguments.
fn args(&self) -> &Self::ArgsType;
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -58,45 +48,61 @@ pub trait Payload {
}
}
// A reference to a payload is a valid payload.
impl<P: Payload + ?Sized> Payload for &'_ P {
type ArgsType = P::ArgsType;
type ReturnType = P::ReturnType;
fn pallet_name(&self) -> &str {
P::pallet_name(*self)
}
fn function_name(&self) -> &str {
P::function_name(*self)
}
fn args(&self) -> &Self::ArgsType {
P::args(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
P::validation_hash(*self)
}
}
/// A View Function payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
/// and interpreting the result of the call as `ReturnType`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
pub struct DefaultPayload<ArgsData, ReturnTy> {
query_id: [u8; 32],
args_data: ArgsData,
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
pub struct StaticPayload<ArgsType, ReturnType> {
pallet_name: Cow<'static, str>,
function_name: Cow<'static, str>,
args: ArgsType,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
_marker: PhantomData<ReturnType>,
}
/// A statically generated View Function payload.
pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
/// A dynamic View Function payload.
pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
for DefaultPayload<ArgsData, ReturnTy>
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
for StaticPayload<ArgsType, ReturnType>
{
type ReturnType = ReturnTy;
type ArgsType = ArgsType;
type ReturnType = ReturnType;
fn query_id(&self) -> &[u8; 32] {
&self.query_id
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let view_function = metadata
.view_function_by_query_id(&self.query_id)
.ok_or(MetadataError::ViewFunctionNotFound(self.query_id))?;
let mut fields = view_function
.inputs()
.map(|input| scale_encode::Field::named(input.ty, &input.name));
fn function_name(&self) -> &str {
&self.function_name
}
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
fn args(&self) -> &Self::ArgsType {
&self.args
}
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -104,30 +110,37 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
}
}
impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
/// Create a new [`DefaultPayload`] for a View Function call.
pub fn new(query_id: [u8; 32], args_data: ArgsData) -> Self {
DefaultPayload {
query_id,
args_data,
impl<ReturnTy, ArgsType> StaticPayload<ArgsType, ReturnTy> {
/// Create a new [`StaticPayload`] for a View Function call.
pub fn new(
pallet_name: impl Into<String>,
function_name: impl Into<String>,
args: ArgsType,
) -> Self {
StaticPayload {
pallet_name: pallet_name.into().into(),
function_name: function_name.into().into(),
args,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`DefaultPayload`] for a View Function call
/// Create a new static [`StaticPayload`] for a View Function call
/// using static function name and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
query_id: [u8; 32],
args_data: ArgsData,
pallet_name: &'static str,
function_name: &'static str,
args: ArgsType,
hash: [u8; 32],
) -> DefaultPayload<ArgsData, ReturnTy> {
DefaultPayload {
query_id,
args_data,
) -> StaticPayload<ArgsType, ReturnTy> {
StaticPayload {
pallet_name: Cow::Borrowed(pallet_name),
function_name: Cow::Borrowed(function_name),
args,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
@@ -140,14 +153,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
..self
}
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicPayload`] to call a View Function.
pub fn dynamic(query_id: [u8; 32], args_data: impl Into<Composite<()>>) -> DynamicPayload {
DefaultPayload::new(query_id, args_data.into())
pub fn dynamic<ArgsType, ReturnType>(
pallet_name: impl Into<String>,
function_name: impl Into<String>,
args: ArgsType,
) -> DynamicPayload<ArgsType, ReturnType> {
DynamicPayload::new(pallet_name, function_name, args)
}