diff --git a/Cargo.lock b/Cargo.lock index 03f606be52..614450fde2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5566,6 +5566,7 @@ dependencies = [ "bitvec", "derive-where", "either", + "frame-decode", "frame-metadata 23.0.0", "futures", "hex", diff --git a/core/Cargo.toml b/core/Cargo.toml index 8978c6322c..f41477cc87 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -27,6 +27,8 @@ std = [ "tracing/std", "impl-serde/std", "primitive-types/std", + "sp-core/std", + "sp-keyring/std", "sp-crypto-hashing/std", ] diff --git a/core/src/blocks/extrinsics.rs b/core/src/blocks/extrinsics.rs index d58af73483..cda5cc1aac 100644 --- a/core/src/blocks/extrinsics.rs +++ b/core/src/blocks/extrinsics.rs @@ -41,7 +41,7 @@ impl Extrinsics { // Try to decode the extrinsic. let decoded_info = frame_decode::extrinsics::decode_extrinsic( cursor, - metadata.deref(), + &metadata, metadata.types(), ) .map_err(|error| BlockError::ExtrinsicDecodeError { diff --git a/core/src/metadata/metadata_type.rs b/core/src/metadata/metadata_type.rs index 4fe325a035..b30cdc0427 100644 --- a/core/src/metadata/metadata_type.rs +++ b/core/src/metadata/metadata_type.rs @@ -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, StorageInfoError<'_>> { + self.inner.storage_info(pallet_name, storage_entry) + } + + fn storage_entries(&self) -> impl Iterator> { + 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, RuntimeApiInfoError<'_>> { + self.inner.runtime_api_info(trait_name, method_name) + } + + fn runtime_apis(&self) -> impl Iterator> { + 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, ExtrinsicInfoError<'_>> { + self.inner.extrinsic_call_info(pallet_index, call_index) + } + + fn extrinsic_signature_info( + &self, + ) -> Result, ExtrinsicInfoError<'_>> { + self.inner.extrinsic_signature_info() + } + + fn extrinsic_extension_info( + &self, + extension_version: Option, + ) -> Result, 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, ViewFunctionInfoError<'_>> { + self.inner.view_function_info(pallet_name, function_name) + } + + fn view_functions(&self) -> impl Iterator> { + 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( diff --git a/core/src/storage/address.rs b/core/src/storage/address.rs index 433caf8704..3115a82c22 100644 --- a/core/src/storage/address.rs +++ b/core/src/storage/address.rs @@ -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 { +/// An address which is generated by the static APIs. +pub struct StaticAddress { 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 StaticFetchableAddress { - /// Create a new [`StaticFetchableAddress`] using static strings for the pallet and call name. +impl StaticAddress { + /// 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>, - entry_name: impl Into>, - 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 StaticFetchableAddress Address - for StaticFetchableAddress +impl Address + for StaticAddress 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 FetchableAddress - for StaticFetchableAddress -where - KeyParts: IntoEncodableValues, - Value: DecodeAsType, -{ type HasDefaultValue = HasDefaultValue; -} - -/// An address which points to a set of storage values. -pub struct StaticIterableAddress { - 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 - StaticIterableAddress -{ - /// 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>, - entry_name: impl Into>, - 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 Address - for StaticIterableAddress -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 IterableAddress - for StaticIterableAddress -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 = StaticAddress; -/// Construct a new dynamic storage fetch address. -pub fn dynamic_fetch( +/// 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( pallet_name: impl Into>, entry_name: impl Into>, - storage_entry_keys: Keys, -) -> impl FetchableAddress { - StaticFetchableAddress::::new( - pallet_name, - entry_name, - storage_entry_keys, - ) +) -> DynamicAddress { + DynamicAddress:: { + 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( - pallet_name: impl Into>, - entry_name: impl Into>, - storage_entry_keys: Keys, -) -> impl IterableAddress { - StaticIterableAddress::, scale_value::Value>::new( - pallet_name, - entry_name, - storage_entry_keys, - ) -} diff --git a/core/src/storage/mod.rs b/core/src/storage/mod.rs index 3c4ae9fc6c..5e2020390b 100644 --- a/core/src/storage/mod.rs +++ b/core/src/storage/mod.rs @@ -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(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( +pub fn get_address_bytes>( address: &Addr, metadata: &Metadata, + keys: Keys, ) -> Result, Error> { frame_decode::storage::encode_storage_key( address.pallet_name(), address.entry_name(), - &address.key_parts(), + &keys, &**metadata, metadata.types(), ) diff --git a/core/src/storage/prefix_of.rs b/core/src/storage/prefix_of.rs new file mode 100644 index 0000000000..b397c79a27 --- /dev/null +++ b/core/src/storage/prefix_of.rs @@ -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: IntoEncodableValues {} + +// Any reference which impls PrefixOf also impls PrefixOf +impl <'a, K, T: PrefixOf> PrefixOf 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 PrefixOf<(A,)> for () {} + +impl PrefixOf<(A,B)> for () {} +impl PrefixOf<(A, B)> for (A,) +where (A,): IntoEncodableValues {} + +impl PrefixOf<(A, B, C)> for () {} +impl PrefixOf<(A, B, C)> for (A,) +where (A,): IntoEncodableValues {} +impl PrefixOf<(A, B, C)> for (A, B) +where (A, B): IntoEncodableValues {} + +impl PrefixOf<(A, B, C, D)> for () {} +impl PrefixOf<(A, B, C, D)> for (A,) +where (A,): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D)> for (A, B) +where (A, B): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D)> for (A, B, C) +where (A, B, C): IntoEncodableValues {} + +impl PrefixOf<(A, B, C, D, E)> for () {} +impl PrefixOf<(A, B, C, D, E)> for (A,) +where (A,): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D, E)> for (A, B) +where (A, B): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D, E)> for (A, B, C) +where (A, B, C): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D, E)> for (A, B, C, D) +where (A, B, C, D): IntoEncodableValues {} + +impl PrefixOf<(A, B, C, D, E, F)> for () {} +impl PrefixOf<(A, B, C, D, E, F)> for (A,) +where (A,): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D, E, F)> for (A, B) +where (A, B): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D, E, F)> for (A, B, C) +where (A, B, C): IntoEncodableValues {} +impl PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D) +where (A, B, C, D): IntoEncodableValues {} +impl 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 PrefixOf> for Vec {} + +// 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 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`] does. +pub trait EqualOrPrefixOf: IntoEncodableValues {} + +// Tuples +macro_rules! tuple_impl_eq { + ($($t:ident)+) => { + // Any T that is a PrefixOf impls EqualOrPrefixOf too + impl <$($t,)+ T: PrefixOf<($($t,)+)>> EqualOrPrefixOf<($($t,)+)> for T {} + // Keys impls EqualOrPrefixOf + impl <$($t),+> EqualOrPrefixOf<($($t,)+)> for ($($t,)+) where ($($t,)+): IntoEncodableValues {} + // &'a Keys impls EqualOrPrefixOf + 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 EqualOrPrefixOf> for Vec {} +impl <'a, T: EncodeAsType> EqualOrPrefixOf> for &'a Vec {} + +// Arrays +macro_rules! array_impl_eq { + ($($n:literal)+) => { + $( + impl EqualOrPrefixOf<[A; $n]> for [A; $n] {} + impl <'a, A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for &'a [A; $n] {} + )+ + } +} + +impl 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(core::marker::PhantomData); + + impl Test { + fn new() -> Self { + Test(core::marker::PhantomData) + } + fn accepts_prefix_of>(&self, keys: P) { + let _encoder = keys.into_encodable_values(); + } + fn accepts_eq_or_prefix_of>(&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([]); + } +} \ No newline at end of file diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index 42c499f85c..741aa34841 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -73,8 +73,12 @@ unsafe impl Sync for PhantomDataSendSync {} /// as `BTreeMap` which allows us to easily swap the two during codegen. pub type KeyedVec = 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 { diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 7caf8df4d0..7ae5336067 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -90,6 +90,7 @@ sp-crypto-hashing = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } frame-metadata = { workspace = true } +frame-decode = { workspace = true } either = { workspace = true } web-time = { workspace = true } diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 00326d7f4d..6702b74449 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -10,7 +10,7 @@ use crate::{ error::{BlockError, DecodeError, Error}, events, runtime_api::RuntimeApi, - storage::Storage, + storage::StorageClientAt, }; use codec::{Decode, Encode}; @@ -104,8 +104,8 @@ where } /// Work with storage. - pub fn storage(&self) -> Storage { - Storage::new(self.client.clone(), self.block_ref.clone()) + pub fn storage(&self) -> StorageClientAt { + StorageClientAt::new(self.client.clone(), self.block_ref.clone()) } /// Execute a runtime API call at this block. diff --git a/subxt/src/storage/mod.rs b/subxt/src/storage/mod.rs index 6a1b18bf0b..d5e7e957e3 100644 --- a/subxt/src/storage/mod.rs +++ b/subxt/src/storage/mod.rs @@ -4,94 +4,12 @@ //! Types associated with accessing and working with storage items. -// mod storage_client; -// mod storage_type; +mod storage_client; +mod storage_client_at; -// pub use storage_client::StorageClient; -// pub use storage_type::{Storage, StorageKeyValuePair}; -// pub use subxt_core::storage::address::{ -// Address, DefaultAddress, DynamicAddress, StaticAddress, StaticStorageKey, StorageKey, dynamic, -// }; // TODO re-add - -use crate::{ - backend::BlockRef, - client::{OfflineClientT, OnlineClientT}, - config::{Config, HashFor}, - error::Error, +pub use storage_client::StorageClient; +pub use storage_client_at::StorageClientAt; +pub use subxt_core::storage::address::{ + Address, StaticAddress, DynamicAddress, dynamic, }; -use derive_where::derive_where; -use std::{future::Future, marker::PhantomData}; - - -/// Query the runtime storage. -#[derive_where(Clone; Client)] -pub struct StorageClient { - client: Client, - _marker: PhantomData, -} - -impl StorageClient { - /// Create a new [`StorageClient`] - pub fn new(client: Client) -> Self { - Self { - client, - _marker: PhantomData, - } - } -} - -impl StorageClient -where - T: Config, - Client: OfflineClientT, -{ - /// Run the validation logic against some storage 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). - /// Return an error if the address was not valid or something went wrong trying to validate it (ie - /// the pallet or storage entry in question do not exist at all). - pub fn validate(&self, address: &Addr) -> Result<(), Error> { - subxt_core::storage::validate(address, &self.client.metadata()).map_err(Into::into) - } - - /// Convert some storage address into the raw bytes that would be submitted to the node in order - /// to retrieve the entries at the root of the associated address. - pub fn address_root_bytes(&self, address: &Addr) -> Vec { - subxt_core::storage::get_address_root_bytes(address) - } - - /// Convert some storage address into the raw bytes that would be submitted to the node in order - /// to retrieve an entry. This fails if [`Address::append_entry_bytes`] does; in the built-in - /// implementation this would be if the pallet and storage entry being asked for is not available on the - /// node you're communicating with, or if the metadata is missing some type information (which should not - /// happen). - pub fn address_bytes(&self, address: &Addr) -> Result, Error> { - subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into) - } -} - -impl StorageClient -where - T: Config, - Client: OnlineClientT, -{ - /// Obtain storage at some block hash. - pub fn at(&self, block_ref: impl Into>>) -> Storage { - Storage::new(self.client.clone(), block_ref.into()) - } - - /// Obtain storage at the latest finalized block. - pub fn at_latest( - &self, - ) -> impl Future, Error>> + Send + 'static { - // Clone and pass the client in like this so that we can explicitly - // return a Future that's Send + 'static, rather than tied to &self. - let client = self.client.clone(); - async move { - // get the ref for the latest finalized block and use that. - let block_ref = client.backend().latest_finalized_block_ref().await?; - - Ok(Storage::new(client, block_ref)) - } - } -} \ No newline at end of file diff --git a/subxt/src/storage/storage_client.rs b/subxt/src/storage/storage_client.rs index c02a4ba157..39a4a38b15 100644 --- a/subxt/src/storage/storage_client.rs +++ b/subxt/src/storage/storage_client.rs @@ -2,7 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use super::storage_type::Storage; +use super::storage_client_at::StorageClientAt; use crate::{ backend::BlockRef, client::{OfflineClientT, OnlineClientT}, @@ -12,6 +12,7 @@ use crate::{ use derive_where::derive_where; use std::{future::Future, marker::PhantomData}; use subxt_core::storage::address::Address; +use subxt_core::storage::EqualOrPrefixOf; /// Query the runtime storage. #[derive_where(Clone; Client)] @@ -45,7 +46,7 @@ where /// Convert some storage address into the raw bytes that would be submitted to the node in order /// to retrieve the entries at the root of the associated address. - pub fn address_root_bytes(&self, address: &Addr) -> Vec { + pub fn address_root_bytes(&self, address: &Addr) -> [u8; 32] { subxt_core::storage::get_address_root_bytes(address) } @@ -54,8 +55,8 @@ where /// implementation this would be if the pallet and storage entry being asked for is not available on the /// node you're communicating with, or if the metadata is missing some type information (which should not /// happen). - pub fn address_bytes(&self, address: &Addr) -> Result, Error> { - subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into) + pub fn address_bytes>(&self, address: &Addr, keys: Keys) -> Result, Error> { + subxt_core::storage::get_address_bytes(address, &self.client.metadata(), keys).map_err(Into::into) } } @@ -65,14 +66,14 @@ where Client: OnlineClientT, { /// Obtain storage at some block hash. - pub fn at(&self, block_ref: impl Into>>) -> Storage { - Storage::new(self.client.clone(), block_ref.into()) + pub fn at(&self, block_ref: impl Into>>) -> StorageClientAt { + StorageClientAt::new(self.client.clone(), block_ref.into()) } /// Obtain storage at the latest finalized block. pub fn at_latest( &self, - ) -> impl Future, Error>> + Send + 'static { + ) -> impl Future, Error>> + Send + 'static { // Clone and pass the client in like this so that we can explicitly // return a Future that's Send + 'static, rather than tied to &self. let client = self.client.clone(); @@ -80,7 +81,7 @@ where // get the ref for the latest finalized block and use that. let block_ref = client.backend().latest_finalized_block_ref().await?; - Ok(Storage::new(client, block_ref)) + Ok(StorageClientAt::new(client, block_ref)) } } } diff --git a/subxt/src/storage/storage_type.rs b/subxt/src/storage/storage_client_at.rs similarity index 70% rename from subxt/src/storage/storage_type.rs rename to subxt/src/storage/storage_client_at.rs index b82539dc83..1d837d994a 100644 --- a/subxt/src/storage/storage_type.rs +++ b/subxt/src/storage/storage_client_at.rs @@ -13,21 +13,21 @@ use codec::Decode; use derive_where::derive_where; use futures::StreamExt; use std::{future::Future, marker::PhantomData}; -use subxt_core::storage::address::{Address, StorageHashers, StorageKey}; -use subxt_core::utils::Yes; +use subxt_core::storage::address::Address; +use subxt_core::utils::{Maybe, Yes, No}; /// This is returned from a couple of storage functions. pub use crate::backend::StreamOfResults; /// Query the runtime storage. #[derive_where(Clone; Client)] -pub struct Storage { +pub struct StorageClientAt { client: Client, block_ref: BlockRef>, _marker: PhantomData, } -impl Storage { +impl StorageClientAt { /// Create a new [`Storage`] pub(crate) fn new(client: Client, block_ref: BlockRef>) -> Self { Self { @@ -38,6 +38,134 @@ impl Storage { } } +impl StorageClientAt +where + T: Config, + Client: OnlineClientT, +{ + pub fn entry(&self, address: Addr) -> Result, Error> { + subxt_core::storage::validate(&address, &self.client.metadata())?; + + use frame_decode::storage::StorageTypeInfo; + let storage_info = self + .client + .metadata() + .storage_info(address.pallet_name(), address.entry_name())?; + + let value = if storage_info.keys.is_empty() { + StorageEntryClientValue::Plain(StorageEntryPlainClient { + client: self.client.clone(), + block_ref: self.block_ref.clone(), + address, + _marker: core::marker::PhantomData + }) + } else { + StorageEntryClientValue::Map(StorageEntryMapClient { + client: self.client.clone(), + block_ref: self.block_ref.clone(), + address, + _marker: core::marker::PhantomData + }) + }; + + Ok(StorageEntryClient { + value, + marker: core::marker::PhantomData + }) + } +} + +pub struct StorageEntryClient { + value: StorageEntryClientValue, + marker: core::marker::PhantomData, +} + +enum StorageEntryClientValue { + Plain(StorageEntryPlainClient), + Map(StorageEntryMapClient), +} + +impl StorageEntryClient { + pub fn pallet_name(&self) -> &str { + match &self.value { + StorageEntryClientValue::Plain(client) => client.address.pallet_name(), + StorageEntryClientValue::Map(client) => client.address.pallet_name(), + } + } + + pub fn storage_name(&self) -> &str { + match &self.value { + StorageEntryClientValue::Plain(client) => client.address.entry_name(), + StorageEntryClientValue::Map(client) => client.address.entry_name(), + } + } +} + +// When IsMap = Yes, we have statically asserted that the entry is a map. This can only be false +// if we skip validation of a static call, and the storage entry, while still present, has changed from +// plain to map. +impl StorageEntryClient { + pub fn into_map(self) -> StorageEntryMapClient { + match self.value { + StorageEntryClientValue::Map(this) => this, + StorageEntryClientValue::Plain(_) => panic!("When IsMap = Yes, StorageEntryClient should always be a map.") + } + } +} + +// When IsMap = No, we have statically asserted that the entry is a plain value. This can only be false +// if we skip validation of a static call, and the storage entry, while still present, has changed from +// map to plain value. +impl StorageEntryClient { + pub fn into_plain(self) -> StorageEntryPlainClient { + match self.value { + StorageEntryClientValue::Map(_) => panic!("When IsMap = No, StorageEntryClient should always be a plain value."), + StorageEntryClientValue::Plain(this) => this, + } + } +} + +// Regardless, we can do the "safe" thing and try to convert the entry into a map or plain entry. +impl StorageEntryClient { + pub fn into_map(self) -> Option> { + match self.value { + StorageEntryClientValue::Map(client) => Some(client), + StorageEntryClientValue::Plain(_) => None, + } + } + + pub fn into_plain(self) -> Option> { + match self.value { + StorageEntryClientValue::Plain(client) => Some(client), + StorageEntryClientValue::Map(_) => None, + } + } + + pub fn is_plain(&self) -> bool { + matches!(self.value, StorageEntryClientValue::Plain(_)) + } + + pub fn is_map(&self) -> bool { + matches!(self.value, StorageEntryClientValue::Map(_)) + } +} + +pub struct StorageEntryPlainClient { + client: Client, + block_ref: BlockRef>, + address: Addr, + _marker: PhantomData, +} + +pub struct StorageEntryMapClient { + client: Client, + block_ref: BlockRef>, + address: Addr, + _marker: PhantomData, +} + + +/* impl Storage where T: Config, @@ -321,3 +449,4 @@ pub struct StorageKeyValuePair { /// The value of the storage entry. pub value: T::Target, } +*/ \ No newline at end of file