Second pass over Address type and start impl in Subxt

This commit is contained in:
James Wilson
2025-09-26 15:20:22 +01:00
parent 9243ea739e
commit 331c54063f
13 changed files with 490 additions and 274 deletions
+2
View File
@@ -27,6 +27,8 @@ std = [
"tracing/std",
"impl-serde/std",
"primitive-types/std",
"sp-core/std",
"sp-keyring/std",
"sp-crypto-hashing/std",
]
+1 -1
View File
@@ -41,7 +41,7 @@ impl<T: Config> Extrinsics<T> {
// Try to decode the extrinsic.
let decoded_info = frame_decode::extrinsics::decode_extrinsic(
cursor,
metadata.deref(),
&metadata,
metadata.types(),
)
.map_err(|error| BlockError::ExtrinsicDecodeError {
+86
View File
@@ -6,6 +6,19 @@ use crate::error::MetadataError;
use alloc::borrow::ToOwned;
use alloc::sync::Arc;
use frame_decode::extrinsics::{
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoError,
ExtrinsicSignatureInfo,
};
use frame_decode::storage::{
StorageEntry, StorageInfo, StorageInfoError
};
use frame_decode::runtime_apis::{
RuntimeApi, RuntimeApiInfo, RuntimeApiInfoError
};
use frame_decode::view_functions::{
ViewFunction, ViewFunctionInfo, ViewFunctionInfoError
};
/// A cheaply clone-able representation of the runtime metadata received from a node.
#[derive(Clone, Debug)]
@@ -20,6 +33,79 @@ impl core::ops::Deref for Metadata {
}
}
impl frame_decode::storage::StorageTypeInfo for Metadata {
type TypeId = u32;
fn storage_info(
&self,
pallet_name: &str,
storage_entry: &str,
) -> Result<StorageInfo<'_, Self::TypeId>, StorageInfoError<'_>> {
self.inner.storage_info(pallet_name, storage_entry)
}
fn storage_entries(&self) -> impl Iterator<Item = StorageEntry<'_>> {
self.inner.storage_entries()
}
}
impl frame_decode::runtime_apis::RuntimeApiTypeInfo for Metadata {
type TypeId = u32;
fn runtime_api_info(
&self,
trait_name: &str,
method_name: &str,
) -> Result<RuntimeApiInfo<'_, Self::TypeId>, RuntimeApiInfoError<'_>> {
self.inner.runtime_api_info(trait_name, method_name)
}
fn runtime_apis(&self) -> impl Iterator<Item = RuntimeApi<'_>> {
self.inner.runtime_apis()
}
}
impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
type TypeId = u32;
fn extrinsic_call_info(
&self,
pallet_index: u8,
call_index: u8,
) -> Result<ExtrinsicCallInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
self.inner.extrinsic_call_info(pallet_index, call_index)
}
fn extrinsic_signature_info(
&self,
) -> Result<ExtrinsicSignatureInfo<Self::TypeId>, ExtrinsicInfoError<'_>> {
self.inner.extrinsic_signature_info()
}
fn extrinsic_extension_info(
&self,
extension_version: Option<u8>,
) -> Result<ExtrinsicExtensionInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
self.inner.extrinsic_extension_info(extension_version)
}
}
impl frame_decode::view_functions::ViewFunctionTypeInfo for Metadata {
type TypeId = u32;
fn view_function_info(
&self,
pallet_name: &str,
function_name: &str,
) -> Result<ViewFunctionInfo<'_, Self::TypeId>, ViewFunctionInfoError<'_>> {
self.inner.view_function_info(pallet_name, function_name)
}
fn view_functions(&self) -> impl Iterator<Item = ViewFunction<'_>> {
self.inner.view_functions()
}
}
impl Metadata {
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
pub fn pallet_by_name_err(
+38 -166
View File
@@ -5,17 +5,28 @@
//! Construct addresses to access storage entries with.
use alloc::borrow::Cow;
use alloc::vec::Vec;
use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues};
use frame_decode::storage::IntoEncodableValues;
use scale_decode::DecodeAsType;
use crate::utils::Maybe;
/// A storage address. Concrete addresses are expected to implement either [`FetchableAddress`]
/// or [`IterableAddress`], which extends this to define fetchable and iterable storage keys.
/// 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 {
/// A set of types we'll hash and append to the prefix to build the storage key.
/// 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;
/// Type of the storage value at this location.
type Value: DecodeAsType;
/// Does the address have a default value defined for it.
/// Set to [`crate::utils::Yes`] to enable APIs which require one,
/// or [`crate::utils::Maybe`] to enable APIs which allow one
type HasDefaultValue;
/// Does the address point to a map (as opposed to a plain value)?
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
type IsMap;
/// The pallet containing this storage entry.
fn pallet_name(&self) -> &str;
@@ -23,69 +34,35 @@ pub trait Address {
/// The name of the storage entry.
fn entry_name(&self) -> &str;
/// Return the input key parts needed to point to this storage entry / entries.
fn key_parts(&self) -> impl IntoEncodableValues;
/// Return a unique hash for this address which can be used to validate it against metadata.
fn validation_hash(&self) -> Option<[u8; 32]>;
}
/// This trait represents any storage address which points to a single value we can fetch.
pub trait FetchableAddress: Address {
/// Does the address have a default value defined for it.
/// Set to [`Yes`] to enable APIs which require one.
type HasDefaultValue;
}
/// This trait represents any storage address which points to multiple 0 or more values to iterate over.
pub trait IterableAddress: Address {
/// The storage key values that we'll decode for each value
type OutputKeys: IntoDecodableValues;
}
/// An address which points to an individual storage value.
pub struct StaticFetchableAddress<KeyParts, Value, HasDefaultValue> {
/// An address which is generated by the static APIs.
pub struct StaticAddress<KeyParts, Value, HasDefaultValue, IsMap> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
key_parts: KeyParts,
validation_hash: Option<[u8; 32]>,
marker: core::marker::PhantomData<(Value, HasDefaultValue)>,
marker: core::marker::PhantomData<(KeyParts, Value, HasDefaultValue, IsMap)>,
}
impl<KeyParts, Value, HasDefaultValue> StaticFetchableAddress<KeyParts, Value, HasDefaultValue> {
/// Create a new [`StaticFetchableAddress`] using static strings for the pallet and call name.
impl<KeyParts, Value, HasDefaultValue, IsMap> StaticAddress<KeyParts, Value, HasDefaultValue, IsMap> {
/// 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,
key_parts: KeyParts,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
key_parts,
validation_hash: Some(hash),
marker: core::marker::PhantomData,
}
}
/// Create a new [`StaticFetchableAddress`].
pub fn new(
pallet_name: impl Into<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
key_parts: KeyParts,
) -> Self {
Self {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
key_parts,
validation_hash: None,
marker: core::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(mut self) -> Self {
self.validation_hash = None;
@@ -93,105 +70,16 @@ impl<KeyParts, Value, HasDefaultValue> StaticFetchableAddress<KeyParts, Value, H
}
}
impl<KeyParts, Value, HasDefaultValue> Address
for StaticFetchableAddress<KeyParts, Value, HasDefaultValue>
impl<KeyParts, Value, HasDefaultValue, IsMap> Address
for StaticAddress<KeyParts, Value, HasDefaultValue, IsMap>
where
KeyParts: IntoEncodableValues,
Value: DecodeAsType,
{
type KeyParts = KeyParts;
type Value = Value;
fn key_parts(&self) -> impl IntoEncodableValues {
&self.key_parts
}
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn entry_name(&self) -> &str {
&self.entry_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
impl<KeyParts, Value, HasDefaultValue> FetchableAddress
for StaticFetchableAddress<KeyParts, Value, HasDefaultValue>
where
KeyParts: IntoEncodableValues,
Value: DecodeAsType,
{
type HasDefaultValue = HasDefaultValue;
}
/// An address which points to a set of storage values.
pub struct StaticIterableAddress<InputKeyParts, OutputKeyParts, Value> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
input_key_parts: InputKeyParts,
validation_hash: Option<[u8; 32]>,
marker: core::marker::PhantomData<(OutputKeyParts, Value)>,
}
impl<InputKeyParts, OutputKeyParts, Value>
StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
{
/// Create a new [`StaticIterableAddress`] 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,
input_key_parts: InputKeyParts,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
input_key_parts,
validation_hash: Some(hash),
marker: core::marker::PhantomData,
}
}
/// Create a new [`StaticIterableAddress`].
pub fn new(
pallet_name: impl Into<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
input_key_parts: InputKeyParts,
) -> Self {
Self {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
input_key_parts,
validation_hash: None,
marker: core::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(mut self) -> Self {
self.validation_hash = None;
self
}
}
impl<InputKeyParts, OutputKeyParts, Value> Address
for StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
where
InputKeyParts: IntoEncodableValues,
Value: DecodeAsType,
{
type KeyParts = InputKeyParts;
type Value = Value;
fn key_parts(&self) -> impl IntoEncodableValues {
&self.input_key_parts
}
type IsMap = IsMap;
fn pallet_name(&self) -> &str {
&self.pallet_name
@@ -206,38 +94,22 @@ where
}
}
impl<InputKeyParts, OutputKeyParts, Value> IterableAddress
for StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
where
InputKeyParts: IntoEncodableValues,
OutputKeyParts: IntoDecodableValues,
Value: DecodeAsType,
{
type OutputKeys = OutputKeyParts;
}
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
/// entry could be a map and could have a default value.
pub type DynamicAddress<KeyParts, Value> = StaticAddress<KeyParts, Value, Maybe, Maybe>;
/// Construct a new dynamic storage fetch address.
pub fn dynamic_fetch<Keys: IntoEncodableValues>(
/// 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<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
storage_entry_keys: Keys,
) -> impl FetchableAddress {
StaticFetchableAddress::<Keys, scale_value::Value, ()>::new(
pallet_name,
entry_name,
storage_entry_keys,
)
) -> DynamicAddress<KeyParts, Value> {
DynamicAddress::<KeyParts, Value> {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
validation_hash: None,
marker: core::marker::PhantomData
}
}
/// Construct a new dynamic storage iter address.
pub fn dynamic_iter<Keys: IntoEncodableValues>(
pallet_name: impl Into<Cow<'static, str>>,
entry_name: impl Into<Cow<'static, str>>,
storage_entry_keys: Keys,
) -> impl IterableAddress {
StaticIterableAddress::<Keys, Vec<scale_value::Value>, scale_value::Value>::new(
pallet_name,
entry_name,
storage_entry_keys,
)
}
+7 -2
View File
@@ -41,6 +41,8 @@
//! println!("Alice's account info: {value:?}");
//! ```
mod prefix_of;
pub mod address;
use crate::{
@@ -52,6 +54,8 @@ use alloc::vec::Vec;
use frame_decode::storage::StorageTypeInfo;
use scale_decode::IntoVisitor;
pub use prefix_of::{ EqualOrPrefixOf, PrefixOf };
/// 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.
///
@@ -78,14 +82,15 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
/// 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>(
pub fn get_address_bytes<Addr: Address, Keys: EqualOrPrefixOf<Addr::KeyParts>>(
address: &Addr,
metadata: &Metadata,
keys: Keys,
) -> Result<Vec<u8>, Error> {
frame_decode::storage::encode_storage_key(
address.pallet_name(),
address.entry_name(),
&address.key_parts(),
&keys,
&**metadata,
metadata.types(),
)
+197
View File
@@ -0,0 +1,197 @@
// 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 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 {}
// Any reference which impls PrefixOf<K> also impls PrefixOf<K>
impl <'a, K, T: PrefixOf<K>> PrefixOf<K> for &'a 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 <'a, T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for &'a 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([]);
}
}
+6 -2
View File
@@ -73,8 +73,12 @@ 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 unit marker enum.
pub enum Yes {}
/// A unit marker enum.
pub enum Maybe {}
/// A unit marker enum.
pub enum No {}
/// A quick helper to encode some bytes to hex.
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {