diff --git a/new/src/client.rs b/new/src/client.rs index 9699b29b35..4f5db17ba1 100644 --- a/new/src/client.rs +++ b/new/src/client.rs @@ -5,7 +5,8 @@ use crate::config::{Config, HashFor}; use crate::error::{EventsError, ExtrinsicError}; use crate::events::Events; use crate::extrinsics::Extrinsics; -use crate::transactions::Transactions; +use crate::storage::StorageClient; +use crate::transactions::TransactionsClient; use core::marker::PhantomData; use subxt_metadata::Metadata; @@ -35,8 +36,13 @@ where Client: OfflineClientAtBlockT, { /// Construct and submit transactions. - pub fn tx(&self) -> Transactions { - Transactions::new(self.client.clone()) + pub fn tx(&self) -> TransactionsClient { + TransactionsClient::new(self.client.clone()) + } + + /// Access storage at this block. + pub fn storage(&self) -> StorageClient { + StorageClient::new(self.client.clone()) } /// Obtain a reference to the metadata. diff --git a/new/src/extrinsics.rs b/new/src/extrinsics.rs index 913a0a926c..09d0fbf9b7 100644 --- a/new/src/extrinsics.rs +++ b/new/src/extrinsics.rs @@ -10,6 +10,7 @@ use crate::events::{self, DecodeAsEvent}; use frame_decode::extrinsics::Extrinsic as ExtrinsicInfo; use scale_decode::{DecodeAsFields, DecodeAsType}; use std::marker::PhantomData; +use std::sync::Arc; use subxt_metadata::Metadata; pub use decode_as_extrinsic::DecodeAsExtrinsic; @@ -18,9 +19,10 @@ pub use extrinsic_transaction_extensions::{ }; /// The extrinsics in a block. +#[derive(Debug, Clone)] pub struct Extrinsics { client: C, - extrinsics: Vec>, + extrinsics: Arc>>, marker: PhantomData, } @@ -36,7 +38,7 @@ impl> Extrinsics { Ok(Extrinsics { client, - extrinsics, + extrinsics: Arc::new(extrinsics), marker: PhantomData, }) } diff --git a/new/src/lib.rs b/new/src/lib.rs index b8e3965367..c4ebd23942 100644 --- a/new/src/lib.rs +++ b/new/src/lib.rs @@ -38,6 +38,7 @@ pub mod config; pub mod error; pub mod events; pub mod extrinsics; +pub mod storage; pub mod transactions; pub mod utils; // pub mod book; diff --git a/new/src/storage.rs b/new/src/storage.rs new file mode 100644 index 0000000000..918fb0fb13 --- /dev/null +++ b/new/src/storage.rs @@ -0,0 +1,208 @@ +mod prefix_of; +mod storage_entry; +mod storage_key; +mod storage_key_value; +mod storage_value; + +use crate::backend::BackendExt; +use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT}; +use crate::config::Config; +use crate::error::StorageError; +use address::Address; +use core::marker::PhantomData; +use frame_decode::helpers::Entry; +use frame_decode::storage::StorageEntryInfo; +use std::borrow::Cow; + +pub use prefix_of::PrefixOf; +pub use storage_entry::StorageEntry; +pub use storage_key::{StorageKey, StorageKeyPart}; +pub use storage_key_value::StorageKeyValue; +pub use storage_value::StorageValue; +pub mod address; + +/// A client for working with transactions. +#[derive(Clone)] +pub struct StorageClient { + client: Client, + marker: PhantomData, +} + +impl StorageClient { + pub(crate) fn new(client: Client) -> Self { + StorageClient { + client, + marker: PhantomData, + } + } +} + +impl> StorageClient { + /// 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(&self, address: Addr) -> Result<(), StorageError> { + let Some(hash) = address.validation_hash() else { + return Ok(()); + }; + + let pallet_name = address.pallet_name(); + let entry_name = address.entry_name(); + + let pallet_metadata = self + .client + .metadata_ref() + .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(), + } + })?; + + if storage_hash != hash { + Err(StorageError::IncompatibleCodegen) + } else { + Ok(()) + } + } + + /// This returns a [`StorageEntry`], which allows working with the storage entry at the provided address. + pub fn entry( + &self, + address: Addr, + ) -> Result, StorageError> { + self.validate(&address)?; + StorageEntry::new(&self.client, address) + } + + /// Iterate over all of the storage entries listed in the metadata for the current block. This does **not** include well known + /// storage entries like `:code` which are not listed in the metadata. + pub fn entries(&self) -> impl Iterator> { + let metadata = self.client.metadata_ref(); + Entry::tuples_of(metadata.storage_entries()).map(|(pallet_name, entry_name)| { + StorageEntries { + pallet_name: pallet_name.clone(), + entry_name, + client: &self.client, + marker: std::marker::PhantomData, + } + }) + } +} + +impl> StorageClient { + /// This is essentially a shorthand for `client.entry(addr)?.fetch(key_parts)`. See [`StorageEntry::fetch()`]. + pub async fn fetch( + &self, + addr: Addr, + key_parts: Addr::KeyParts, + ) -> Result, StorageError> { + let entry = self.entry(addr)?; + entry.internal_fetch(key_parts).await + } + + /// This is essentially a shorthand for `client.entry(addr)?.try_fetch(key_parts)`. See [`StorageEntry::try_fetch()`]. + pub async fn try_fetch( + &self, + addr: Addr, + key_parts: Addr::KeyParts, + ) -> Result>, StorageError> { + let entry = self.entry(addr)?; + entry.internal_try_fetch(key_parts).await + } + + /// This is essentially a shorthand for `client.entry(addr)?.iter(key_parts)`. See [`StorageEntry::iter()`]. + pub async fn iter>( + &self, + addr: Addr, + key_parts: KeyParts, + ) -> Result< + impl futures::Stream, StorageError>> + + use<'_, Addr, Client, T, KeyParts>, + StorageError, + > { + let entry = self.entry(addr)?; + entry.internal_iter(key_parts).await + } + + /// In rare cases, you may wish to fetch a storage value that does not live at a typical address. This method + /// is a fallback for those cases, and allows you to provide the raw storage key bytes corresponding to the + /// entry you wish to obtain. The response will either be the bytes for the value found at that location, or + /// otherwise an error. [`StorageError::NoValueFound`] will be returned in the event that the request was valid + /// but no value lives at the given location). + pub async fn fetch_raw(&self, key_bytes: Vec) -> Result, StorageError> { + let block_hash = self.client.block_hash(); + let value = self + .client + .backend() + .storage_fetch_value(key_bytes, block_hash) + .await + .map_err(StorageError::CannotFetchValue)? + .ok_or(StorageError::NoValueFound)?; + + Ok(value) + } + + /// The storage version of a pallet. + /// The storage version refers to the `frame_support::traits::Metadata::StorageVersion` type. + pub async fn storage_version(&self, pallet_name: impl AsRef) -> Result { + // construct the storage key. This is done similarly in + // `frame_support::traits::metadata::StorageVersion::storage_key()`: + let mut key_bytes: Vec = vec![]; + key_bytes.extend(&sp_crypto_hashing::twox_128( + pallet_name.as_ref().as_bytes(), + )); + key_bytes.extend(&sp_crypto_hashing::twox_128(b":__STORAGE_VERSION__:")); + + // fetch the raw bytes and decode them into the StorageVersion struct: + let storage_version_bytes = self.fetch_raw(key_bytes).await?; + + ::decode(&mut &storage_version_bytes[..]) + .map_err(StorageError::CannotDecodeStorageVersion) + } + + /// Fetch the runtime WASM code. + pub async fn runtime_wasm_code(&self) -> Result, StorageError> { + // note: this should match the `CODE` constant in `sp_core::storage::well_known_keys` + self.fetch_raw(b":code".to_vec()).await + } +} + +/// Working with a specific storage entry. +pub struct StorageEntries<'atblock, Client, T> { + pallet_name: Cow<'atblock, str>, + entry_name: Cow<'atblock, str>, + client: &'atblock Client, + marker: std::marker::PhantomData, +} + +impl<'atblock, Client, T> StorageEntries<'atblock, Client, T> +where + T: Config, + Client: OfflineClientAtBlockT, +{ + /// The pallet name. + pub fn pallet_name(&self) -> &str { + &self.pallet_name + } + + /// The storage entry name. + pub fn entry_name(&self) -> &str { + &self.entry_name + } + + /// Extract the relevant storage information so that we can work with this entry. + pub fn entry( + &self, + ) -> Result< + StorageEntry<'_, T, Client, address::DynamicAddress, crate::utils::Maybe>, + StorageError, + > { + let addr = address::dynamic(self.pallet_name.to_owned(), self.entry_name.to_owned()); + StorageEntry::new(self.client, addr) + } +} diff --git a/new/src/storage/address.rs b/new/src/storage/address.rs new file mode 100644 index 0000000000..4dd5fdd20b --- /dev/null +++ b/new/src/storage/address.rs @@ -0,0 +1,169 @@ +// 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. + +//! Construct addresses to access storage entries with. + +use crate::utils::{Maybe, YesMaybe}; +use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues}; +use scale_decode::DecodeAsType; +use std::borrow::Cow; + +/// 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 { + /// 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 pallet containing this storage entry. + fn pallet_name(&self) -> &str; + + /// The name of the storage entry. + fn entry_name(&self) -> &str; + + /// 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 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) + } + + fn validation_hash(&self) -> Option<[u8; 32]> { + A::validation_hash(*self) + } +} + +/// An address which is generated by the static APIs. +pub struct StaticAddress { + pallet_name: Cow<'static, str>, + entry_name: Cow<'static, str>, + validation_hash: Option<[u8; 32]>, + marker: core::marker::PhantomData<(KeyParts, Value, IsPlain)>, +} + +impl Clone for StaticAddress { + fn clone(&self) -> Self { + Self { + pallet_name: self.pallet_name.clone(), + entry_name: self.entry_name.clone(), + validation_hash: self.validation_hash, + marker: self.marker, + } + } +} + +impl core::fmt::Debug for StaticAddress { + 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 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, hash: [u8; 32]) -> Self { + Self { + pallet_name: Cow::Borrowed(pallet_name), + entry_name: Cow::Borrowed(entry_name), + validation_hash: Some(hash), + marker: core::marker::PhantomData, + } + } + + /// Create a new address. + pub fn new(pallet_name: impl Into, entry_name: impl Into) -> Self { + Self { + pallet_name: pallet_name.into().into(), + entry_name: entry_name.into().into(), + 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 StaticAddress +where + KeyParts: IntoEncodableValues + IntoDecodableValues, + Value: DecodeAsType, + IsPlain: YesMaybe, +{ + type KeyParts = KeyParts; + type Value = Value; + type IsPlain = IsPlain; + + 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, B: AsRef> Address for (A, B) { + type KeyParts = Vec; + 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, Value = scale_value::Value> = + StaticAddress; + +/// 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, +) -> DynamicAddress { + DynamicAddress::::new(pallet_name.into(), entry_name.into()) +} diff --git a/new/src/storage/prefix_of.rs b/new/src/storage/prefix_of.rs new file mode 100644 index 0000000000..4f9244fadf --- /dev/null +++ b/new/src/storage/prefix_of.rs @@ -0,0 +1,194 @@ +// 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 {} + +// If T impls PrefixOf, &T impls PrefixOf. +impl> PrefixOf 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 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 EqualOrPrefixOf> for &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([]); + } +} diff --git a/new/src/storage/storage_entry.rs b/new/src/storage/storage_entry.rs new file mode 100644 index 0000000000..dd3fa97c31 --- /dev/null +++ b/new/src/storage/storage_entry.rs @@ -0,0 +1,336 @@ +use crate::backend::BackendExt; +use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT}; +use crate::config::Config; +use crate::error::StorageError; +use crate::storage::address::Address; +use crate::storage::{PrefixOf, StorageKeyValue, StorageValue}; +use crate::utils::{Maybe, Yes, YesMaybe}; +use core::marker::PhantomData; +use frame_decode::storage::{IntoEncodableValues, StorageInfo, StorageTypeInfo}; +use futures::StreamExt; +use std::sync::Arc; + +/// This represents a single storage entry (be it a plain value or map) +/// and the operations that can be performed on it. +#[derive(Debug)] +pub struct StorageEntry<'atblock, T: Config, Client, Addr, IsPlain> { + inner: Arc>, + marker: PhantomData<(T, IsPlain)>, +} + +impl<'atblock, T: Config, Client, Addr, IsPlain> Clone + for StorageEntry<'atblock, T, Client, Addr, IsPlain> +{ + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + marker: self.marker, + } + } +} + +#[derive(Debug)] +struct StorageEntryInner<'atblock, Addr, Client> { + address: Addr, + info: Arc>, + client: &'atblock Client, +} + +impl<'atblock, T, Client, Addr, IsPlain> StorageEntry<'atblock, T, Client, Addr, IsPlain> +where + T: Config, + Addr: Address, + Client: OfflineClientAtBlockT, +{ + pub(crate) fn new(client: &'atblock Client, address: Addr) -> Result { + let info = client + .metadata_ref() + .storage_info(address.pallet_name(), address.entry_name()) + .map_err(|e| StorageError::StorageInfoError(e.into_owned()))?; + + let inner = StorageEntryInner { + address, + info: Arc::new(info), + client, + }; + + Ok(Self { + inner: Arc::new(inner), + marker: PhantomData, + }) + } + + /// Name of the pallet containing this storage entry. + pub fn pallet_name(&self) -> &str { + self.inner.address.pallet_name() + } + + /// Name of the storage entry. + pub fn entry_name(&self) -> &str { + self.inner.address.entry_name() + } + + /// Is the storage entry a plain value? + pub fn is_plain(&self) -> bool { + self.inner.info.keys.is_empty() + } + + /// Is the storage entry a map? + pub fn is_map(&self) -> bool { + !self.is_plain() + } + + /// Return the default value for this storage entry, if there is one. Returns `None` if there + /// is no default value. + pub fn default_value(&self) -> Option> { + let info = &self.inner.info; + let client = self.inner.client; + info.default_value.as_ref().map(|default_value| { + StorageValue::new( + info.clone(), + client.metadata_ref().types(), + default_value.to_vec(), + ) + }) + } + + /// Create the bytes for a storage key given the key parts. + /// + /// **Warning:** This provides no safety around the provided keys in order that it can be used + /// behind the scenes in several places. + pub(crate) fn internal_key_bytes( + &self, + key_parts: Keys, + ) -> Result, StorageError> { + let key = frame_decode::storage::encode_storage_key_with_info( + self.pallet_name(), + self.entry_name(), + key_parts, + &self.inner.info, + self.inner.client.metadata_ref().types(), + ) + .map_err(StorageError::StorageKeyEncodeError)?; + + Ok(key) + } +} + +impl<'atblock, T, Client, Addr, IsPlain> StorageEntry<'atblock, T, Client, Addr, IsPlain> +where + T: Config, + Addr: Address, + Client: OnlineClientAtBlockT, +{ + /// Fetch a value, using the default value if none can be found, or returning an error + /// if no value exists at this location and there is no default value. + /// + /// **Warning:** This provides no safety around the provided keys in order that it can be used + /// behind the scenes in several places. + pub(crate) async fn internal_fetch( + &self, + key_parts: impl IntoEncodableValues, + ) -> Result, StorageError> { + let value = self + .internal_try_fetch(key_parts) + .await? + .or_else(|| self.default_value()) + .ok_or(StorageError::NoValueFound)?; + + Ok(value) + } + + /// Fetch a value, returning `None` if no value exists at that location. + /// + /// **Warning:** This provides no safety around the provided keys in order that it can be used + /// behind the scenes in several places. + pub(crate) async fn internal_try_fetch( + &self, + key_parts: impl IntoEncodableValues, + ) -> Result>, StorageError> { + let key = self.internal_key_bytes(key_parts)?; + let block_hash = self.inner.client.block_hash(); + + let value = self + .inner + .client + .backend() + .storage_fetch_value(key, block_hash) + .await + .map_err(StorageError::CannotFetchValue)? + .map(|bytes| { + StorageValue::new( + self.inner.info.clone(), + self.inner.client.metadata_ref().types(), + bytes, + ) + }) + .or_else(|| self.default_value()); + + Ok(value) + } + + /// Iterate over the values under the provided key. + /// + /// **Warning:** This provides no safety around the provided keys in order that it can be used + /// behind the scenes in several places. + pub(crate) async fn internal_iter>( + &self, + key_parts: KeyParts, + ) -> Result< + impl futures::Stream, StorageError>> + + use<'atblock, Addr, Client, T, KeyParts, IsPlain>, + StorageError, + > { + let info = self.inner.info.clone(); + let types = self.inner.client.metadata_ref().types(); + let key_bytes = self.internal_key_bytes(key_parts)?; + let block_hash = self.inner.client.block_hash(); + + let stream = self + .inner + .client + .backend() + .storage_fetch_descendant_values(key_bytes, block_hash) + .await + .map_err(StorageError::CannotIterateValues)? + .map(move |kv| { + let kv = match kv { + Ok(kv) => kv, + Err(e) => return Err(StorageError::StreamFailure(e)), + }; + Ok(StorageKeyValue::new( + info.clone(), + types, + kv.key.into(), + kv.value, + )) + }); + + Ok(Box::pin(stream)) + } +} + +// Plain values get a fetch method with no extra arguments. +impl<'atblock, T, Client, Addr> StorageEntry<'atblock, T, Client, Addr, Yes> +where + T: Config, + Addr: Address, + Client: OnlineClientAtBlockT, +{ + /// Fetch the storage value at this location. If no value is found, the default value will be returned + /// for this entry if one exists. If no value is found and no default value exists, an error will be returned. + pub async fn fetch(&self) -> Result, StorageError> { + self.internal_fetch(()).await + } + + /// Fetch the storage value at this location. If no value is found, `None` will be returned. + pub async fn try_fetch( + &self, + ) -> Result>, StorageError> { + self.internal_try_fetch(()).await + } + + /// This is identical to [`StorageEntry::key_prefix()`] and is the full + /// key for this storage entry. + pub fn key(&self) -> [u8; 32] { + self.key_prefix() + } + + /// 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.pallet_name(), self.entry_name()) + } +} + +// When HasDefaultValue = Yes, we expect there to exist a valid default value and will use that +// if we fetch an entry and get nothing back. +impl<'atblock, T, Client, Addr> StorageEntry<'atblock, T, Client, Addr, Maybe> +where + T: Config, + Addr: Address, + Client: OnlineClientAtBlockT, +{ + /// Fetch a storage value within this storage entry. + /// + /// This entry may be a map, and so you must provide the relevant values for each part of the storage + /// key that is required in order to point to a single value. + /// + /// If no value is found, the default value will be returned for this entry if one exists. If no value is + /// found and no default value exists, an error will be returned. + pub async fn fetch( + &self, + key_parts: Addr::KeyParts, + ) -> Result, StorageError> { + self.internal_fetch(key_parts).await + } + + /// Fetch a storage value within this storage entry. + /// + /// This entry may be a map, and so you must provide the relevant values for each part of the storage + /// key that is required in order to point to a single value. + /// + /// If no value is found, `None` will be returned. + pub async fn try_fetch( + &self, + key_parts: Addr::KeyParts, + ) -> Result>, StorageError> { + self.internal_try_fetch(key_parts).await + } + + /// Iterate over storage values within this storage entry. + /// + /// You may provide any prefix of the values needed to point to a single value. Normally you will + /// provide `()` to iterate over _everything_, or `(first_key,)` to iterate over everything underneath + /// `first_key` in the map, or `(first_key, second_key)` to iterate over everything underneath `first_key` + /// and `second_key` in the map, and so on, up to the actual depth of the map - 1. + pub async fn iter>( + &self, + key_parts: KeyParts, + ) -> Result< + impl futures::Stream, StorageError>> + + use<'atblock, Addr, Client, T, KeyParts>, + StorageError, + > { + self.internal_iter(key_parts).await + } + + /// This returns a full key to a single value in this storage entry. + pub fn key(&self, key_parts: Addr::KeyParts) -> Result, StorageError> { + let num_keys = self.inner.info.keys.len(); + if key_parts.num_encodable_values() != num_keys { + Err(StorageError::WrongNumberOfKeyPartsProvidedForFetching { + expected: num_keys, + got: key_parts.num_encodable_values(), + }) + } else { + self.internal_key_bytes(key_parts) + } + } + + /// This returns valid keys to iterate over the storage entry at the available levels. + pub fn iter_key>( + &self, + key_parts: KeyParts, + ) -> Result, StorageError> { + let num_keys = self.inner.info.keys.len(); + if Addr::IsPlain::is_yes() { + Err(StorageError::CannotIterPlainEntry { + pallet_name: self.pallet_name().into(), + entry_name: self.entry_name().into(), + }) + } else if key_parts.num_encodable_values() >= num_keys { + Err(StorageError::WrongNumberOfKeyPartsProvidedForIterating { + max_expected: num_keys - 1, + got: key_parts.num_encodable_values(), + }) + } else { + self.internal_key_bytes(key_parts) + } + } + + /// The first 32 bytes of the storage entry key, which points to the entry but not necessarily + /// a single storage value (unless the entry is a plain value). + pub fn key_prefix(&self) -> [u8; 32] { + frame_decode::storage::encode_storage_key_prefix(self.pallet_name(), self.entry_name()) + } +} diff --git a/new/src/storage/storage_key.rs b/new/src/storage/storage_key.rs new file mode 100644 index 0000000000..871419d97d --- /dev/null +++ b/new/src/storage/storage_key.rs @@ -0,0 +1,138 @@ +// 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::StorageKeyError; +use core::marker::PhantomData; +use frame_decode::storage::{IntoDecodableValues, StorageInfo, StorageKey as StorageKeyPartInfo}; +use scale_info::PortableRegistry; +use std::sync::Arc; + +pub use frame_decode::storage::StorageHasher; + +/// This represents the different parts of a storage key. +pub struct StorageKey<'info, KeyParts> { + info: Arc>, + types: &'info PortableRegistry, + bytes: Arc<[u8]>, + marker: PhantomData, +} + +impl<'info, KeyParts: IntoDecodableValues> StorageKey<'info, KeyParts> { + pub(crate) fn new( + info: &StorageInfo<'info, u32>, + types: &'info PortableRegistry, + bytes: Arc<[u8]>, + ) -> Result { + 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 !cursor.is_empty() { + return Err(StorageKeyError::LeftoverBytes { + bytes: cursor.to_vec(), + }); + } + + Ok(StorageKey { + info: Arc::new(storage_key_info), + types, + bytes, + marker: PhantomData, + }) + } + + /// 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 { + let values = + frame_decode::storage::decode_storage_key_values(&self.bytes, &self.info, self.types) + .map_err(StorageKeyError::CannotDecodeValuesInKey)?; + + 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> { + 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> { + if index < self.parts().len() { + Some(StorageKeyPart { + index, + info: self.info.clone(), + types: self.types, + bytes: self.bytes.clone(), + }) + } else { + None + } + } +} + +/// This represents a part of a storage key. +pub struct StorageKeyPart<'info> { + index: usize, + info: Arc>, + types: &'info PortableRegistry, + bytes: Arc<[u8]>, +} + +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, + }; + &self.bytes[combined_range] + } + + /// Get the hasher that was used to construct this part of the storage key. + pub fn hasher(&self) -> StorageHasher { + self.info[self.index].hasher() + } + + /// 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(&self) -> Result, StorageKeyError> { + let part_info = &self.info[self.index]; + let Some(value_info) = part_info.value() else { + return Ok(None); + }; + + 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)) + } +} diff --git a/new/src/storage/storage_key_value.rs b/new/src/storage/storage_key_value.rs new file mode 100644 index 0000000000..9060bb06b8 --- /dev/null +++ b/new/src/storage/storage_key_value.rs @@ -0,0 +1,50 @@ +// 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::Address; +use super::storage_key::StorageKey; +use super::storage_value::StorageValue; +use crate::error::StorageKeyError; +use frame_decode::storage::StorageInfo; +use scale_info::PortableRegistry; +use std::sync::Arc; + +/// 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>, + types: &'info PortableRegistry, + key_bytes: Arc<[u8]>, + value_bytes: Vec, + ) -> 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, 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 + } +} diff --git a/new/src/storage/storage_value.rs b/new/src/storage/storage_value.rs new file mode 100644 index 0000000000..1b4800bd8f --- /dev/null +++ b/new/src/storage/storage_value.rs @@ -0,0 +1,70 @@ +// 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 core::marker::PhantomData; +use frame_decode::storage::StorageInfo; +use scale_decode::DecodeAsType; +use scale_info::PortableRegistry; +use std::sync::Arc; + +/// This represents a storage value. +#[derive(Debug)] +pub struct StorageValue<'info, Value> { + pub(crate) info: Arc>, + pub(crate) types: &'info PortableRegistry, + bytes: Vec, + marker: PhantomData, +} + +impl<'info, Value: DecodeAsType> StorageValue<'info, Value> { + pub(crate) fn new( + info: Arc>, + types: &'info PortableRegistry, + bytes: Vec, + ) -> 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 { + self.bytes.to_vec() + } + + /// Decode this storage value into the provided response type. + pub fn decode(&self) -> Result { + self.decode_as::() + } + + /// Decode this storage value into an arbitrary type. + pub fn decode_as(&self) -> Result { + 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) + } +} diff --git a/new/src/transactions.rs b/new/src/transactions.rs index 59e36710b0..293fee9a2d 100644 --- a/new/src/transactions.rs +++ b/new/src/transactions.rs @@ -29,21 +29,21 @@ pub use validation_result::{ /// A client for working with transactions. #[derive(Clone)] -pub struct Transactions { +pub struct TransactionsClient { client: Client, marker: PhantomData, } -impl Transactions { +impl TransactionsClient { pub(crate) fn new(client: Client) -> Self { - Transactions { + TransactionsClient { client, marker: PhantomData, } } } -impl> Transactions { +impl> TransactionsClient { /// Run the validation logic against some transaction you'd like to submit. Returns `Ok(())` /// 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 @@ -294,7 +294,7 @@ impl> Transactions { } } -impl> Transactions { +impl> TransactionsClient { /// Get the account nonce for a given account ID. pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result { account_nonce::get_account_nonce(&self.client, account_id)