diff --git a/codegen/src/api/storage.rs b/codegen/src/api/storage.rs index 1426c6c999..aa900fc99c 100644 --- a/codegen/src/api/storage.rs +++ b/codegen/src/api/storage.rs @@ -111,11 +111,11 @@ fn generate_storage_entry_fns( .collect(); let pallet_name = pallet.name(); - let storage_name = storage_entry.name(); - let Some(storage_hash) = pallet.storage_hash(storage_name) else { + let entry_name = storage_entry.name(); + let Some(storage_hash) = pallet.storage_hash(entry_name) else { return Err(CodegenError::MissingStorageMetadata( pallet_name.into(), - storage_name.into(), + entry_name.into(), )); }; @@ -223,7 +223,7 @@ fn generate_storage_entry_fns( > { #crate_path::storage::address::StaticAddress::new_static( #pallet_name, - #storage_name, + #entry_name, #keys, [#(#storage_hash,)*] ) diff --git a/core/src/dynamic.rs b/core/src/dynamic.rs index a314950110..8f02b2d04c 100644 --- a/core/src/dynamic.rs +++ b/core/src/dynamic.rs @@ -23,7 +23,7 @@ pub use crate::tx::payload::dynamic as tx; pub use crate::constants::address::dynamic as constant; // Lookup storage values dynamically. -// pub use crate::storage::address::dynamic as storage; // TODO re-add. +pub use crate::storage::address::dynamic as storage; // Execute runtime API function call dynamically. pub use crate::runtime_api::payload::dynamic as runtime_api_call; diff --git a/core/src/storage/address.rs b/core/src/storage/address.rs index 3115a82c22..ae4608300f 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; +use crate::utils::{Maybe, YesNoMaybe}; /// 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 @@ -22,7 +22,7 @@ pub trait Address { /// 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; + type HasDefaultValue: YesNoMaybe; /// 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. @@ -63,6 +63,19 @@ impl StaticAddress>, + entry_name: impl Into>, + ) -> Self { + Self { + pallet_name: pallet_name.into(), + entry_name: entry_name.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; @@ -75,6 +88,7 @@ impl Address where KeyParts: IntoEncodableValues, Value: DecodeAsType, + HasDefaultValue: YesNoMaybe, { type KeyParts = KeyParts; type Value = Value; @@ -95,8 +109,8 @@ where } /// 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; +/// 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 @@ -105,11 +119,6 @@ pub fn dynamic( pallet_name: impl Into>, entry_name: impl Into>, ) -> DynamicAddress { - DynamicAddress:: { - pallet_name: pallet_name.into(), - entry_name: entry_name.into(), - validation_hash: None, - marker: core::marker::PhantomData - } + DynamicAddress::::new(pallet_name, entry_name) } diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index 741aa34841..50a7ba0ca8 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -13,6 +13,7 @@ mod multi_signature; mod static_type; mod unchecked_extrinsic; mod wrapper_opaque; +mod yesnomaybe; use alloc::borrow::ToOwned; use alloc::format; @@ -21,6 +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 account_id::AccountId32; pub use account_id20::AccountId20; pub use era::Era; @@ -73,13 +75,6 @@ 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 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 { format!("0x{}", hex::encode(bytes.as_ref())) diff --git a/core/src/utils/yesnomaybe.rs b/core/src/utils/yesnomaybe.rs new file mode 100644 index 0000000000..52805bbe51 --- /dev/null +++ b/core/src/utils/yesnomaybe.rs @@ -0,0 +1,31 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/// A unit marker enum. +pub enum Yes {} +/// A unit marker enum. +pub enum Maybe {} +/// A unit marker enum. +pub enum No {} + +/// This is implemented for [`Yes`], [`No`] and [`Maybe`] and +/// allows us to check at runtime which of these types is present. +pub trait YesNoMaybe { + /// [`Yes`] + fn is_yes() -> bool { false } + /// [`No`] + fn is_no() -> bool { false } + /// [`Maybe`] + fn is_maybe() -> bool { false } +} + +impl YesNoMaybe for Yes { + fn is_yes() -> bool { true } +} +impl YesNoMaybe for No { + fn is_no() -> bool { true } +} +impl YesNoMaybe for Maybe { + fn is_maybe() -> bool { true } +} \ No newline at end of file diff --git a/historic/examples/extrinsics.rs b/historic/examples/extrinsics.rs index 019b33c650..82ba674b89 100644 --- a/historic/examples/extrinsics.rs +++ b/historic/examples/extrinsics.rs @@ -43,7 +43,7 @@ async fn main() -> Result<(), Error> { println!( " {}: {}", field.name(), - field.decode::().unwrap() + field.decode_as::().unwrap() ); } @@ -53,7 +53,7 @@ async fn main() -> Result<(), Error> { extrinsic .call() .fields() - .decode::>() + .decode_as::>() .unwrap() ); @@ -66,14 +66,14 @@ async fn main() -> Result<(), Error> { println!( " {}: {}", extension.name(), - extension.decode::().unwrap() + extension.decode_as::().unwrap() ); } // Or all of them at once: println!( " All: {}", - extensions.decode::>().unwrap() + extensions.decode_as::>().unwrap() ); } } diff --git a/historic/examples/storage.rs b/historic/examples/storage.rs index 2798b73c89..cd23041412 100644 --- a/historic/examples/storage.rs +++ b/historic/examples/storage.rs @@ -25,8 +25,8 @@ async fn main() -> Result<(), Error> { .into_map()?; // We can see the default value for this entry at this block, if one exists. - if let Some(default_value) = account_balances.default() { - let default_balance_info = default_value.decode::()?; + if let Some(default_value) = account_balances.default_value() { + let default_balance_info = default_value.decode_as::()?; println!(" Default balance info: {default_balance_info}"); } @@ -37,7 +37,7 @@ async fn main() -> Result<(), Error> { if let Some(entry) = account_balances.fetch((account_id,)).await? { // We can decode the value into our generic `scale_value::Value` type, which can // represent any SCALE-encoded value, like so: - let _balance_info = entry.decode::()?; + let _balance_info = entry.decode_as::()?; // Or, if we know what shape to expect, we can decode the parts of the value that we care // about directly into a static type, which is more efficient and allows easy type-safe @@ -53,7 +53,7 @@ async fn main() -> Result<(), Error> { misc_frozen: u128, fee_frozen: u128, } - let balance_info = entry.decode::()?; + let balance_info = entry.decode_as::()?; println!( " Single balance info from {account_id_hex} => free: {} reserved: {} misc_frozen: {} fee_frozen: {}", @@ -72,21 +72,21 @@ async fn main() -> Result<(), Error> { let mut all_balances = account_balances.iter(()).await?.take(10); while let Some(entry) = all_balances.next().await { let entry = entry?; - let key = entry.decode_key()?; + let key = entry.key()?; // Decode the account ID from the key (we know here that we're working // with a map which has one value, an account ID, so we just decode that part: let account_id = key .part(0) .unwrap() - .decode::<[u8; 32]>()? + .decode_as::<[u8; 32]>()? .expect("We expect this key to decode into a 32 byte AccountId"); let account_id_hex = hex::encode(account_id); // Decode these values into our generic scale_value::Value type. Less efficient than // defining a static type as above, but easier for the sake of the example. - let balance_info = entry.decode_value::()?; + let balance_info = entry.value().decode_as::()?; println!(" {account_id_hex} => {balance_info}"); } } diff --git a/historic/src/error.rs b/historic/src/error.rs index 4c5dbd1e5a..be7352203e 100644 --- a/historic/src/error.rs +++ b/historic/src/error.rs @@ -214,22 +214,22 @@ pub enum ExtrinsicCallError { #[allow(missing_docs)] #[derive(Debug, thiserror::Error)] -#[error("Storage entry is not a map: pallet {pallet_name}, storage {storage_name}")] +#[error("Storage entry is not a map: pallet {pallet_name}, storage {entry_name}")] pub struct StorageEntryIsNotAMap { /// The pallet containing the storage entry that was not found. pub pallet_name: String, /// The storage entry that was not found. - pub storage_name: String, + pub entry_name: String, } #[allow(missing_docs)] #[derive(Debug, thiserror::Error)] -#[error("Storage entry is not a plain value: pallet {pallet_name}, storage {storage_name}")] +#[error("Storage entry is not a plain value: pallet {pallet_name}, storage {entry_name}")] pub struct StorageEntryIsNotAPlainValue { /// The pallet containing the storage entry that was not found. pub pallet_name: String, /// The storage entry that was not found. - pub storage_name: String, + pub entry_name: String, } #[allow(missing_docs)] diff --git a/historic/src/extrinsics/extrinsic_call.rs b/historic/src/extrinsics/extrinsic_call.rs index 31ffce6151..f42339cd73 100644 --- a/historic/src/extrinsics/extrinsic_call.rs +++ b/historic/src/extrinsics/extrinsic_call.rs @@ -98,7 +98,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> { } /// Attempt to decode the fields into the given type. - pub fn decode(&self) -> Result { + pub fn decode_as(&self) -> Result { with_info!(&self.info => { let cursor = &mut self.bytes(); let mut fields = &mut info.info.call_data().map(|named_arg| { @@ -156,7 +156,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallField<'extrinsics, 'atblock> { } /// Attempt to decode the value of this field into the given type. - pub fn decode(&self) -> Result { + pub fn decode_as(&self) -> Result { with_call_field_info!(&self.info => { let cursor = &mut &*self.field_bytes; let decoded = T::decode_as_type(cursor, info.info.ty().clone(), info.resolver) diff --git a/historic/src/extrinsics/extrinsic_transaction_extensions.rs b/historic/src/extrinsics/extrinsic_transaction_extensions.rs index 28c8747548..9a0a8aa081 100644 --- a/historic/src/extrinsics/extrinsic_transaction_extensions.rs +++ b/historic/src/extrinsics/extrinsic_transaction_extensions.rs @@ -105,7 +105,7 @@ impl<'extrinsics, 'atblock> ExtrinsicTransactionExtensions<'extrinsics, 'atblock /// Attempt to decode the transaction extensions into a type where each field name is the name of the transaction /// extension and the field value is the decoded extension. - pub fn decode( + pub fn decode_as( &self, ) -> Result { with_extensions_info!(&self.info => { @@ -189,7 +189,7 @@ impl<'extrinsics, 'atblock> ExtrinsicTransactionExtension<'extrinsics, 'atblock> } /// Decode the bytes for this transaction extension into a type that implements `scale_decode::DecodeAsType`. - pub fn decode( + pub fn decode_as( &self, ) -> Result { with_extension_info!(&self.info => { diff --git a/historic/src/storage.rs b/historic/src/storage.rs index 144a6f38e7..95b89e0c57 100644 --- a/historic/src/storage.rs +++ b/historic/src/storage.rs @@ -42,14 +42,14 @@ where pub fn entry( &self, pallet_name: impl Into, - storage_name: impl Into, + entry_name: impl Into, ) -> Result, StorageError> { let pallet_name = pallet_name.into(); - let storage_name = storage_name.into(); + let entry_name = entry_name.into(); let storage_info = AnyStorageInfo::new( &pallet_name, - &storage_name, + &entry_name, self.client.metadata(), self.client.legacy_types(), )?; @@ -58,7 +58,7 @@ where Ok(StorageEntryClient::Map(StorageEntryMapClient { client: self.client, pallet_name, - storage_name, + entry_name, info: storage_info, marker: std::marker::PhantomData, })) @@ -66,7 +66,7 @@ where Ok(StorageEntryClient::Plain(StorageEntryPlainClient { client: self.client, pallet_name, - storage_name, + entry_name, info: storage_info, marker: std::marker::PhantomData, })) @@ -105,7 +105,7 @@ where } /// The storage entry name. - pub fn storage_name(&self) -> &str { + pub fn entry_name(&self) -> &str { &self.entry.storage_entry } @@ -143,10 +143,10 @@ where } /// Get the storage entry name. - pub fn storage_name(&self) -> &str { + pub fn entry_name(&self) -> &str { match self { - StorageEntryClient::Plain(client) => &client.storage_name, - StorageEntryClient::Map(client) => &client.storage_name, + StorageEntryClient::Plain(client) => &client.entry_name, + StorageEntryClient::Map(client) => &client.entry_name, } } @@ -168,7 +168,7 @@ where StorageEntryClient::Plain(client) => Ok(client), StorageEntryClient::Map(_) => Err(StorageEntryIsNotAPlainValue { pallet_name: self.pallet_name().into(), - storage_name: self.storage_name().into(), + entry_name: self.entry_name().into(), }), } } @@ -180,18 +180,27 @@ where match self { StorageEntryClient::Plain(_) => Err(StorageEntryIsNotAMap { pallet_name: self.pallet_name().into(), - storage_name: self.storage_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, - storage_name: String, + entry_name: String, info: AnyStorageInfo<'atblock>, marker: std::marker::PhantomData, } @@ -207,13 +216,13 @@ where } /// Get the storage entry name. - pub fn storage_name(&self) -> &str { - &self.storage_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. - pub fn default(&self) -> Option> { + 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()) @@ -227,30 +236,24 @@ where T: Config + 'atblock, Client: OnlineClientAtBlockT<'atblock, T>, { - /// Fetch the value for this storage entry. + /// 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(); - fetch(self.client, &key_bytes) - .await - .map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes)))) - } + let value = fetch(self.client, &key_bytes) + .await? + .map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))) + .or_else(|| self.default_value()); - /// Fetch the value for this storage entry as per [`StorageEntryPlainClient::fetch`], but return the default - /// value for the storage entry if one exists and the entry does not exist. - pub async fn fetch_or_default( - &self, - ) -> Result>, StorageError> { - self.fetch() - .await - .map(|option_val| option_val.or_else(|| self.default())) + Ok(value) } /// The key for this storage entry. pub fn key(&self) -> [u8; 32] { let pallet_name = &*self.pallet_name; - let storage_name = &*self.storage_name; + let entry_name = &*self.entry_name; - frame_decode::storage::encode_storage_key_prefix(pallet_name, storage_name) + frame_decode::storage::encode_storage_key_prefix(pallet_name, entry_name) } } @@ -258,7 +261,7 @@ where pub struct StorageEntryMapClient<'atblock, Client, T> { client: &'atblock Client, pallet_name: String, - storage_name: String, + entry_name: String, info: AnyStorageInfo<'atblock>, marker: std::marker::PhantomData, } @@ -274,13 +277,13 @@ where } /// Get the storage entry name. - pub fn storage_name(&self) -> &str { - &self.storage_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. - pub fn default(&self) -> Option> { + 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()) @@ -296,7 +299,8 @@ where { /// Fetch a specific key in this map. If the number of keys provided is not equal /// to the number of keys required to fetch a single value from the map, then an error - /// will be emitted. + /// will be emitted. If no value exists but there is a default value for this storage + /// entry, then the default value will be returned. Else, `None` will be returned. pub async fn fetch( &self, keys: Keys, @@ -313,20 +317,12 @@ where } let key_bytes = self.key(keys)?; - fetch(self.client, &key_bytes) - .await - .map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes)))) - } + let value = fetch(self.client, &key_bytes) + .await? + .map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))) + .or_else(|| self.default_value()); - /// Fetch a specific key in this map as per [`StorageEntryMapClient::fetch`], but return the default - /// value for the storage entry if one exists and the entry was not found. - pub async fn fetch_or_default( - &self, - keys: Keys, - ) -> Result>, StorageError> { - self.fetch(keys) - .await - .map(|option_val| option_val.or_else(|| self.default())) + Ok(value) } /// Iterate over the values underneath the provided keys. @@ -387,14 +383,12 @@ where // the key bytes and some metadata about them. Or maybe just fetch_raw and iter_raw. fn key(&self, keys: Keys) -> Result, StorageError> { with_info!(info = &self.info => { - let mut key_bytes = Vec::new(); - frame_decode::storage::encode_storage_key_with_info_to( + let key_bytes = frame_decode::storage::encode_storage_key_with_info( &self.pallet_name, - &self.storage_name, + &self.entry_name, keys, &info.info, info.resolver, - &mut key_bytes, ).map_err(|e| StorageError::KeyEncodeError { reason: e })?; Ok(key_bytes) }) diff --git a/historic/src/storage/storage_entry.rs b/historic/src/storage/storage_entry.rs index 736b092fb0..ed9093e743 100644 --- a/historic/src/storage/storage_entry.rs +++ b/historic/src/storage/storage_entry.rs @@ -1,8 +1,7 @@ use super::storage_info::AnyStorageInfo; use super::storage_key::StorageKey; use super::storage_value::StorageValue; -use crate::error::{StorageKeyError, StorageValueError}; -use scale_decode::DecodeAsType; +use crate::error::StorageKeyError; use std::borrow::Cow; /// This represents a storage entry, which is a key-value pair in the storage. @@ -30,11 +29,6 @@ impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> { &self.key } - /// Get the raw bytes for this storage entry's value. - pub fn value_bytes(&self) -> &[u8] { - self.value.bytes() - } - /// Consume this storage entry and return the raw bytes for the key and value. pub fn into_key_and_value_bytes(self) -> (Vec, Vec) { (self.key, self.value.into_bytes()) @@ -42,12 +36,12 @@ impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> { /// 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 decode_key(&'_ self) -> Result, StorageKeyError> { + pub fn key(&'_ self) -> Result, StorageKeyError> { StorageKey::new(self.value.info, &self.key) } - /// Decode this storage value. - pub fn decode_value(&self) -> Result { - self.value.decode::() + /// Return the storage value. + pub fn value(&self) -> &StorageValue<'entry, 'atblock> { + &self.value } } diff --git a/historic/src/storage/storage_key.rs b/historic/src/storage/storage_key.rs index 1ec12e9202..927a39d92f 100644 --- a/historic/src/storage/storage_key.rs +++ b/historic/src/storage/storage_key.rs @@ -81,7 +81,7 @@ impl<'entry, 'atblock> StorageKey<'entry, 'atblock> { /// Attempt to decode the values contained within this storage key to the `Target` type /// provided. This type is typically a tuple of types which each implement [`scale_decode::DecodeAsType`] /// and correspond to each of the key types present, in order. - pub fn decode(&self) -> Result { + pub fn decode_as(&self) -> Result { with_key_info!(info = &self.info => { let values = frame_decode::storage::decode_storage_key_values( self.bytes, @@ -154,7 +154,7 @@ impl<'key, 'entry, 'atblock> StorageKeyPart<'key, 'entry, 'atblock> { /// 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(&self) -> Result, StorageKeyError> { + pub fn decode_as(&self) -> Result, StorageKeyError> { with_key_info!(info = &self.info => { let part_info = &info.info[self.index]; let Some(value_info) = part_info.value() else { diff --git a/historic/src/storage/storage_value.rs b/historic/src/storage/storage_value.rs index f4f2704e50..0a32b5f4fe 100644 --- a/historic/src/storage/storage_value.rs +++ b/historic/src/storage/storage_value.rs @@ -27,7 +27,7 @@ impl<'entry, 'atblock> StorageValue<'entry, 'atblock> { } /// Decode this storage value. - pub fn decode(&self) -> Result { + pub fn decode_as(&self) -> Result { with_info!(info = &self.info => { let cursor = &mut &*self.bytes; diff --git a/metadata/benches/bench.rs b/metadata/benches/bench.rs index c9adb41109..701927163a 100644 --- a/metadata/benches/bench.rs +++ b/metadata/benches/bench.rs @@ -74,10 +74,10 @@ fn bench_get_storage_hash(c: &mut Criterion) { }; for storage in storage_entries.entries() { - let storage_name = storage.name(); - let bench_name = format!("{pallet_name}/{storage_name}"); + let entry_name = storage.name(); + let bench_name = format!("{pallet_name}/{entry_name}"); group.bench_function(&bench_name, |b| { - b.iter(|| pallet.storage_hash(storage_name)) + b.iter(|| pallet.storage_hash(entry_name)) }); } } diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index 849b006b69..5d1c3cfd7f 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -546,25 +546,34 @@ async fn wait_runtime_upgrade_in_finalized_block( Err(err) => return Some(Err(err)), }; - let key: Vec = vec![]; - let addr = crate::dynamic::storage("System", "LastRuntimeUpgrade", key); + let addr = crate::dynamic::storage::<(), scale_value::Value>("System", "LastRuntimeUpgrade"); - let chunk = match client.storage().at(block_ref.hash()).fetch(&addr).await { - Ok(Some(v)) => v, - Ok(None) => { - // The storage `system::lastRuntimeUpgrade` should always exist. - // - unreachable!("The storage item `system::lastRuntimeUpgrade` should always exist") - } - Err(e) => return Some(Err(e)), + let client_at = client.storage().at(block_ref.hash()); + let Ok(client) = client_at.entry(addr) else { + // The storage `system::lastRuntimeUpgrade` should always exist. + // + return Some(Err(Error::Other( + "The storage item `system::lastRuntimeUpgrade` should always exist (1)".to_string(), + ))); }; - let scale_val = match chunk.to_value() { - Ok(v) => v, - Err(e) => return Some(Err(e.into())), + let client = client + .into_plain() + .expect("System.LastRuntimeUpgrade should always be a plain storage entry"); + + 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(), + ))), + Err(e) => return Some(Err(e)) }; - let Some(Ok(spec_version)) = scale_val + let value = value + .decode_as::() + .expect("Should be able to decode anything into scale_value::Value"); + + let Some(Ok(spec_version)) = value .at("spec_version") .and_then(|v| v.as_u128()) .map(u32::try_from) diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 589f0ca6af..be292c012e 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -76,7 +76,7 @@ pub mod metadata { /// Submit dynamic transactions. pub mod dynamic { pub use subxt_core::dynamic::{ - At, DecodedValue, DecodedValueThunk, Value, constant, runtime_api_call, tx, // storage // TODO re-add + At, DecodedValue, DecodedValueThunk, Value, constant, runtime_api_call, tx, storage, view_function_call, }; } diff --git a/subxt/src/storage/mod.rs b/subxt/src/storage/mod.rs index d5e7e957e3..0c4f239b85 100644 --- a/subxt/src/storage/mod.rs +++ b/subxt/src/storage/mod.rs @@ -6,6 +6,9 @@ mod storage_client; mod storage_client_at; +mod storage_value; +mod storage_entry; +mod storage_key; pub use storage_client::StorageClient; pub use storage_client_at::StorageClientAt; diff --git a/subxt/src/storage/storage_client_at.rs b/subxt/src/storage/storage_client_at.rs index 1d837d994a..d2292ba760 100644 --- a/subxt/src/storage/storage_client_at.rs +++ b/subxt/src/storage/storage_client_at.rs @@ -4,34 +4,52 @@ use crate::{ backend::{BackendExt, BlockRef}, - client::OnlineClientT, + client::{OfflineClientT, OnlineClientT}, config::{Config, HashFor}, error::{Error, MetadataError, StorageAddressError}, metadata::DecodeWithMetadata, + storage::storage_value::StorageValue, }; use codec::Decode; use derive_where::derive_where; use futures::StreamExt; +use scale_info::PortableRegistry; use std::{future::Future, marker::PhantomData}; -use subxt_core::storage::address::Address; -use subxt_core::utils::{Maybe, Yes, No}; +use subxt_core::storage::{address::Address, PrefixOf}; +use subxt_core::utils::{Maybe, Yes, No, YesNoMaybe}; +use subxt_core::Metadata; +use frame_decode::storage::{IntoEncodableValues, StorageInfo}; +use std::borrow::Cow; +use scale_decode::DecodeAsType; +use super::storage_entry::StorageEntry; /// This is returned from a couple of storage functions. -pub use crate::backend::StreamOfResults; +pub use crate::backend::StreamOf; /// Query the runtime storage. #[derive_where(Clone; Client)] pub struct StorageClientAt { client: Client, + metadata: Metadata, block_ref: BlockRef>, _marker: PhantomData, } -impl StorageClientAt { - /// Create a new [`Storage`] +impl StorageClientAt +where + T: Config, + Client: OfflineClientT +{ + /// Create a new [`StorageClientAt`]. pub(crate) fn new(client: Client, block_ref: BlockRef>) -> Self { + // Retrieve and store metadata here so that we can borrow it in + // subsequent structs, and thus also borrow storage info and + // things that borrow from metadata. + let metadata = client.metadata(); + Self { client, + metadata, block_ref, _marker: PhantomData, } @@ -41,29 +59,34 @@ impl StorageClientAt { impl StorageClientAt where T: Config, - Client: OnlineClientT, + 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; - let storage_info = self + let types = self.metadata.types(); + let info = self .client .metadata() .storage_info(address.pallet_name(), address.entry_name())?; - let value = if storage_info.keys.is_empty() { + let value = if info.keys.is_empty() { StorageEntryClientValue::Plain(StorageEntryPlainClient { client: self.client.clone(), block_ref: self.block_ref.clone(), - address, + address, + info, + types, _marker: core::marker::PhantomData }) } else { StorageEntryClientValue::Map(StorageEntryMapClient { client: self.client.clone(), block_ref: self.block_ref.clone(), - address, + address, + info, + types, _marker: core::marker::PhantomData }) }; @@ -75,28 +98,49 @@ where } } -pub struct StorageEntryClient { - value: StorageEntryClientValue, +/// 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 { - Plain(StorageEntryPlainClient), - Map(StorageEntryMapClient), +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 StorageEntryClient { +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.address.pallet_name(), - StorageEntryClientValue::Map(client) => client.address.pallet_name(), + StorageEntryClientValue::Plain(client) => client.pallet_name(), + StorageEntryClientValue::Map(client) => client.pallet_name(), } } - pub fn storage_name(&self) -> &str { + pub fn entry_name(&self) -> &str { match &self.value { - StorageEntryClientValue::Plain(client) => client.address.entry_name(), - StorageEntryClientValue::Map(client) => client.address.entry_name(), + 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(), } } } @@ -104,11 +148,21 @@ impl StorageEntryClient StorageEntryClient { - pub fn into_map(self) -> StorageEntryMapClient { +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(_) => panic!("When IsMap = Yes, StorageEntryClient should always be a map.") + 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 + } + } } } } @@ -116,54 +170,258 @@ impl StorageEntryClient // 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 { +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::Map(_) => panic!("When IsMap = No, StorageEntryClient should always be a plain 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 StorageEntryClient { - pub fn into_map(self) -> Option> { +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> { + 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, } } - - 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 { +/// This represents a plain storage value at some location. +pub struct StorageEntryPlainClient<'atblock, T: Config, Client, Addr, HasDefaultValue> { client: Client, block_ref: BlockRef>, address: Addr, - _marker: PhantomData, + info: StorageInfo<'atblock, u32>, + types: &'atblock PortableRegistry, + _marker: PhantomData<(T, HasDefaultValue)>, } -pub struct StorageEntryMapClient { +impl <'atblock, T, Client, Addr, HasDefaultValue> StorageEntryPlainClient<'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 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() + ) + } + + /// 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 + } + } +} + +// 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> +where + T: Config, + Addr: Address, + Client: OnlineClientT +{ + pub async fn fetch(&'_ self) -> Result, Error> { + let value = self + .try_fetch() + .await? + .unwrap_or_else(|| { + let bytes = self.info + .default_value + .as_deref() + .unwrap_or_default() + .to_vec(); + StorageValue::new(&self.info, self.types, bytes) + }); + + 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()) + .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, - _marker: PhantomData, + 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 + } + } +} + +// 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> +where + T: Config, + Addr: Address, + Client: OnlineClientT +{ + pub async fn 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. + todo!("Error: wrong number of keys provided.") + } + + let value = self + .try_fetch(keys) + .await? + .or_else(|| self.default_value()) + .unwrap(); + + 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. + todo!("Error: wrong number of keys provided.") + } + + let key = frame_decode::storage::encode_storage_key_with_info( + self.address.pallet_name(), + self.address.entry_name(), + keys, + &self.info, + self.types + )?; + + let value = self.client + .backend() + .storage_fetch_value(key, self.block_ref.hash()) + .await? + .map(|bytes| StorageValue::new(&self.info, self.types, bytes)) + .or_else(|| self.default_value()); + + Ok(value) + } + + 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 info = &self.info; + let types = self.types; + + let stream = self.client + .backend() + .storage_fetch_descendant_values(key_bytes, block_hash) + .await? + .map(|kv| { + let kv = match kv { + Ok(kv) => kv, + Err(e) => return Err(e), + }; + + Ok(StorageEntry::new(info, types, kv.key, Cow::Owned(kv.value))) + }); + + Ok(StreamOf::new(Box::pin(stream))) + } + + fn key>(&self, keys: Keys) -> Result, Error> { + let key_bytes = frame_decode::storage::encode_storage_key_with_info( + &self.address.pallet_name(), + &self.address.entry_name(), + keys, + &self.info, + self.types, + )?; + Ok(key_bytes) + } +} /* impl Storage diff --git a/subxt/src/storage/storage_entry.rs b/subxt/src/storage/storage_entry.rs new file mode 100644 index 0000000000..0f96b25bbf --- /dev/null +++ b/subxt/src/storage/storage_entry.rs @@ -0,0 +1,53 @@ +// 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::storage_value::StorageValue; +use super::storage_key::StorageKey; +use subxt_core::storage::address::Address; +use frame_decode::storage::StorageInfo; +use scale_info::PortableRegistry; +use std::borrow::Cow; + +/// This represents a storage entry, which is a key-value pair in the storage. +pub struct StorageEntry<'entry, 'atblock, Addr: Address> { + key: Vec, + // This contains the storage information already: + value: StorageValue<'entry, 'atblock, Addr::Value>, +} + +impl<'entry, 'atblock, Addr: Address> StorageEntry<'entry, 'atblock, Addr> { + /// Create a new storage entry. + pub fn new( + info: &'entry StorageInfo<'atblock, u32>, + types: &'atblock PortableRegistry, + key: Vec, + value: Cow<'atblock, [u8]>, + ) -> Self { + Self { + key, + value: StorageValue::new(info, types, value), + } + } + + /// Get the raw bytes for this storage entry's key. + pub fn key_bytes(&self) -> &[u8] { + &self.key + } + + /// Consume this storage entry and return the raw bytes for the key and value. + pub fn into_key_and_value_bytes(self) -> (Vec, Vec) { + (self.key, self.value.into_bytes()) + } + + /// 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) + } + + /// Return the storage value. + pub fn value(&self) -> &StorageValue<'entry, 'atblock, Addr::Value> { + &self.value + } +} diff --git a/subxt/src/storage/storage_key.rs b/subxt/src/storage/storage_key.rs new file mode 100644 index 0000000000..30f15d825b --- /dev/null +++ b/subxt/src/storage/storage_key.rs @@ -0,0 +1,140 @@ +// 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::storage::{StorageInfo, StorageKey as StorageKeyPartInfo, IntoDecodableValues}; +use scale_info::PortableRegistry; +use core::marker::PhantomData; + +pub use frame_decode::storage::StorageHasher; + +/// This represents the different parts of a storage key. +pub struct StorageKey<'entry, 'atblock, KeyParts> { + info: StorageKeyPartInfo, + types: &'atblock PortableRegistry, + bytes: &'entry [u8], + marker: PhantomData +} + +impl<'entry, 'atblock, KeyParts: IntoDecodableValues> StorageKey<'entry, 'atblock, KeyParts> { + pub(crate) fn new( + info: &StorageInfo<'atblock, u32>, + types: &'atblock PortableRegistry, + bytes: &'entry [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::DecodeError { reason: e.map_type_id(|id| id.to_string()) } + })?; + + if !cursor.is_empty() { + return Err(StorageKeyError::LeftoverBytes { + leftover_bytes: cursor.to_vec(), + }); + } + + Ok(StorageKey { + info: 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(|e| { + StorageKeyError::DecodeKeyValueError { reason: e } + })?; + + 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, + types: self.types, + bytes: self.bytes, + }) + } + + /// 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, + types: self.types, + bytes: self.bytes, + }) + } else { + None + } + } +} + +/// This represents a part of a storage key. +pub struct StorageKeyPart<'key, 'entry, 'atblock> { + index: usize, + info: &'key StorageKeyPartInfo, + types: &'atblock PortableRegistry, + bytes: &'entry [u8], +} + +impl<'key, 'entry, 'atblock> StorageKeyPart<'key, 'entry, 'atblock> { + /// Get the raw bytes for this part of the storage key. + pub fn bytes(&self) -> &'entry [u8] { + let part = &self.info[self.index]; + let hash_range = part.hash_range(); + let value_range = part + .value() + .map(|v| v.range()) + .unwrap_or(std::ops::Range { start: hash_range.end, end: hash_range.end }); + let combined_range = std::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().clone(); + + let decoded_key_part = T::decode_as_type( + &mut &*value_bytes, + value_ty, + self.types, + ).map_err(|e| StorageKeyError::DecodePartError { index: self.index, reason: e })?; + + Ok(Some(decoded_key_part)) + } +} diff --git a/subxt/src/storage/storage_value.rs b/subxt/src/storage/storage_value.rs new file mode 100644 index 0000000000..85cd19ae04 --- /dev/null +++ b/subxt/src/storage/storage_value.rs @@ -0,0 +1,62 @@ +// 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::storage::StorageInfo; +use scale_decode::DecodeAsType; +use scale_info::PortableRegistry; +use core::marker::PhantomData; +use std::borrow::Cow; + +use crate::Error; + +/// This represents a storage value. +pub struct StorageValue<'entry, 'atblock, Value> { + pub(crate) info: &'entry StorageInfo<'atblock, u32>, + pub(crate) types: &'atblock PortableRegistry, + bytes: Cow<'entry, [u8]>, + marker: PhantomData +} + +impl<'entry, 'atblock, Value: DecodeAsType> StorageValue<'entry, 'atblock, Value> { + /// Create a new storage value. + pub fn new( + info: &'entry StorageInfo<'atblock, u32>, + types: &'atblock PortableRegistry, + bytes: impl Into>, + ) -> Self { + Self { info, types, bytes: bytes.into(), 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 = T::decode_as_type( + cursor, + self.info.value_id, + self.types, + ).map_err(|e| todo!("Define proper errors"))?; + + if !cursor.is_empty() { + return Err(todo!("Define proper errors")); + } + + Ok(value) + } +}