diff --git a/core/src/storage/address.rs b/core/src/storage/address.rs index ae4608300f..ab05731b65 100644 --- a/core/src/storage/address.rs +++ b/core/src/storage/address.rs @@ -7,7 +7,7 @@ use alloc::borrow::Cow; use frame_decode::storage::IntoEncodableValues; use scale_decode::DecodeAsType; -use crate::utils::{Maybe, YesNoMaybe}; +use crate::utils::{Maybe, YesMaybe}; /// 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 @@ -19,14 +19,10 @@ pub trait Address { 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: YesNoMaybe; - /// Does the address point to a map (as opposed to a plain value)? + /// 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 IsMap; + type IsPlain: YesMaybe; /// The pallet containing this storage entry. fn pallet_name(&self) -> &str; @@ -39,14 +35,14 @@ pub trait Address { } /// An address which is generated by the static APIs. -pub struct StaticAddress { +pub struct StaticAddress { pallet_name: Cow<'static, str>, entry_name: Cow<'static, str>, validation_hash: Option<[u8; 32]>, - marker: core::marker::PhantomData<(KeyParts, Value, HasDefaultValue, IsMap)>, + marker: core::marker::PhantomData<(KeyParts, Value, IsPlain)>, } -impl StaticAddress { +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)] @@ -83,17 +79,16 @@ impl StaticAddress Address - for StaticAddress +impl Address + for StaticAddress where KeyParts: IntoEncodableValues, Value: DecodeAsType, - HasDefaultValue: YesNoMaybe, + IsPlain: YesMaybe, { type KeyParts = KeyParts; type Value = Value; - type HasDefaultValue = HasDefaultValue; - type IsMap = IsMap; + type IsPlain = IsPlain; fn pallet_name(&self) -> &str { &self.pallet_name @@ -110,7 +105,7 @@ where /// 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; +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 diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index 50a7ba0ca8..50da64197a 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -22,7 +22,7 @@ use alloc::vec::Vec; use codec::{Compact, Decode, Encode}; use derive_where::derive_where; -pub use yesnomaybe::{Yes, No, Maybe, YesNoMaybe}; +pub use yesnomaybe::{Yes, Maybe, YesMaybe}; pub use account_id::AccountId32; pub use account_id20::AccountId20; pub use era::Era; diff --git a/core/src/utils/yesnomaybe.rs b/core/src/utils/yesnomaybe.rs index 52805bbe51..af8796d8d2 100644 --- a/core/src/utils/yesnomaybe.rs +++ b/core/src/utils/yesnomaybe.rs @@ -6,26 +6,19 @@ pub enum Yes {} /// A unit marker enum. pub enum Maybe {} -/// A unit marker enum. -pub enum No {} -/// This is implemented for [`Yes`], [`No`] and [`Maybe`] and +/// This is implemented for [`Yes`] and [`Maybe`] and /// allows us to check at runtime which of these types is present. -pub trait YesNoMaybe { +pub trait YesMaybe { /// [`Yes`] fn is_yes() -> bool { false } - /// [`No`] - fn is_no() -> bool { false } /// [`Maybe`] fn is_maybe() -> bool { false } } -impl YesNoMaybe for Yes { +impl YesMaybe for Yes { fn is_yes() -> bool { true } } -impl YesNoMaybe for No { - fn is_no() -> bool { true } -} -impl YesNoMaybe for Maybe { +impl YesMaybe for Maybe { fn is_maybe() -> bool { true } } \ No newline at end of file diff --git a/historic/examples/storage.rs b/historic/examples/storage.rs index cd23041412..c6a2c5c8be 100644 --- a/historic/examples/storage.rs +++ b/historic/examples/storage.rs @@ -21,8 +21,7 @@ async fn main() -> Result<(), Error> { // We'll work the account balances at the given block, for this example. let account_balances = client_at_block .storage() - .entry("System", "Account")? - .into_map()?; + .entry("System", "Account")?; // We can see the default value for this entry at this block, if one exists. if let Some(default_value) = account_balances.default_value() { diff --git a/historic/src/error.rs b/historic/src/error.rs index be7352203e..b26ed7882d 100644 --- a/historic/src/error.rs +++ b/historic/src/error.rs @@ -21,10 +21,6 @@ pub enum Error { #[error(transparent)] StorageError(#[from] StorageError), #[error(transparent)] - StorageEntryIsNotAMap(#[from] StorageEntryIsNotAMap), - #[error(transparent)] - StorageEntryIsNotAPlainValue(#[from] StorageEntryIsNotAPlainValue), - #[error(transparent)] StorageKeyError(#[from] StorageKeyError), #[error(transparent)] StorageValueError(#[from] StorageValueError), @@ -252,14 +248,23 @@ pub enum StorageError { reason: frame_decode::storage::StorageKeyEncodeError, }, #[error( - "Too many keys provided: expected {num_keys_expected} keys, but got {num_keys_provided}" + "Wrong number of keys provided to fetch a value: expected {num_keys_expected} keys, but got {num_keys_provided}" )] - WrongNumberOfKeysProvided { + WrongNumberOfKeysProvidedForFetch { /// The number of keys that were provided. num_keys_provided: usize, /// The number of keys expected. num_keys_expected: usize, }, + #[error( + "too many keys were provided to iterate over a storage entry: expected at most {max_keys_expected} keys, but got {num_keys_provided}" + )] + TooManyKeysProvidedForIter { + /// The number of keys that were provided. + num_keys_provided: usize, + /// The maximum number of keys that we expect. + max_keys_expected: usize + }, #[error( "Could not extract storage information from metadata: Unsupported metadata version ({version})" )] diff --git a/historic/src/storage.rs b/historic/src/storage.rs index 95b89e0c57..f3a287fd55 100644 --- a/historic/src/storage.rs +++ b/historic/src/storage.rs @@ -5,7 +5,7 @@ mod storage_value; use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT}; use crate::config::Config; -use crate::error::{StorageEntryIsNotAMap, StorageEntryIsNotAPlainValue, StorageError}; +use crate::error::StorageError; use crate::storage::storage_info::with_info; use std::borrow::Cow; use storage_info::AnyStorageInfo; @@ -54,23 +54,13 @@ where self.client.legacy_types(), )?; - if storage_info.is_map() { - Ok(StorageEntryClient::Map(StorageEntryMapClient { - client: self.client, - pallet_name, - entry_name, - info: storage_info, - marker: std::marker::PhantomData, - })) - } else { - Ok(StorageEntryClient::Plain(StorageEntryPlainClient { - client: self.client, - pallet_name, - entry_name, - info: storage_info, - marker: std::marker::PhantomData, - })) - } + Ok(StorageEntryClient { + client: self.client, + pallet_name, + entry_name, + info: storage_info, + marker: std::marker::PhantomData, + }) } /// Iterate over all of the storage entries listed in the metadata for the current block. This does **not** include well known @@ -122,90 +112,16 @@ where } } -/// A client for working with a specific storage entry. This is an enum because the storage entry -/// might be either a map or a plain value, and each has a different interface. -pub enum StorageEntryClient<'atblock, Client, T> { - Plain(StorageEntryPlainClient<'atblock, Client, T>), - Map(StorageEntryMapClient<'atblock, Client, T>), +/// A client for working with a specific storage entry. +pub struct StorageEntryClient<'atblock, Client, T> { + client: &'atblock Client, + pallet_name: String, + entry_name: String, + info: AnyStorageInfo<'atblock>, + marker: std::marker::PhantomData, } impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T> -where - T: Config + 'atblock, - Client: OfflineClientAtBlockT<'atblock, T>, -{ - /// Get the pallet name. - pub fn pallet_name(&self) -> &str { - match self { - StorageEntryClient::Plain(client) => &client.pallet_name, - StorageEntryClient::Map(client) => &client.pallet_name, - } - } - - /// Get the storage entry name. - pub fn entry_name(&self) -> &str { - match self { - StorageEntryClient::Plain(client) => &client.entry_name, - StorageEntryClient::Map(client) => &client.entry_name, - } - } - - /// Is the storage entry a plain value? - pub fn is_plain(&self) -> bool { - matches!(self, StorageEntryClient::Plain(_)) - } - - /// Is the storage entry a map? - pub fn is_map(&self) -> bool { - matches!(self, StorageEntryClient::Map(_)) - } - - /// If this storage entry is a plain value, return the client for working with it. Else return an error. - pub fn into_plain( - self, - ) -> Result, StorageEntryIsNotAPlainValue> { - match self { - StorageEntryClient::Plain(client) => Ok(client), - StorageEntryClient::Map(_) => Err(StorageEntryIsNotAPlainValue { - pallet_name: self.pallet_name().into(), - entry_name: self.entry_name().into(), - }), - } - } - - /// If this storage entry is a map, return the client for working with it. Else return an error. - pub fn into_map( - self, - ) -> Result, StorageEntryIsNotAMap> { - match self { - StorageEntryClient::Plain(_) => Err(StorageEntryIsNotAMap { - pallet_name: self.pallet_name().into(), - entry_name: self.entry_name().into(), - }), - StorageEntryClient::Map(client) => Ok(client), - } - } - - /// 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> { - match self { - StorageEntryClient::Plain(client) => client.default_value(), - StorageEntryClient::Map(client) => client.default_value(), - } - } -} - -/// A client for working with a plain storage entry. -pub struct StorageEntryPlainClient<'atblock, Client, T> { - client: &'atblock Client, - pallet_name: String, - entry_name: String, - info: AnyStorageInfo<'atblock>, - marker: std::marker::PhantomData, -} - -impl<'atblock, Client, T> StorageEntryPlainClient<'atblock, Client, T> where T: Config + 'atblock, Client: OfflineClientAtBlockT<'atblock, T>, @@ -220,66 +136,13 @@ where &self.entry_name } - /// 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> { - with_info!(info = &self.info => { - info.info.default_value.as_ref().map(|default_value| { - StorageValue::new(&self.info, default_value.clone()) - }) - }) - } -} - -impl<'atblock, Client, T> StorageEntryPlainClient<'atblock, Client, T> -where - T: Config + 'atblock, - Client: OnlineClientAtBlockT<'atblock, T>, -{ - /// Fetch the value for this storage entry. If no value exists and no default value is - /// set for this storage entry, then `None` will be returned. - pub async fn fetch(&self) -> Result>, StorageError> { - let key_bytes = self.key(); - let value = fetch(self.client, &key_bytes) - .await? - .map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))) - .or_else(|| self.default_value()); - - Ok(value) - } - - /// The key for this storage entry. - pub fn key(&self) -> [u8; 32] { + /// The key which points to this storage entry (but not necessarily any values within it). + pub fn key_prefix(&self) -> [u8; 32] { let pallet_name = &*self.pallet_name; let entry_name = &*self.entry_name; frame_decode::storage::encode_storage_key_prefix(pallet_name, entry_name) } -} - -/// A client for working with a storage entry that is a map. -pub struct StorageEntryMapClient<'atblock, Client, T> { - client: &'atblock Client, - pallet_name: String, - entry_name: String, - info: AnyStorageInfo<'atblock>, - marker: std::marker::PhantomData, -} - -impl<'atblock, Client, T> StorageEntryMapClient<'atblock, Client, T> -where - T: Config + 'atblock, - Client: OfflineClientAtBlockT<'atblock, T>, -{ - /// Get the pallet name. - pub fn pallet_name(&self) -> &str { - &self.pallet_name - } - - /// Get the storage entry name. - pub fn entry_name(&self) -> &str { - &self.entry_name - } /// Return the default value for this storage entry, if there is one. Returns `None` if there /// is no default value. @@ -292,7 +155,7 @@ where } } -impl<'atblock, Client, T> StorageEntryMapClient<'atblock, Client, T> +impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T> where T: Config + 'atblock, Client: OnlineClientAtBlockT<'atblock, T>, @@ -309,8 +172,9 @@ where info.info.keys.len() }); + // For fetching, we need exactly as many keys as exist for a storage entry. if expected_num_keys != keys.num_encodable_values() { - return Err(StorageError::WrongNumberOfKeysProvided { + return Err(StorageError::WrongNumberOfKeysProvidedForFetch { num_keys_provided: keys.num_encodable_values(), num_keys_expected: expected_num_keys, }); @@ -338,6 +202,19 @@ where ArchiveStorageEvent, StorageQuery, StorageQueryType, }; + let expected_num_keys = with_info!(info = &self.info => { + info.info.keys.len() + }); + + // For iterating, we need at most one less key than the number that exists for a storage entry. + // TODO: The error message will be confusing if == keys are provided! + if keys.num_encodable_values() >= expected_num_keys { + return Err(StorageError::TooManyKeysProvidedForIter { + num_keys_provided: keys.num_encodable_values(), + max_keys_expected: expected_num_keys - 1 + }); + } + let block_hash = self.client.block_hash(); let key_bytes = self.key(keys)?; diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index 5d1c3cfd7f..331c0ffb71 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -557,11 +557,7 @@ async fn wait_runtime_upgrade_in_finalized_block( ))); }; - let client = client - .into_plain() - .expect("System.LastRuntimeUpgrade should always be a plain storage entry"); - - let value = match client.try_fetch().await { + let value = match client.try_fetch(()).await { Ok(Some(value)) => value, Ok(None) => return Some(Err(Error::Other( "The storage item `system::lastRuntimeUpgrade` should always exist (2)".to_string(), diff --git a/subxt/src/storage/storage_client_at.rs b/subxt/src/storage/storage_client_at.rs index d2292ba760..110820f632 100644 --- a/subxt/src/storage/storage_client_at.rs +++ b/subxt/src/storage/storage_client_at.rs @@ -15,8 +15,8 @@ use derive_where::derive_where; use futures::StreamExt; use scale_info::PortableRegistry; use std::{future::Future, marker::PhantomData}; -use subxt_core::storage::{address::Address, PrefixOf}; -use subxt_core::utils::{Maybe, Yes, No, YesNoMaybe}; +use subxt_core::storage::{address::Address, PrefixOf, EqualOrPrefixOf}; +use subxt_core::utils::{Maybe, Yes, YesMaybe}; use subxt_core::Metadata; use frame_decode::storage::{IntoEncodableValues, StorageInfo}; use std::borrow::Cow; @@ -61,7 +61,7 @@ where T: Config, Client: OfflineClientT, { - pub fn entry(&'_ self, address: Addr) -> Result, Error> { + pub fn entry(&'_ self, address: Addr) -> Result, Error> { subxt_core::storage::validate(&address, &self.client.metadata())?; use frame_decode::storage::StorageTypeInfo; @@ -71,174 +71,51 @@ where .metadata() .storage_info(address.pallet_name(), address.entry_name())?; - let value = if info.keys.is_empty() { - StorageEntryClientValue::Plain(StorageEntryPlainClient { - client: self.client.clone(), - block_ref: self.block_ref.clone(), - address, - info, - types, - _marker: core::marker::PhantomData - }) - } else { - StorageEntryClientValue::Map(StorageEntryMapClient { - client: self.client.clone(), - block_ref: self.block_ref.clone(), - address, - info, - types, - _marker: core::marker::PhantomData - }) - }; - Ok(StorageEntryClient { - value, - marker: core::marker::PhantomData + client: self.client.clone(), + block_ref: self.block_ref.clone(), + address, + info, + types, + _marker: core::marker::PhantomData }) } } /// This represents a single storage entry (be it a plain value or map) /// and the operations that can be performed on it. -pub struct StorageEntryClient<'atblock, T: Config, Client, Addr: Address, IsMap> { - value: StorageEntryClientValue<'atblock, T, Client, Addr>, - marker: core::marker::PhantomData, -} - -enum StorageEntryClientValue<'atblock, T: Config, Client, Addr: Address> { - Plain(StorageEntryPlainClient<'atblock, T, Client, Addr, Addr::HasDefaultValue>), - Map(StorageEntryMapClient<'atblock, T, Client, Addr, Addr::HasDefaultValue>), -} - -impl <'atblock, T: Config, Client, Addr: Address, IsMap> StorageEntryClient<'atblock, T, Client, Addr, IsMap> { - pub fn pallet_name(&self) -> &str { - match &self.value { - StorageEntryClientValue::Plain(client) => client.pallet_name(), - StorageEntryClientValue::Map(client) => client.pallet_name(), - } - } - - pub fn entry_name(&self) -> &str { - match &self.value { - StorageEntryClientValue::Plain(client) => client.entry_name(), - StorageEntryClientValue::Map(client) => client.entry_name(), - } - } - - /// Is the storage entry a plain value? - pub fn is_plain(&self) -> bool { - matches!(self.value, StorageEntryClientValue::Plain(_)) - } - - /// Is the storage entry a map? - pub fn is_map(&self) -> bool { - matches!(self.value, StorageEntryClientValue::Map(_)) - } - - /// 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> { - match &self.value { - StorageEntryClientValue::Plain(client) => client.default_value(), - StorageEntryClientValue::Map(client) => client.default_value(), - } - } -} - -// 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 <'atblock, T: Config, Client, Addr: Address> StorageEntryClient<'atblock, T, Client, Addr, Yes> { - pub fn into_map(self) -> StorageEntryMapClient<'atblock, T, Client, Addr, Addr::HasDefaultValue> { - match self.value { - StorageEntryClientValue::Map(this) => this, - StorageEntryClientValue::Plain(this) => { - tracing::warn!("StorageEntryClient::into_map called on a plain storage value"); - StorageEntryMapClient { - client: this.client, - block_ref: this.block_ref, - address: this.address, - info: this.info, - types: this.types, - _marker: this._marker - } - } - } - } -} - -// 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 <'atblock, T: Config, Client, Addr: Address> StorageEntryClient<'atblock, T, Client, Addr, No> { - pub fn into_plain(self) -> StorageEntryPlainClient<'atblock, T, Client, Addr, Addr::HasDefaultValue> { - match self.value { - StorageEntryClientValue::Plain(this) => this, - StorageEntryClientValue::Map(this) => { - tracing::warn!("StorageEntryClient::into_plain called on a map storage value"); - StorageEntryPlainClient { - client: this.client, - block_ref: this.block_ref, - address: this.address, - info: this.info, - types: this.types, - _marker: this._marker - } - } - } - } -} - -// Regardless, we can do the "safe" thing and try to convert the entry into a map or plain entry. -impl <'atblock, T: Config, Client, Addr: Address> StorageEntryClient<'atblock, T, Client, Addr, Maybe> { - // TODO: In subxt-historic we return Result, not Option, with "StorageEntryIsNotAMapValue" like err. - pub fn into_map(self) -> Option> { - match self.value { - StorageEntryClientValue::Map(client) => Some(client), - StorageEntryClientValue::Plain(_) => None, - } - } - - pub fn into_plain(self) -> Option> { - // TODO: In subxt-historic we return Result, not Option, with "StorageEntryIsNotAPlainValue" like err. - match self.value { - StorageEntryClientValue::Plain(client) => Some(client), - StorageEntryClientValue::Map(_) => None, - } - } -} - -/// This represents a plain storage value at some location. -pub struct StorageEntryPlainClient<'atblock, T: Config, Client, Addr, HasDefaultValue> { +pub struct StorageEntryClient<'atblock, T: Config, Client, Addr, IsPlain> { client: Client, block_ref: BlockRef>, address: Addr, info: StorageInfo<'atblock, u32>, types: &'atblock PortableRegistry, - _marker: PhantomData<(T, HasDefaultValue)>, + _marker: PhantomData<(T, IsPlain)>, } -impl <'atblock, T, Client, Addr, HasDefaultValue> StorageEntryPlainClient<'atblock, T, Client, Addr, HasDefaultValue> +impl <'atblock, T, Client, Addr, IsPlain> StorageEntryClient<'atblock, T, Client, Addr, IsPlain> where T: Config, Addr: Address, { - /// Get the pallet name. + /// Name of the pallet containing this storage entry. pub fn pallet_name(&self) -> &str { self.address.pallet_name() } - /// Get the storage entry name. + /// Name of the storage entry. pub fn entry_name(&self) -> &str { self.address.entry_name() } - /// Return the key used to retrieve this storage value. - pub fn key(&self) -> [u8; 32] { - frame_decode::storage::encode_storage_key_prefix( - self.address.pallet_name(), - self.address.entry_name() - ) + /// Is the storage entry a plain value? + pub fn is_plain(&self) -> bool { + self.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 @@ -252,9 +129,8 @@ where } } -// 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> StorageEntryPlainClient<'atblock, T, Client, Addr, Yes> +// Plain values get a fetch method with no extra arguments. +impl <'atblock, T, Client, Addr> StorageEntryClient<'atblock, T, Client, Addr, Yes> where T: Config, Addr: Address, @@ -275,64 +151,29 @@ where Ok(value) } -} -impl <'atblock, T, Client, Addr, HasDefaultValue> StorageEntryPlainClient<'atblock, T, Client, Addr, HasDefaultValue> -where - T: Config, - Addr: Address, - Client: OnlineClientT -{ pub async fn try_fetch(&self) -> Result>, Error> { let value = self.client .backend() - .storage_fetch_value(self.key().to_vec(), self.block_ref.hash()) + .storage_fetch_value(self.key_prefix().to_vec(), self.block_ref.hash()) .await? .map(|bytes| StorageValue::new(&self.info, self.types, bytes)); Ok(value) } -} -/// This represents a map of storage values at some location. -pub struct StorageEntryMapClient<'atblock, T: Config, Client, Addr, HasDefaultValue> { - client: Client, - block_ref: BlockRef>, - address: Addr, - info: StorageInfo<'atblock, u32>, - types: &'atblock PortableRegistry, - _marker: PhantomData<(T, HasDefaultValue)>, -} - -impl <'atblock, T, Client, Addr, HasDefaultValue> StorageEntryMapClient<'atblock, T, Client, Addr, HasDefaultValue> -where - T: Config, - Addr: Address, -{ - /// Get the pallet name. - pub fn pallet_name(&self) -> &str { - self.address.pallet_name() - } - - /// Get the storage entry name. - pub fn entry_name(&self) -> &str { - self.address.entry_name() - } - - /// 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> { - if let Some(default_bytes) = self.info.default_value.as_deref() { - Some(StorageValue::new(&self.info, self.types, Cow::Borrowed(default_bytes))) - } else { - None - } + /// 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.address.pallet_name(), + self.address.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> StorageEntryMapClient<'atblock, T, Client, Addr, Yes> +impl <'atblock, T, Client, Addr> StorageEntryClient<'atblock, T, Client, Addr, Maybe> where T: Config, Addr: Address, @@ -352,14 +193,7 @@ where Ok(value) } -} - -impl <'atblock, T, Client, Addr, HasDefaultValue> StorageEntryMapClient<'atblock, T, Client, Addr, HasDefaultValue> -where - T: Config, - Addr: Address, - Client: OnlineClientT -{ + pub async fn try_fetch(&self, keys: Addr::KeyParts) -> Result>, Error> { if keys.num_encodable_values() != self.info.keys.len() { // This shouldn't be possible in static cases but if Vec is keys then we need to be checking. @@ -384,14 +218,17 @@ where Ok(value) } - pub async fn iter>(&self, keys: Keys) -> Result, Error>>, Error> { - if keys.num_encodable_values() != self.info.keys.len() { + pub async fn iter>( + &self, + keys: Keys + ) -> Result, Error>>, Error> { + if keys.num_encodable_values() >= self.info.keys.len() { // This shouldn't be possible in static cases but if Vec is keys then we need to be checking. todo!("Error: wrong number of keys provided.") } let block_hash = self.block_ref.hash(); - let key_bytes = self.key(keys)?; + let key_bytes = self.key_from(keys)?; let info = &self.info; let types = self.types; @@ -408,10 +245,28 @@ where Ok(StorageEntry::new(info, types, kv.key, Cow::Owned(kv.value))) }); - Ok(StreamOf::new(Box::pin(stream))) + Ok(Box::pin(stream)) } - fn key>(&self, keys: Keys) -> Result, Error> { + /// Keys for map values require additional values; you can provide [`Address::KeyParts`] + /// values to construct a complete key, or a prefix of these to construct a key that would + /// iterate over values. + pub fn key>(&self, keys: Keys) -> Result, Error> { + self.key_from(keys) + } + + /// 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.address.pallet_name(), + self.address.entry_name() + ) + } + + // An internal function to generate keys, because owing to lack of specialisation, things that impl + // `EqualOrPrefixOf` don't also provably impl `PrefixOf`. + fn key_from(&self, keys: Keys) -> Result, Error> { let key_bytes = frame_decode::storage::encode_storage_key_with_info( &self.address.pallet_name(), &self.address.entry_name(),