From d77f2178b31803f082da58c20193975429b1540f Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 28 Feb 2024 13:15:17 +0000 Subject: [PATCH 1/3] Storage decode tweaks --- codegen/src/api/storage.rs | 6 +- metadata/src/lib.rs | 18 ++- subxt/src/error/mod.rs | 4 +- subxt/src/storage/mod.rs | 3 +- subxt/src/storage/storage_key.rs | 192 +++++++++++++++++------------- subxt/src/storage/storage_type.rs | 14 ++- subxt/src/storage/utils.rs | 42 +------ 7 files changed, 139 insertions(+), 140 deletions(-) diff --git a/codegen/src/api/storage.rs b/codegen/src/api/storage.rs index f6ecd8861a..330843ccf7 100644 --- a/codegen/src/api/storage.rs +++ b/codegen/src/api/storage.rs @@ -201,7 +201,7 @@ fn generate_storage_entry_fns( 0 => (quote!(()), quote!(())), 1 => { let key = &keys_slice[0]; - if key.hasher.hash_contains_unhashed_key() { + if key.hasher.ends_with_key() { let arg = &key.arg_name; let keys = quote!(#static_storage_key::new(#arg.borrow())); let path = &key.alias_type_path; @@ -216,7 +216,7 @@ fn generate_storage_entry_fns( |MapEntryKey { arg_name, hasher, .. }| { - if hasher.hash_contains_unhashed_key() { + if hasher.ends_with_key() { quote!( #static_storage_key::new(#arg_name.borrow()) ) } else { quote!(()) @@ -230,7 +230,7 @@ fn generate_storage_entry_fns( hasher, .. }| { - if hasher.hash_contains_unhashed_key() { + if hasher.ends_with_key() { quote!( #static_storage_key<#alias_type_path> ) } else { quote!(()) diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 0b5db7277c..2f3c6c0000 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -476,14 +476,14 @@ pub enum StorageHasher { } impl StorageHasher { - /// The hash produced by a [`StorageHasher`] has 1 or 2 of the following components: - /// 1. A fixed size hash. (not present for [`StorageHasher::Identity`]) - /// 2. The key that was used as an input to the hasher. (only present for + /// The hash produced by a [`StorageHasher`] can have these two components, in order: + /// + /// 1. A fixed size hash. (not present for [`StorageHasher::Identity`]). + /// 2. The SCALE encoded key that was used as an input to the hasher (only present for /// [`StorageHasher::Twox64Concat`], [`StorageHasher::Blake2_128Concat`] or [`StorageHasher::Identity`]). /// - /// This function returns the number of hash bytes that must be skipped to get to the 2. component, the unhashed key. - /// To check whether or not an unhashed key is contained, see [`StorageHasher::hash_bytes_before_unhashed_key`]. - pub fn hash_bytes_before_unhashed_key(&self) -> usize { + /// This function returns the number of bytes used to represent the first of these. + pub fn len_excluding_key(&self) -> usize { match self { StorageHasher::Blake2_128Concat => 16, StorageHasher::Twox64Concat => 8, @@ -495,10 +495,8 @@ impl StorageHasher { } } - /// Returns if the key a hash was produces for, is contained in the hash itself. - /// If this is `true`, [`StorageHasher::hash_bytes_before_unhashed_key`] can be used, - /// to find the offset of the start byte of the key from the start of the hash bytes. - pub fn hash_contains_unhashed_key(&self) -> bool { + /// Returns true if the key used to produce the hash is appended to the hash itself. + pub fn ends_with_key(&self) -> bool { matches!( self, StorageHasher::Blake2_128Concat | StorageHasher::Twox64Concat | StorageHasher::Identity diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 65b8ffaa57..a46c203f2a 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -211,12 +211,10 @@ pub enum StorageAddressError { #[error("Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata.")] UnexpectedAddressBytes, /// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose. - #[error("An invalid hasher was used to reconstruct a value of type {ty_name} (id={ty_id}) from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher")] + #[error("An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher")] HasherCannotReconstructKey { /// Type id of the key's type. ty_id: u32, - /// Type name of the key's type. - ty_name: String, /// The invalid hasher that caused this error. hasher: StorageHasher, }, diff --git a/subxt/src/storage/mod.rs b/subxt/src/storage/mod.rs index 5cb96f4726..98f8365274 100644 --- a/subxt/src/storage/mod.rs +++ b/subxt/src/storage/mod.rs @@ -8,8 +8,7 @@ mod storage_address; mod storage_client; mod storage_key; mod storage_type; - -pub mod utils; +mod utils; pub use storage_client::StorageClient; diff --git a/subxt/src/storage/storage_key.rs b/subxt/src/storage/storage_key.rs index 5b60d49c2d..842c76bf6e 100644 --- a/subxt/src/storage/storage_key.rs +++ b/subxt/src/storage/storage_key.rs @@ -1,11 +1,12 @@ -use super::utils::strip_storage_hash_bytes; use crate::{ - dynamic::DecodedValueThunk, error::{Error, StorageAddressError}, - metadata::{DecodeWithMetadata, Metadata}, + metadata::Metadata, utils::{Encoded, Static}, }; +use scale_decode::{visitor::IgnoreVisitor, DecodeAsType}; use scale_encode::EncodeAsType; +use scale_info::PortableRegistry; +use scale_value::Value; use subxt_metadata::StorageHasher; use derivative::Derivative; @@ -21,17 +22,20 @@ pub trait StorageKey { // But that plays poorly with the `Flatten` and `Chain` structs. fn keys_len(&self) -> usize; - /// Attempts to decode the StorageKey from a storage address that has been stripped of its root bytes. + /// Attempts to decode the StorageKey given some bytes and a set of hashers and type IDs that they are meant to represent. /// - /// Example: Imagine The `StorageKey` is a tuple (A,B) and the hashers are: [Blake2_128Concat, Twox64Concat]. + /// Example: Imagine The `StorageKey` is a tuple `(A,B)` and the hashers are `[Blake2_128Concat, Twox64Concat]`. /// Then the memory layout of the storage address is: + /// /// ```txt - /// | 8 bytes pallet hash | 8 bytes entry hash | 16 bytes hash of A | ... bytes of A | 8 bytes hash of B | ... bytes of B | + /// | 16 byte hash of A | n bytes for SCALE encoded A | 8 byte hash of B | n bytes for SCALE encoded B | /// ``` - /// `cursor` should point into a region after those first 16 bytes, at the start of a new hash. - /// `hashers_and_ty_ids` should consume all the hashers that have been used for decoding, such that there are less hashers coming to the next key. + /// + /// Implementations of this must advance the `bytes` and `hashers_and_ty_ids` cursors to consume any that they are using, or + /// return an error if they cannot appropriately do so. When a tuple of such implementations is given, each implementation + /// in the tuple receives the remaining un-consumed bytes and hashers from the previous ones. fn decode_from_bytes( - cursor: &mut &[u8], + bytes: &mut &[u8], hashers_and_ty_ids: &mut &[(StorageHasher, u32)], metadata: &Metadata, ) -> Result @@ -39,7 +43,8 @@ pub trait StorageKey { Self: Sized + 'static; } -/// Implement `StorageKey` for `()` which can be used for keyless storage entries. +/// Implement `StorageKey` for `()` which can be used for keyless storage entries, +/// or to otherwise just ignore some entry. impl StorageKey for () { fn keys_iter(&self) -> impl Iterator { std::iter::empty() @@ -50,18 +55,20 @@ impl StorageKey for () { } fn decode_from_bytes( - _cursor: &mut &[u8], + bytes: &mut &[u8], hashers_and_ty_ids: &mut &[(StorageHasher, u32)], - _metadata: &Metadata, + metadata: &Metadata, ) -> Result { - if hashers_and_ty_ids.is_empty() { - return Err(StorageAddressError::WrongNumberOfHashers { - hashers: 0, - fields: 1, - } - .into()); - } - *hashers_and_ty_ids = &hashers_and_ty_ids[1..]; // Advance cursor by 1 + // If no hashers, we just do nothing. + let Some((hasher, ty_id)) = hashers_and_ty_ids.first() else { + return Ok(()); + }; + + // Consume the hash bytes (we don't care about the key output here). + consume_hash_returning_key_bytes(bytes, *hasher, *ty_id, metadata.types())?; + // Advance our hasher cursor as well now that we've used it. + *hashers_and_ty_ids = &hashers_and_ty_ids[1..]; + Ok(()) } } @@ -71,8 +78,8 @@ impl StorageKey for () { #[derive(Derivative)] #[derivative(Clone(bound = ""), Debug(bound = ""))] pub struct StaticStorageKey { - pub(super) bytes: Static, - pub(super) _marker: std::marker::PhantomData, + bytes: Static, + _marker: std::marker::PhantomData, } impl StaticStorageKey { @@ -111,7 +118,7 @@ impl StorageKey for StaticStorageKey { } fn decode_from_bytes( - cursor: &mut &[u8], + bytes: &mut &[u8], hashers_and_ty_ids: &mut &[(StorageHasher, u32)], metadata: &Metadata, ) -> Result @@ -125,53 +132,28 @@ impl StorageKey for StaticStorageKey { } .into()); }; - *hashers_and_ty_ids = &hashers_and_ty_ids[1..]; // Advance cursor by 1 - decode_storage_key_from_hash(cursor, hasher, *ty_id, metadata) - } -} -pub fn decode_storage_key_from_hash( - cursor: &mut &[u8], - hasher: &StorageHasher, - ty_id: u32, - metadata: &Metadata, -) -> Result, Error> { - strip_storage_hash_bytes(cursor, hasher)?; - - let bytes = *cursor; - if let Err(err) = scale_decode::visitor::decode_with_visitor( - cursor, - ty_id, - metadata.types(), - scale_decode::visitor::IgnoreVisitor, - ) { - return Err(scale_decode::Error::from(err).into()); + // Advance the bytes cursor, returning any key bytes. + let key_bytes = consume_hash_returning_key_bytes(bytes, *hasher, *ty_id, metadata.types())?; + // Advance the hasher cursor now we've used it. + *hashers_and_ty_ids = &hashers_and_ty_ids[1..]; + + // if the hasher had no key appended, we can't decode it into a `StaticStorageKey`. + let Some(key_bytes) = key_bytes else { + return Err(StorageAddressError::HasherCannotReconstructKey { + ty_id: *ty_id, + hasher: *hasher, + } + .into()); + }; + + // Return the key bytes. + let key = StaticStorageKey { + bytes: Static(Encoded(key_bytes.to_vec())), + _marker: std::marker::PhantomData::, + }; + Ok(key) } - let bytes_decoded = bytes.len() - cursor.len(); - - // Note: This validation check makes sure, only zero-sized types can be decoded from - // hashers that do not support reconstruction of a value - if !hasher.hash_contains_unhashed_key() && bytes_decoded > 0 { - let ty_name = metadata - .types() - .resolve(ty_id) - .expect("ty_id is in metadata, because decode_with_visitor did not fail above; qed") - .path - .to_string(); - return Err(StorageAddressError::HasherCannotReconstructKey { - ty_id, - ty_name, - hasher: *hasher, - } - .into()); - }; - - let key_bytes = bytes[..bytes_decoded].to_vec(); - let key = StaticStorageKey { - bytes: Static(Encoded(key_bytes)), - _marker: std::marker::PhantomData::, - }; - Ok(key) } impl StorageKey for Vec { @@ -186,32 +168,78 @@ impl StorageKey for Vec { } fn decode_from_bytes( - cursor: &mut &[u8], + bytes: &mut &[u8], hashers_and_ty_ids: &mut &[(StorageHasher, u32)], metadata: &Metadata, ) -> Result where Self: Sized + 'static, { - let mut hashers_and_ty_ids_iter = hashers_and_ty_ids.iter(); let mut result: Vec = vec![]; - let mut n = 0; - while !cursor.is_empty() { - let Some((hasher, ty_id)) = hashers_and_ty_ids_iter.next() else { - // Still bytes left, but no hashers and type ids anymore to pull from: this is an unexpected error. - return Err(StorageAddressError::UnexpectedAddressBytes.into()); - }; - strip_storage_hash_bytes(cursor, hasher)?; - let decoded = DecodedValueThunk::decode_with_metadata(cursor, *ty_id, metadata)?; - let value = decoded.to_value()?; - result.push(value.remove_context()); - n += 1; + for (hasher, ty_id) in hashers_and_ty_ids.iter() { + match consume_hash_returning_key_bytes(bytes, *hasher, *ty_id, metadata.types())? { + Some(value_bytes) => { + let value = + Value::decode_as_type(&mut &*value_bytes, *ty_id, metadata.types())?; + result.push(value.remove_context()); + } + None => { + result.push(Value::unnamed_composite([])); + } + } + *hashers_and_ty_ids = &hashers_and_ty_ids[1..]; // Advance by 1 each time. + } + + // We've consumed all of the hashers, so we expect to also consume all of the bytes: + if !bytes.is_empty() { + return Err(StorageAddressError::UnexpectedAddressBytes.into()); } - *hashers_and_ty_ids = &hashers_and_ty_ids[n..]; // Advance cursor by n + Ok(result) } } +// Skip over the hash bytes (including any key at the end), returning bytes +// representing the key if one exists, or None if the hasher has no key appended. +fn consume_hash_returning_key_bytes<'a>( + bytes: &mut &'a [u8], + hasher: StorageHasher, + ty_id: u32, + types: &PortableRegistry, +) -> Result, Error> { + // Strip the bytes off for the actual hash, consuming them. + strip_storage_hash_bytes(bytes, hasher)?; + // Now, find the bytes representing the key, consuming them. + let before_key = *bytes; + if hasher.ends_with_key() { + scale_decode::visitor::decode_with_visitor(bytes, ty_id, types, IgnoreVisitor) + .map_err(|err| Error::Decode(err.into()))?; + + // Return the key bytes, having advanced the input cursor past them. + let key_bytes = &before_key[before_key.len() - bytes.len()..]; + Ok(Some(key_bytes)) + } else { + Ok(None) + } +} + +/// Strips the first few bytes off a hash to possibly skip to the plan key value, +/// if [`StorageHasher::hash_contains_unhashed_key()`] for this StorageHasher. +/// +/// Returns `Err(..)` if there are not enough bytes. +/// Returns `Ok(())` otherwise +fn strip_storage_hash_bytes( + hash: &mut &[u8], + hasher: StorageHasher, +) -> Result<(), StorageAddressError> { + let bytes_to_strip = hasher.len_excluding_key(); + if hash.len() < bytes_to_strip { + return Err(StorageAddressError::UnexpectedAddressBytes); + } + *hash = &hash[bytes_to_strip..]; + Ok(()) +} + /// Generates StorageKey implementations for tuples, e.g. /// ```rs,norun /// impl StorageKey for (StorageKey, StorageKey) { diff --git a/subxt/src/storage/storage_type.rs b/subxt/src/storage/storage_type.rs index 20650a817e..34353f478e 100644 --- a/subxt/src/storage/storage_type.rs +++ b/subxt/src/storage/storage_type.rs @@ -3,7 +3,6 @@ // see LICENSE for license details. use super::storage_address::{StorageAddress, Yes}; -use super::utils::strip_storage_addess_root_bytes; use super::StorageKey; use crate::{ @@ -311,7 +310,8 @@ where } } -pub(crate) fn storage_hasher_type_id_pairs( +/// Return a vec of hashers and the associated type IDs for the keys that are hashed. +fn storage_hasher_type_id_pairs( entry: &StorageEntryType, metadata: &Metadata, ) -> Result, Error> { @@ -357,6 +357,16 @@ pub(crate) fn storage_hasher_type_id_pairs( } } +/// Strips the first 16 bytes (8 for the pallet hash, 8 for the entry hash) off some storage address bytes. +fn strip_storage_addess_root_bytes(address_bytes: &mut &[u8]) -> Result<(), StorageAddressError> { + if address_bytes.len() >= 16 { + *address_bytes = &address_bytes[16..]; + Ok(()) + } else { + Err(StorageAddressError::UnexpectedAddressBytes) + } +} + /// A pair of keys and values together with all the bytes that make up the storage address. /// `keys` is `None` if non-concat hashers are used. In this case the keys could not be extracted back from the key_bytes. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] diff --git a/subxt/src/storage/utils.rs b/subxt/src/storage/utils.rs index 281d587323..9dda39d7af 100644 --- a/subxt/src/storage/utils.rs +++ b/subxt/src/storage/utils.rs @@ -6,17 +6,12 @@ //! aren't things that should ever be overridden, and so don't exist on //! the trait itself. -use subxt_metadata::StorageHasher; - use super::StorageAddress; -use crate::{ - error::{Error, StorageAddressError}, - metadata::Metadata, -}; +use crate::{error::Error, metadata::Metadata}; /// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name /// and append those bytes to the output. -pub(crate) fn write_storage_address_root_bytes( +pub fn write_storage_address_root_bytes( addr: &Address, out: &mut Vec, ) { @@ -26,7 +21,7 @@ pub(crate) fn write_storage_address_root_bytes( /// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent /// a lookup in a storage map at that location. -pub(crate) fn storage_address_bytes( +pub fn storage_address_bytes( addr: &Address, metadata: &Metadata, ) -> Result, Error> { @@ -37,37 +32,8 @@ pub(crate) fn storage_address_bytes( } /// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`]. -pub(crate) fn storage_address_root_bytes(addr: &Address) -> Vec { +pub fn storage_address_root_bytes(addr: &Address) -> Vec { let mut bytes = Vec::new(); write_storage_address_root_bytes(addr, &mut bytes); bytes } - -/// Strips the first 16 bytes (8 for the pallet hash, 8 for the entry hash) off some storage address bytes. -pub(crate) fn strip_storage_addess_root_bytes( - address_bytes: &mut &[u8], -) -> Result<(), StorageAddressError> { - if address_bytes.len() >= 16 { - *address_bytes = &address_bytes[16..]; - Ok(()) - } else { - Err(StorageAddressError::UnexpectedAddressBytes) - } -} - -/// Strips the first few bytes off a hash to possibly skip to the plan key value, -/// if [`StorageHasher::hash_contains_unhashed_key()`] for this StorageHasher. -/// -/// Returns `Err(..)` if there are not enough bytes. -/// Returns `Ok(())` otherwise -pub fn strip_storage_hash_bytes( - hash: &mut &[u8], - hasher: &StorageHasher, -) -> Result<(), StorageAddressError> { - let bytes_to_strip = hasher.hash_bytes_before_unhashed_key(); - if hash.len() < bytes_to_strip { - return Err(StorageAddressError::UnexpectedAddressBytes); - } - *hash = &hash[bytes_to_strip..]; - Ok(()) -} From ef091021c35d23bed00d72783edd2d4fb75bbc67 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 28 Feb 2024 13:23:29 +0000 Subject: [PATCH 2/3] doc tweak --- subxt/src/storage/storage_key.rs | 39 ++++++++++++-------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/subxt/src/storage/storage_key.rs b/subxt/src/storage/storage_key.rs index 842c76bf6e..41846f948b 100644 --- a/subxt/src/storage/storage_key.rs +++ b/subxt/src/storage/storage_key.rs @@ -208,7 +208,12 @@ fn consume_hash_returning_key_bytes<'a>( types: &PortableRegistry, ) -> Result, Error> { // Strip the bytes off for the actual hash, consuming them. - strip_storage_hash_bytes(bytes, hasher)?; + let bytes_to_strip = hasher.len_excluding_key(); + if bytes.len() < bytes_to_strip { + return Err(StorageAddressError::UnexpectedAddressBytes.into()); + } + *bytes = &bytes[bytes_to_strip..]; + // Now, find the bytes representing the key, consuming them. let before_key = *bytes; if hasher.ends_with_key() { @@ -219,27 +224,11 @@ fn consume_hash_returning_key_bytes<'a>( let key_bytes = &before_key[before_key.len() - bytes.len()..]; Ok(Some(key_bytes)) } else { + // There are no key bytes, so return None. Ok(None) } } -/// Strips the first few bytes off a hash to possibly skip to the plan key value, -/// if [`StorageHasher::hash_contains_unhashed_key()`] for this StorageHasher. -/// -/// Returns `Err(..)` if there are not enough bytes. -/// Returns `Ok(())` otherwise -fn strip_storage_hash_bytes( - hash: &mut &[u8], - hasher: StorageHasher, -) -> Result<(), StorageAddressError> { - let bytes_to_strip = hasher.len_excluding_key(); - if hash.len() < bytes_to_strip { - return Err(StorageAddressError::UnexpectedAddressBytes); - } - *hash = &hash[bytes_to_strip..]; - Ok(()) -} - /// Generates StorageKey implementations for tuples, e.g. /// ```rs,norun /// impl StorageKey for (StorageKey, StorageKey) { @@ -306,11 +295,11 @@ macro_rules! impl_tuples { #[rustfmt::skip] const _: () = { - impl_tuples!(A iter_a 0, B iter_ab 1); - impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2); - impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3); - impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4); - impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5); - impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6); - impl_tuples!(A iter_a 0, B iter_ab 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6, H iter_h 7); + impl_tuples!(A iter_a 0, B iter_b 1); + impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2); + impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3); + impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4); + impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5); + impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6); + impl_tuples!(A iter_a 0, B iter_b 1, C iter_c 2, D iter_d 3, E iter_e 4, F iter_f 5, G iter_g 6, H iter_h 7); }; From ea34e58aab7edf34f304bd4ddd816371de8093b9 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 28 Feb 2024 13:36:10 +0000 Subject: [PATCH 3/3] more precise error when leftover or not enough bytes --- subxt/src/error/mod.rs | 8 +++++++- subxt/src/storage/storage_key.rs | 12 ++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index a46c203f2a..8fb4d7007f 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -207,8 +207,14 @@ pub enum StorageAddressError { /// The number of fields in the metadata for this storage entry. fields: usize, }, + /// We weren't given enough bytes to decode the storage address/key. + #[error("Not enough remaining bytes to decode the storage address/key")] + NotEnoughBytes, + /// We have leftover bytes after decoding the storage address. + #[error("We have leftover bytes after decoding the storage address")] + TooManyBytes, /// The bytes of a storage address are not the expected address for decoding the storage keys of the address. - #[error("Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata.")] + #[error("Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata")] UnexpectedAddressBytes, /// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose. #[error("An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher")] diff --git a/subxt/src/storage/storage_key.rs b/subxt/src/storage/storage_key.rs index 41846f948b..6c0c8b91f5 100644 --- a/subxt/src/storage/storage_key.rs +++ b/subxt/src/storage/storage_key.rs @@ -59,9 +59,13 @@ impl StorageKey for () { hashers_and_ty_ids: &mut &[(StorageHasher, u32)], metadata: &Metadata, ) -> Result { - // If no hashers, we just do nothing. + // If no hashers, we just do nothing (erroring if ). let Some((hasher, ty_id)) = hashers_and_ty_ids.first() else { - return Ok(()); + if bytes.is_empty() { + return Ok(()); + } else { + return Err(StorageAddressError::TooManyBytes.into()); + } }; // Consume the hash bytes (we don't care about the key output here). @@ -192,7 +196,7 @@ impl StorageKey for Vec { // We've consumed all of the hashers, so we expect to also consume all of the bytes: if !bytes.is_empty() { - return Err(StorageAddressError::UnexpectedAddressBytes.into()); + return Err(StorageAddressError::TooManyBytes.into()); } Ok(result) @@ -210,7 +214,7 @@ fn consume_hash_returning_key_bytes<'a>( // Strip the bytes off for the actual hash, consuming them. let bytes_to_strip = hasher.len_excluding_key(); if bytes.len() < bytes_to_strip { - return Err(StorageAddressError::UnexpectedAddressBytes.into()); + return Err(StorageAddressError::NotEnoughBytes.into()); } *bytes = &bytes[bytes_to_strip..];