mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 22:51:13 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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([]);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user