mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 00:31:07 +00:00
Storage APIs added and compiling
This commit is contained in:
+9
-3
@@ -5,7 +5,8 @@ use crate::config::{Config, HashFor};
|
||||
use crate::error::{EventsError, ExtrinsicError};
|
||||
use crate::events::Events;
|
||||
use crate::extrinsics::Extrinsics;
|
||||
use crate::transactions::Transactions;
|
||||
use crate::storage::StorageClient;
|
||||
use crate::transactions::TransactionsClient;
|
||||
use core::marker::PhantomData;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
@@ -35,8 +36,13 @@ where
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
{
|
||||
/// Construct and submit transactions.
|
||||
pub fn tx(&self) -> Transactions<T, Client> {
|
||||
Transactions::new(self.client.clone())
|
||||
pub fn tx(&self) -> TransactionsClient<T, Client> {
|
||||
TransactionsClient::new(self.client.clone())
|
||||
}
|
||||
|
||||
/// Access storage at this block.
|
||||
pub fn storage(&self) -> StorageClient<T, Client> {
|
||||
StorageClient::new(self.client.clone())
|
||||
}
|
||||
|
||||
/// Obtain a reference to the metadata.
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::events::{self, DecodeAsEvent};
|
||||
use frame_decode::extrinsics::Extrinsic as ExtrinsicInfo;
|
||||
use scale_decode::{DecodeAsFields, DecodeAsType};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
pub use decode_as_extrinsic::DecodeAsExtrinsic;
|
||||
@@ -18,9 +19,10 @@ pub use extrinsic_transaction_extensions::{
|
||||
};
|
||||
|
||||
/// The extrinsics in a block.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Extrinsics<T, C> {
|
||||
client: C,
|
||||
extrinsics: Vec<Vec<u8>>,
|
||||
extrinsics: Arc<Vec<Vec<u8>>>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -36,7 +38,7 @@ impl<T: Config, C: OnlineClientAtBlockT<T>> Extrinsics<T, C> {
|
||||
|
||||
Ok(Extrinsics {
|
||||
client,
|
||||
extrinsics,
|
||||
extrinsics: Arc::new(extrinsics),
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ pub mod config;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod extrinsics;
|
||||
pub mod storage;
|
||||
pub mod transactions;
|
||||
pub mod utils;
|
||||
// pub mod book;
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
mod prefix_of;
|
||||
mod storage_entry;
|
||||
mod storage_key;
|
||||
mod storage_key_value;
|
||||
mod storage_value;
|
||||
|
||||
use crate::backend::BackendExt;
|
||||
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
|
||||
use crate::config::Config;
|
||||
use crate::error::StorageError;
|
||||
use address::Address;
|
||||
use core::marker::PhantomData;
|
||||
use frame_decode::helpers::Entry;
|
||||
use frame_decode::storage::StorageEntryInfo;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use prefix_of::PrefixOf;
|
||||
pub use storage_entry::StorageEntry;
|
||||
pub use storage_key::{StorageKey, StorageKeyPart};
|
||||
pub use storage_key_value::StorageKeyValue;
|
||||
pub use storage_value::StorageValue;
|
||||
pub mod address;
|
||||
|
||||
/// A client for working with transactions.
|
||||
#[derive(Clone)]
|
||||
pub struct StorageClient<T, Client> {
|
||||
client: Client,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClient<T, Client> {
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
StorageClient {
|
||||
client,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OfflineClientAtBlockT<T>> StorageClient<T, Client> {
|
||||
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
|
||||
/// that the shape of the storage value is the same as the shape expected by the static address.
|
||||
///
|
||||
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
|
||||
/// shape of the constant value), this just returns `Ok(())`
|
||||
pub fn validate<Addr: Address>(&self, address: Addr) -> Result<(), StorageError> {
|
||||
let Some(hash) = address.validation_hash() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let pallet_name = address.pallet_name();
|
||||
let entry_name = address.entry_name();
|
||||
|
||||
let pallet_metadata = self
|
||||
.client
|
||||
.metadata_ref()
|
||||
.pallet_by_name(pallet_name)
|
||||
.ok_or_else(|| StorageError::PalletNameNotFound(pallet_name.to_string()))?;
|
||||
let storage_hash = pallet_metadata.storage_hash(entry_name).ok_or_else(|| {
|
||||
StorageError::StorageEntryNotFound {
|
||||
pallet_name: pallet_name.to_string(),
|
||||
entry_name: entry_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
if storage_hash != hash {
|
||||
Err(StorageError::IncompatibleCodegen)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This returns a [`StorageEntry`], which allows working with the storage entry at the provided address.
|
||||
pub fn entry<Addr: Address>(
|
||||
&self,
|
||||
address: Addr,
|
||||
) -> Result<StorageEntry<'_, T, Client, Addr, Addr::IsPlain>, StorageError> {
|
||||
self.validate(&address)?;
|
||||
StorageEntry::new(&self.client, address)
|
||||
}
|
||||
|
||||
/// Iterate over all of the storage entries listed in the metadata for the current block. This does **not** include well known
|
||||
/// storage entries like `:code` which are not listed in the metadata.
|
||||
pub fn entries(&self) -> impl Iterator<Item = StorageEntries<'_, Client, T>> {
|
||||
let metadata = self.client.metadata_ref();
|
||||
Entry::tuples_of(metadata.storage_entries()).map(|(pallet_name, entry_name)| {
|
||||
StorageEntries {
|
||||
pallet_name: pallet_name.clone(),
|
||||
entry_name,
|
||||
client: &self.client,
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OnlineClientAtBlockT<T>> StorageClient<T, Client> {
|
||||
/// This is essentially a shorthand for `client.entry(addr)?.fetch(key_parts)`. See [`StorageEntry::fetch()`].
|
||||
pub async fn fetch<Addr: Address>(
|
||||
&self,
|
||||
addr: Addr,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<StorageValue<'_, Addr::Value>, StorageError> {
|
||||
let entry = self.entry(addr)?;
|
||||
entry.internal_fetch(key_parts).await
|
||||
}
|
||||
|
||||
/// This is essentially a shorthand for `client.entry(addr)?.try_fetch(key_parts)`. See [`StorageEntry::try_fetch()`].
|
||||
pub async fn try_fetch<Addr: Address>(
|
||||
&self,
|
||||
addr: Addr,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<Option<StorageValue<'_, Addr::Value>>, StorageError> {
|
||||
let entry = self.entry(addr)?;
|
||||
entry.internal_try_fetch(key_parts).await
|
||||
}
|
||||
|
||||
/// This is essentially a shorthand for `client.entry(addr)?.iter(key_parts)`. See [`StorageEntry::iter()`].
|
||||
pub async fn iter<Addr: Address, KeyParts: PrefixOf<Addr::KeyParts>>(
|
||||
&self,
|
||||
addr: Addr,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<
|
||||
impl futures::Stream<Item = Result<StorageKeyValue<'_, Addr>, StorageError>>
|
||||
+ use<'_, Addr, Client, T, KeyParts>,
|
||||
StorageError,
|
||||
> {
|
||||
let entry = self.entry(addr)?;
|
||||
entry.internal_iter(key_parts).await
|
||||
}
|
||||
|
||||
/// In rare cases, you may wish to fetch a storage value that does not live at a typical address. This method
|
||||
/// is a fallback for those cases, and allows you to provide the raw storage key bytes corresponding to the
|
||||
/// entry you wish to obtain. The response will either be the bytes for the value found at that location, or
|
||||
/// otherwise an error. [`StorageError::NoValueFound`] will be returned in the event that the request was valid
|
||||
/// but no value lives at the given location).
|
||||
pub async fn fetch_raw(&self, key_bytes: Vec<u8>) -> Result<Vec<u8>, StorageError> {
|
||||
let block_hash = self.client.block_hash();
|
||||
let value = self
|
||||
.client
|
||||
.backend()
|
||||
.storage_fetch_value(key_bytes, block_hash)
|
||||
.await
|
||||
.map_err(StorageError::CannotFetchValue)?
|
||||
.ok_or(StorageError::NoValueFound)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// The storage version of a pallet.
|
||||
/// The storage version refers to the `frame_support::traits::Metadata::StorageVersion` type.
|
||||
pub async fn storage_version(&self, pallet_name: impl AsRef<str>) -> Result<u16, StorageError> {
|
||||
// construct the storage key. This is done similarly in
|
||||
// `frame_support::traits::metadata::StorageVersion::storage_key()`:
|
||||
let mut key_bytes: Vec<u8> = vec![];
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(
|
||||
pallet_name.as_ref().as_bytes(),
|
||||
));
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(b":__STORAGE_VERSION__:"));
|
||||
|
||||
// fetch the raw bytes and decode them into the StorageVersion struct:
|
||||
let storage_version_bytes = self.fetch_raw(key_bytes).await?;
|
||||
|
||||
<u16 as codec::Decode>::decode(&mut &storage_version_bytes[..])
|
||||
.map_err(StorageError::CannotDecodeStorageVersion)
|
||||
}
|
||||
|
||||
/// Fetch the runtime WASM code.
|
||||
pub async fn runtime_wasm_code(&self) -> Result<Vec<u8>, StorageError> {
|
||||
// note: this should match the `CODE` constant in `sp_core::storage::well_known_keys`
|
||||
self.fetch_raw(b":code".to_vec()).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Working with a specific storage entry.
|
||||
pub struct StorageEntries<'atblock, Client, T> {
|
||||
pallet_name: Cow<'atblock, str>,
|
||||
entry_name: Cow<'atblock, str>,
|
||||
client: &'atblock Client,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'atblock, Client, T> StorageEntries<'atblock, Client, T>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
{
|
||||
/// The pallet name.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
}
|
||||
|
||||
/// The storage entry name.
|
||||
pub fn entry_name(&self) -> &str {
|
||||
&self.entry_name
|
||||
}
|
||||
|
||||
/// Extract the relevant storage information so that we can work with this entry.
|
||||
pub fn entry(
|
||||
&self,
|
||||
) -> Result<
|
||||
StorageEntry<'_, T, Client, address::DynamicAddress, crate::utils::Maybe>,
|
||||
StorageError,
|
||||
> {
|
||||
let addr = address::dynamic(self.pallet_name.to_owned(), self.entry_name.to_owned());
|
||||
StorageEntry::new(self.client, addr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Construct addresses to access storage entries with.
|
||||
|
||||
use crate::utils::{Maybe, YesMaybe};
|
||||
use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues};
|
||||
use scale_decode::DecodeAsType;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A storage address. This allows access to a given storage entry, which can then
|
||||
/// be iterated over or fetched from by providing the relevant set of keys, or
|
||||
/// otherwise inspected.
|
||||
pub trait Address {
|
||||
/// All of the keys required to get to an individual value at this address.
|
||||
/// Keys must always impl [`IntoEncodableValues`], and for iteration must
|
||||
/// also impl [`frame_decode::storage::IntoDecodableValues`].
|
||||
type KeyParts: IntoEncodableValues + IntoDecodableValues;
|
||||
/// Type of the storage value at this location.
|
||||
type Value: DecodeAsType;
|
||||
/// Does the address point to a plain value (as opposed to a map)?
|
||||
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
|
||||
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
|
||||
type IsPlain: YesMaybe;
|
||||
|
||||
/// The pallet containing this storage entry.
|
||||
fn pallet_name(&self) -> &str;
|
||||
|
||||
/// The name of the storage entry.
|
||||
fn entry_name(&self) -> &str;
|
||||
|
||||
/// Return a unique hash for this address which can be used to validate it against metadata.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]>;
|
||||
}
|
||||
|
||||
// Any reference to an address is a valid address.
|
||||
impl<A: Address + ?Sized> Address for &'_ A {
|
||||
type KeyParts = A::KeyParts;
|
||||
type Value = A::Value;
|
||||
type IsPlain = A::IsPlain;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
A::pallet_name(*self)
|
||||
}
|
||||
|
||||
fn entry_name(&self) -> &str {
|
||||
A::entry_name(*self)
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
A::validation_hash(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// An address which is generated by the static APIs.
|
||||
pub struct StaticAddress<KeyParts, Value, IsPlain> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
entry_name: Cow<'static, str>,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
marker: core::marker::PhantomData<(KeyParts, Value, IsPlain)>,
|
||||
}
|
||||
|
||||
impl<KeyParts, Value, IsPlain> Clone for StaticAddress<KeyParts, Value, IsPlain> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
pallet_name: self.pallet_name.clone(),
|
||||
entry_name: self.entry_name.clone(),
|
||||
validation_hash: self.validation_hash,
|
||||
marker: self.marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<KeyParts, Value, IsPlain> core::fmt::Debug for StaticAddress<KeyParts, Value, IsPlain> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("StaticAddress")
|
||||
.field("pallet_name", &self.pallet_name)
|
||||
.field("entry_name", &self.entry_name)
|
||||
.field("validation_hash", &self.validation_hash)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<KeyParts, Value, IsPlain> StaticAddress<KeyParts, Value, IsPlain> {
|
||||
/// Create a new [`StaticAddress`] using static strings for the pallet and call name.
|
||||
/// This is only expected to be used from codegen.
|
||||
#[doc(hidden)]
|
||||
pub fn new_static(pallet_name: &'static str, entry_name: &'static str, hash: [u8; 32]) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
entry_name: Cow::Borrowed(entry_name),
|
||||
validation_hash: Some(hash),
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new address.
|
||||
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
pallet_name: pallet_name.into().into(),
|
||||
entry_name: entry_name.into().into(),
|
||||
validation_hash: None,
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not validate this storage entry prior to accessing it.
|
||||
pub fn unvalidated(mut self) -> Self {
|
||||
self.validation_hash = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<KeyParts, Value, IsPlain> Address for StaticAddress<KeyParts, Value, IsPlain>
|
||||
where
|
||||
KeyParts: IntoEncodableValues + IntoDecodableValues,
|
||||
Value: DecodeAsType,
|
||||
IsPlain: YesMaybe,
|
||||
{
|
||||
type KeyParts = KeyParts;
|
||||
type Value = Value;
|
||||
type IsPlain = IsPlain;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
}
|
||||
|
||||
fn entry_name(&self) -> &str {
|
||||
&self.entry_name
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.validation_hash
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
|
||||
type KeyParts = Vec<scale_value::Value>;
|
||||
type Value = scale_value::Value;
|
||||
type IsPlain = Maybe;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
fn entry_name(&self) -> &str {
|
||||
self.1.as_ref()
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
|
||||
/// entry *might* be a map and *might* have a default value.
|
||||
pub type DynamicAddress<KeyParts = Vec<scale_value::Value>, Value = scale_value::Value> =
|
||||
StaticAddress<KeyParts, Value, Maybe>;
|
||||
|
||||
/// Construct a new dynamic storage address. You can define the type of the
|
||||
/// storage keys and value yourself here, but have no guarantee that they will
|
||||
/// be correct.
|
||||
pub fn dynamic<KeyParts: IntoEncodableValues, Value: DecodeAsType>(
|
||||
pallet_name: impl Into<String>,
|
||||
entry_name: impl Into<String>,
|
||||
) -> DynamicAddress<KeyParts, Value> {
|
||||
DynamicAddress::<KeyParts, Value>::new(pallet_name.into(), entry_name.into())
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use frame_decode::helpers::IntoEncodableValues;
|
||||
use scale_encode::EncodeAsType;
|
||||
|
||||
/// For a given set of values that can be used as keys for a storage entry,
|
||||
/// this is implemented for any prefixes of that set. ie if the keys `(A,B,C)`
|
||||
/// would access a storage value, then `PrefixOf<(A,B,C)>` is implemented for
|
||||
/// `(A,B)`, `(A,)` and `()`.
|
||||
pub trait PrefixOf<Keys>: IntoEncodableValues {}
|
||||
|
||||
// If T impls PrefixOf<K>, &T impls PrefixOf<K>.
|
||||
impl<K, T: PrefixOf<K>> PrefixOf<K> for &T {}
|
||||
|
||||
// Impls for tuples up to length 6 (storage maps rarely require more than 2 entries
|
||||
// so it's very unlikely we'll ever need to go this deep).
|
||||
impl<A> PrefixOf<(A,)> for () {}
|
||||
|
||||
impl<A, B> PrefixOf<(A, B)> for () {}
|
||||
impl<A, B> PrefixOf<(A, B)> for (A,) where (A,): IntoEncodableValues {}
|
||||
|
||||
impl<A, B, C> PrefixOf<(A, B, C)> for () {}
|
||||
impl<A, B, C> PrefixOf<(A, B, C)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C> PrefixOf<(A, B, C)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for () {}
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
|
||||
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for () {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C, D) where
|
||||
(A, B, C, D): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for () {}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A,) where (A,): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B) where (A, B): IntoEncodableValues {}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C) where
|
||||
(A, B, C): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D) where
|
||||
(A, B, C, D): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D, E) where
|
||||
(A, B, C, D, E): IntoEncodableValues
|
||||
{
|
||||
}
|
||||
|
||||
// Vecs are prefixes of vecs. The length is not statically known and so
|
||||
// these would be given dynamically only, leaving the correct length to the user.
|
||||
impl<T: EncodeAsType> PrefixOf<Vec<T>> for Vec<T> {}
|
||||
|
||||
// We don't use arrays in Subxt for storage entry access, but `IntoEncodableValues`
|
||||
// supports them so let's allow impls which do use them to benefit too.
|
||||
macro_rules! array_impl {
|
||||
($n:literal: $($p:literal)+) => {
|
||||
$(
|
||||
impl <T: EncodeAsType> PrefixOf<[T; $n]> for [T; $p] {}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
array_impl!(1: 0);
|
||||
array_impl!(2: 1 0);
|
||||
array_impl!(3: 2 1 0);
|
||||
array_impl!(4: 3 2 1 0);
|
||||
array_impl!(5: 4 3 2 1 0);
|
||||
array_impl!(6: 5 4 3 2 1 0);
|
||||
|
||||
/// This is much like [`PrefixOf`] except that it also includes `Self` as an allowed type,
|
||||
/// where `Self` must impl [`IntoEncodableValues`] just as every [`PrefixOf<Self>`] does.
|
||||
pub trait EqualOrPrefixOf<K>: IntoEncodableValues {}
|
||||
|
||||
// Tuples
|
||||
macro_rules! tuple_impl_eq {
|
||||
($($t:ident)+) => {
|
||||
// Any T that is a PrefixOf<Keys> impls EqualOrPrefixOf<keys> too
|
||||
impl <$($t,)+ T: PrefixOf<($($t,)+)>> EqualOrPrefixOf<($($t,)+)> for T {}
|
||||
// Keys impls EqualOrPrefixOf<Keys>
|
||||
impl <$($t),+> EqualOrPrefixOf<($($t,)+)> for ($($t,)+) where ($($t,)+): IntoEncodableValues {}
|
||||
// &'a Keys impls EqualOrPrefixOf<Keys>
|
||||
impl <'a, $($t),+> EqualOrPrefixOf<($($t,)+)> for &'a ($($t,)+) where ($($t,)+): IntoEncodableValues {}
|
||||
}
|
||||
}
|
||||
|
||||
tuple_impl_eq!(A);
|
||||
tuple_impl_eq!(A B);
|
||||
tuple_impl_eq!(A B C);
|
||||
tuple_impl_eq!(A B C D);
|
||||
tuple_impl_eq!(A B C D E);
|
||||
tuple_impl_eq!(A B C D E F);
|
||||
|
||||
// Vec
|
||||
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for Vec<T> {}
|
||||
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for &Vec<T> {}
|
||||
|
||||
// Arrays
|
||||
macro_rules! array_impl_eq {
|
||||
($($n:literal)+) => {
|
||||
$(
|
||||
impl <A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for [A; $n] {}
|
||||
impl <'a, A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for &'a [A; $n] {}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, A, T> EqualOrPrefixOf<[A; N]> for T where T: PrefixOf<[A; N]> {}
|
||||
array_impl_eq!(1 2 3 4 5 6);
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
struct Test<Keys: IntoEncodableValues>(core::marker::PhantomData<Keys>);
|
||||
|
||||
impl<Keys: IntoEncodableValues> Test<Keys> {
|
||||
fn new() -> Self {
|
||||
Test(core::marker::PhantomData)
|
||||
}
|
||||
fn accepts_prefix_of<P: PrefixOf<Keys>>(&self, keys: P) {
|
||||
let _encoder = keys.into_encodable_values();
|
||||
}
|
||||
fn accepts_eq_or_prefix_of<P: EqualOrPrefixOf<Keys>>(&self, keys: P) {
|
||||
let _encoder = keys.into_encodable_values();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_of() {
|
||||
// In real life we'd have a struct a bit like this:
|
||||
let t = Test::<(bool, String, u64)>::new();
|
||||
|
||||
// And we'd want to be able to call some method like this:
|
||||
//// This shouldn't work:
|
||||
// t.accepts_prefix_of((true, String::from("hi"), 0));
|
||||
t.accepts_prefix_of(&(true, String::from("hi")));
|
||||
t.accepts_prefix_of((true, String::from("hi")));
|
||||
t.accepts_prefix_of((true,));
|
||||
t.accepts_prefix_of(());
|
||||
|
||||
let t = Test::<[u64; 5]>::new();
|
||||
|
||||
//// This shouldn't work:
|
||||
// t.accepts_prefix_of([0,1,2,3,4]);
|
||||
t.accepts_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_prefix_of([0, 1, 2]);
|
||||
t.accepts_prefix_of([0, 1]);
|
||||
t.accepts_prefix_of([0]);
|
||||
t.accepts_prefix_of([]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq_or_prefix_of() {
|
||||
// In real life we'd have a struct a bit like this:
|
||||
let t = Test::<(bool, String, u64)>::new();
|
||||
|
||||
// And we'd want to be able to call some method like this:
|
||||
t.accepts_eq_or_prefix_of(&(true, String::from("hi"), 0));
|
||||
t.accepts_eq_or_prefix_of(&(true, String::from("hi")));
|
||||
t.accepts_eq_or_prefix_of((true,));
|
||||
t.accepts_eq_or_prefix_of(());
|
||||
|
||||
t.accepts_eq_or_prefix_of((true, String::from("hi"), 0));
|
||||
t.accepts_eq_or_prefix_of((true, String::from("hi")));
|
||||
t.accepts_eq_or_prefix_of((true,));
|
||||
t.accepts_eq_or_prefix_of(());
|
||||
|
||||
let t = Test::<[u64; 5]>::new();
|
||||
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2]);
|
||||
t.accepts_eq_or_prefix_of([0, 1]);
|
||||
t.accepts_eq_or_prefix_of([0]);
|
||||
t.accepts_eq_or_prefix_of([]);
|
||||
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
|
||||
t.accepts_eq_or_prefix_of([0, 1, 2]);
|
||||
t.accepts_eq_or_prefix_of([0, 1]);
|
||||
t.accepts_eq_or_prefix_of([0]);
|
||||
t.accepts_eq_or_prefix_of([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
use crate::backend::BackendExt;
|
||||
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
|
||||
use crate::config::Config;
|
||||
use crate::error::StorageError;
|
||||
use crate::storage::address::Address;
|
||||
use crate::storage::{PrefixOf, StorageKeyValue, StorageValue};
|
||||
use crate::utils::{Maybe, Yes, YesMaybe};
|
||||
use core::marker::PhantomData;
|
||||
use frame_decode::storage::{IntoEncodableValues, StorageInfo, StorageTypeInfo};
|
||||
use futures::StreamExt;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// This represents a single storage entry (be it a plain value or map)
|
||||
/// and the operations that can be performed on it.
|
||||
#[derive(Debug)]
|
||||
pub struct StorageEntry<'atblock, T: Config, Client, Addr, IsPlain> {
|
||||
inner: Arc<StorageEntryInner<'atblock, Addr, Client>>,
|
||||
marker: PhantomData<(T, IsPlain)>,
|
||||
}
|
||||
|
||||
impl<'atblock, T: Config, Client, Addr, IsPlain> Clone
|
||||
for StorageEntry<'atblock, T, Client, Addr, IsPlain>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
marker: self.marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct StorageEntryInner<'atblock, Addr, Client> {
|
||||
address: Addr,
|
||||
info: Arc<StorageInfo<'atblock, u32>>,
|
||||
client: &'atblock Client,
|
||||
}
|
||||
|
||||
impl<'atblock, T, Client, Addr, IsPlain> StorageEntry<'atblock, T, Client, Addr, IsPlain>
|
||||
where
|
||||
T: Config,
|
||||
Addr: Address,
|
||||
Client: OfflineClientAtBlockT<T>,
|
||||
{
|
||||
pub(crate) fn new(client: &'atblock Client, address: Addr) -> Result<Self, StorageError> {
|
||||
let info = client
|
||||
.metadata_ref()
|
||||
.storage_info(address.pallet_name(), address.entry_name())
|
||||
.map_err(|e| StorageError::StorageInfoError(e.into_owned()))?;
|
||||
|
||||
let inner = StorageEntryInner {
|
||||
address,
|
||||
info: Arc::new(info),
|
||||
client,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
inner: Arc::new(inner),
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Name of the pallet containing this storage entry.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.inner.address.pallet_name()
|
||||
}
|
||||
|
||||
/// Name of the storage entry.
|
||||
pub fn entry_name(&self) -> &str {
|
||||
self.inner.address.entry_name()
|
||||
}
|
||||
|
||||
/// Is the storage entry a plain value?
|
||||
pub fn is_plain(&self) -> bool {
|
||||
self.inner.info.keys.is_empty()
|
||||
}
|
||||
|
||||
/// Is the storage entry a map?
|
||||
pub fn is_map(&self) -> bool {
|
||||
!self.is_plain()
|
||||
}
|
||||
|
||||
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
||||
/// is no default value.
|
||||
pub fn default_value(&self) -> Option<StorageValue<'atblock, Addr::Value>> {
|
||||
let info = &self.inner.info;
|
||||
let client = self.inner.client;
|
||||
info.default_value.as_ref().map(|default_value| {
|
||||
StorageValue::new(
|
||||
info.clone(),
|
||||
client.metadata_ref().types(),
|
||||
default_value.to_vec(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create the bytes for a storage key given the key parts.
|
||||
///
|
||||
/// **Warning:** This provides no safety around the provided keys in order that it can be used
|
||||
/// behind the scenes in several places.
|
||||
pub(crate) fn internal_key_bytes<Keys: IntoEncodableValues>(
|
||||
&self,
|
||||
key_parts: Keys,
|
||||
) -> Result<Vec<u8>, StorageError> {
|
||||
let key = frame_decode::storage::encode_storage_key_with_info(
|
||||
self.pallet_name(),
|
||||
self.entry_name(),
|
||||
key_parts,
|
||||
&self.inner.info,
|
||||
self.inner.client.metadata_ref().types(),
|
||||
)
|
||||
.map_err(StorageError::StorageKeyEncodeError)?;
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'atblock, T, Client, Addr, IsPlain> StorageEntry<'atblock, T, Client, Addr, IsPlain>
|
||||
where
|
||||
T: Config,
|
||||
Addr: Address,
|
||||
Client: OnlineClientAtBlockT<T>,
|
||||
{
|
||||
/// Fetch a value, using the default value if none can be found, or returning an error
|
||||
/// if no value exists at this location and there is no default value.
|
||||
///
|
||||
/// **Warning:** This provides no safety around the provided keys in order that it can be used
|
||||
/// behind the scenes in several places.
|
||||
pub(crate) async fn internal_fetch(
|
||||
&self,
|
||||
key_parts: impl IntoEncodableValues,
|
||||
) -> Result<StorageValue<'atblock, Addr::Value>, StorageError> {
|
||||
let value = self
|
||||
.internal_try_fetch(key_parts)
|
||||
.await?
|
||||
.or_else(|| self.default_value())
|
||||
.ok_or(StorageError::NoValueFound)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Fetch a value, returning `None` if no value exists at that location.
|
||||
///
|
||||
/// **Warning:** This provides no safety around the provided keys in order that it can be used
|
||||
/// behind the scenes in several places.
|
||||
pub(crate) async fn internal_try_fetch(
|
||||
&self,
|
||||
key_parts: impl IntoEncodableValues,
|
||||
) -> Result<Option<StorageValue<'atblock, Addr::Value>>, StorageError> {
|
||||
let key = self.internal_key_bytes(key_parts)?;
|
||||
let block_hash = self.inner.client.block_hash();
|
||||
|
||||
let value = self
|
||||
.inner
|
||||
.client
|
||||
.backend()
|
||||
.storage_fetch_value(key, block_hash)
|
||||
.await
|
||||
.map_err(StorageError::CannotFetchValue)?
|
||||
.map(|bytes| {
|
||||
StorageValue::new(
|
||||
self.inner.info.clone(),
|
||||
self.inner.client.metadata_ref().types(),
|
||||
bytes,
|
||||
)
|
||||
})
|
||||
.or_else(|| self.default_value());
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Iterate over the values under the provided key.
|
||||
///
|
||||
/// **Warning:** This provides no safety around the provided keys in order that it can be used
|
||||
/// behind the scenes in several places.
|
||||
pub(crate) async fn internal_iter<KeyParts: PrefixOf<Addr::KeyParts>>(
|
||||
&self,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<
|
||||
impl futures::Stream<Item = Result<StorageKeyValue<'atblock, Addr>, StorageError>>
|
||||
+ use<'atblock, Addr, Client, T, KeyParts, IsPlain>,
|
||||
StorageError,
|
||||
> {
|
||||
let info = self.inner.info.clone();
|
||||
let types = self.inner.client.metadata_ref().types();
|
||||
let key_bytes = self.internal_key_bytes(key_parts)?;
|
||||
let block_hash = self.inner.client.block_hash();
|
||||
|
||||
let stream = self
|
||||
.inner
|
||||
.client
|
||||
.backend()
|
||||
.storage_fetch_descendant_values(key_bytes, block_hash)
|
||||
.await
|
||||
.map_err(StorageError::CannotIterateValues)?
|
||||
.map(move |kv| {
|
||||
let kv = match kv {
|
||||
Ok(kv) => kv,
|
||||
Err(e) => return Err(StorageError::StreamFailure(e)),
|
||||
};
|
||||
Ok(StorageKeyValue::new(
|
||||
info.clone(),
|
||||
types,
|
||||
kv.key.into(),
|
||||
kv.value,
|
||||
))
|
||||
});
|
||||
|
||||
Ok(Box::pin(stream))
|
||||
}
|
||||
}
|
||||
|
||||
// Plain values get a fetch method with no extra arguments.
|
||||
impl<'atblock, T, Client, Addr> StorageEntry<'atblock, T, Client, Addr, Yes>
|
||||
where
|
||||
T: Config,
|
||||
Addr: Address<IsPlain = Yes>,
|
||||
Client: OnlineClientAtBlockT<T>,
|
||||
{
|
||||
/// Fetch the storage value at this location. If no value is found, the default value will be returned
|
||||
/// for this entry if one exists. If no value is found and no default value exists, an error will be returned.
|
||||
pub async fn fetch(&self) -> Result<StorageValue<'atblock, Addr::Value>, StorageError> {
|
||||
self.internal_fetch(()).await
|
||||
}
|
||||
|
||||
/// Fetch the storage value at this location. If no value is found, `None` will be returned.
|
||||
pub async fn try_fetch(
|
||||
&self,
|
||||
) -> Result<Option<StorageValue<'atblock, Addr::Value>>, StorageError> {
|
||||
self.internal_try_fetch(()).await
|
||||
}
|
||||
|
||||
/// This is identical to [`StorageEntry::key_prefix()`] and is the full
|
||||
/// key for this storage entry.
|
||||
pub fn key(&self) -> [u8; 32] {
|
||||
self.key_prefix()
|
||||
}
|
||||
|
||||
/// The keys for plain storage values are always 32 byte hashes.
|
||||
pub fn key_prefix(&self) -> [u8; 32] {
|
||||
frame_decode::storage::encode_storage_key_prefix(self.pallet_name(), self.entry_name())
|
||||
}
|
||||
}
|
||||
|
||||
// When HasDefaultValue = Yes, we expect there to exist a valid default value and will use that
|
||||
// if we fetch an entry and get nothing back.
|
||||
impl<'atblock, T, Client, Addr> StorageEntry<'atblock, T, Client, Addr, Maybe>
|
||||
where
|
||||
T: Config,
|
||||
Addr: Address<IsPlain = Maybe>,
|
||||
Client: OnlineClientAtBlockT<T>,
|
||||
{
|
||||
/// Fetch a storage value within this storage entry.
|
||||
///
|
||||
/// This entry may be a map, and so you must provide the relevant values for each part of the storage
|
||||
/// key that is required in order to point to a single value.
|
||||
///
|
||||
/// If no value is found, the default value will be returned for this entry if one exists. If no value is
|
||||
/// found and no default value exists, an error will be returned.
|
||||
pub async fn fetch(
|
||||
&self,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<StorageValue<'atblock, Addr::Value>, StorageError> {
|
||||
self.internal_fetch(key_parts).await
|
||||
}
|
||||
|
||||
/// Fetch a storage value within this storage entry.
|
||||
///
|
||||
/// This entry may be a map, and so you must provide the relevant values for each part of the storage
|
||||
/// key that is required in order to point to a single value.
|
||||
///
|
||||
/// If no value is found, `None` will be returned.
|
||||
pub async fn try_fetch(
|
||||
&self,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<Option<StorageValue<'atblock, Addr::Value>>, StorageError> {
|
||||
self.internal_try_fetch(key_parts).await
|
||||
}
|
||||
|
||||
/// Iterate over storage values within this storage entry.
|
||||
///
|
||||
/// You may provide any prefix of the values needed to point to a single value. Normally you will
|
||||
/// provide `()` to iterate over _everything_, or `(first_key,)` to iterate over everything underneath
|
||||
/// `first_key` in the map, or `(first_key, second_key)` to iterate over everything underneath `first_key`
|
||||
/// and `second_key` in the map, and so on, up to the actual depth of the map - 1.
|
||||
pub async fn iter<KeyParts: PrefixOf<Addr::KeyParts>>(
|
||||
&self,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<
|
||||
impl futures::Stream<Item = Result<StorageKeyValue<'atblock, Addr>, StorageError>>
|
||||
+ use<'atblock, Addr, Client, T, KeyParts>,
|
||||
StorageError,
|
||||
> {
|
||||
self.internal_iter(key_parts).await
|
||||
}
|
||||
|
||||
/// This returns a full key to a single value in this storage entry.
|
||||
pub fn key(&self, key_parts: Addr::KeyParts) -> Result<Vec<u8>, StorageError> {
|
||||
let num_keys = self.inner.info.keys.len();
|
||||
if key_parts.num_encodable_values() != num_keys {
|
||||
Err(StorageError::WrongNumberOfKeyPartsProvidedForFetching {
|
||||
expected: num_keys,
|
||||
got: key_parts.num_encodable_values(),
|
||||
})
|
||||
} else {
|
||||
self.internal_key_bytes(key_parts)
|
||||
}
|
||||
}
|
||||
|
||||
/// This returns valid keys to iterate over the storage entry at the available levels.
|
||||
pub fn iter_key<KeyParts: PrefixOf<Addr::KeyParts>>(
|
||||
&self,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<Vec<u8>, StorageError> {
|
||||
let num_keys = self.inner.info.keys.len();
|
||||
if Addr::IsPlain::is_yes() {
|
||||
Err(StorageError::CannotIterPlainEntry {
|
||||
pallet_name: self.pallet_name().into(),
|
||||
entry_name: self.entry_name().into(),
|
||||
})
|
||||
} else if key_parts.num_encodable_values() >= num_keys {
|
||||
Err(StorageError::WrongNumberOfKeyPartsProvidedForIterating {
|
||||
max_expected: num_keys - 1,
|
||||
got: key_parts.num_encodable_values(),
|
||||
})
|
||||
} else {
|
||||
self.internal_key_bytes(key_parts)
|
||||
}
|
||||
}
|
||||
|
||||
/// The first 32 bytes of the storage entry key, which points to the entry but not necessarily
|
||||
/// a single storage value (unless the entry is a plain value).
|
||||
pub fn key_prefix(&self) -> [u8; 32] {
|
||||
frame_decode::storage::encode_storage_key_prefix(self.pallet_name(), self.entry_name())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::error::StorageKeyError;
|
||||
use core::marker::PhantomData;
|
||||
use frame_decode::storage::{IntoDecodableValues, StorageInfo, StorageKey as StorageKeyPartInfo};
|
||||
use scale_info::PortableRegistry;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use frame_decode::storage::StorageHasher;
|
||||
|
||||
/// This represents the different parts of a storage key.
|
||||
pub struct StorageKey<'info, KeyParts> {
|
||||
info: Arc<StorageKeyPartInfo<u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Arc<[u8]>,
|
||||
marker: PhantomData<KeyParts>,
|
||||
}
|
||||
|
||||
impl<'info, KeyParts: IntoDecodableValues> StorageKey<'info, KeyParts> {
|
||||
pub(crate) fn new(
|
||||
info: &StorageInfo<'info, u32>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Arc<[u8]>,
|
||||
) -> Result<Self, StorageKeyError> {
|
||||
let cursor = &mut &*bytes;
|
||||
let storage_key_info = frame_decode::storage::decode_storage_key_with_info(
|
||||
cursor, info, types,
|
||||
)
|
||||
.map_err(|e| StorageKeyError::StorageKeyDecodeError {
|
||||
bytes: bytes.to_vec(),
|
||||
error: e,
|
||||
})?;
|
||||
|
||||
if !cursor.is_empty() {
|
||||
return Err(StorageKeyError::LeftoverBytes {
|
||||
bytes: cursor.to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(StorageKey {
|
||||
info: Arc::new(storage_key_info),
|
||||
types,
|
||||
bytes,
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to decode the values contained within this storage key. The target type is
|
||||
/// given by the storage address used to access this entry. To decode into a custom type,
|
||||
/// use [`Self::parts()`] or [`Self::part()`] and decode each part.
|
||||
pub fn decode(&self) -> Result<KeyParts, StorageKeyError> {
|
||||
let values =
|
||||
frame_decode::storage::decode_storage_key_values(&self.bytes, &self.info, self.types)
|
||||
.map_err(StorageKeyError::CannotDecodeValuesInKey)?;
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
/// Iterate over the parts of this storage key. Each part of a storage key corresponds to a
|
||||
/// single value that has been hashed.
|
||||
pub fn parts(&self) -> impl ExactSizeIterator<Item = StorageKeyPart<'info>> {
|
||||
let parts_len = self.info.parts().len();
|
||||
(0..parts_len).map(move |index| StorageKeyPart {
|
||||
index,
|
||||
info: self.info.clone(),
|
||||
types: self.types,
|
||||
bytes: self.bytes.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the part of the storage key at the provided index, or `None` if the index is out of bounds.
|
||||
pub fn part(&self, index: usize) -> Option<StorageKeyPart<'info>> {
|
||||
if index < self.parts().len() {
|
||||
Some(StorageKeyPart {
|
||||
index,
|
||||
info: self.info.clone(),
|
||||
types: self.types,
|
||||
bytes: self.bytes.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents a part of a storage key.
|
||||
pub struct StorageKeyPart<'info> {
|
||||
index: usize,
|
||||
info: Arc<StorageKeyPartInfo<u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Arc<[u8]>,
|
||||
}
|
||||
|
||||
impl<'info> StorageKeyPart<'info> {
|
||||
/// Get the raw bytes for this part of the storage key.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
let part = &self.info[self.index];
|
||||
let hash_range = part.hash_range();
|
||||
let value_range = part.value().map(|v| v.range()).unwrap_or(core::ops::Range {
|
||||
start: hash_range.end,
|
||||
end: hash_range.end,
|
||||
});
|
||||
let combined_range = core::ops::Range {
|
||||
start: hash_range.start,
|
||||
end: value_range.end,
|
||||
};
|
||||
&self.bytes[combined_range]
|
||||
}
|
||||
|
||||
/// Get the hasher that was used to construct this part of the storage key.
|
||||
pub fn hasher(&self) -> StorageHasher {
|
||||
self.info[self.index].hasher()
|
||||
}
|
||||
|
||||
/// For keys that were produced using "concat" or "identity" hashers, the value
|
||||
/// is available as a part of the key hash, allowing us to decode it into anything
|
||||
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
|
||||
/// different hasher, this will return `None`.
|
||||
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
|
||||
let part_info = &self.info[self.index];
|
||||
let Some(value_info) = part_info.value() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let value_bytes = &self.bytes[value_info.range()];
|
||||
let value_ty = *value_info.ty();
|
||||
|
||||
let decoded_key_part = T::decode_as_type(&mut &*value_bytes, value_ty, self.types)
|
||||
.map_err(|e| StorageKeyError::CannotDecodeValueInKey {
|
||||
index: self.index,
|
||||
error: e,
|
||||
})?;
|
||||
|
||||
Ok(Some(decoded_key_part))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::address::Address;
|
||||
use super::storage_key::StorageKey;
|
||||
use super::storage_value::StorageValue;
|
||||
use crate::error::StorageKeyError;
|
||||
use frame_decode::storage::StorageInfo;
|
||||
use scale_info::PortableRegistry;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// This represents a storage key/value pair, which is typically returned from
|
||||
/// iterating over values in some storage map.
|
||||
#[derive(Debug)]
|
||||
pub struct StorageKeyValue<'info, Addr: Address> {
|
||||
key: Arc<[u8]>,
|
||||
// This contains the storage information already:
|
||||
value: StorageValue<'info, Addr::Value>,
|
||||
}
|
||||
|
||||
impl<'info, Addr: Address> StorageKeyValue<'info, Addr> {
|
||||
pub(crate) fn new(
|
||||
info: Arc<StorageInfo<'info, u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
key_bytes: Arc<[u8]>,
|
||||
value_bytes: Vec<u8>,
|
||||
) -> Self {
|
||||
StorageKeyValue {
|
||||
key: key_bytes,
|
||||
value: StorageValue::new(info, types, value_bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw bytes for this storage entry's key.
|
||||
pub fn key_bytes(&self) -> &[u8] {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Decode the key for this storage entry. This gives back a type from which we can
|
||||
/// decode specific parts of the key hash (where applicable).
|
||||
pub fn key(&'_ self) -> Result<StorageKey<'info, Addr::KeyParts>, StorageKeyError> {
|
||||
StorageKey::new(&self.value.info, self.value.types, self.key.clone())
|
||||
}
|
||||
|
||||
/// Return the storage value.
|
||||
pub fn value(&self) -> &StorageValue<'info, Addr::Value> {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::error::StorageValueError;
|
||||
use core::marker::PhantomData;
|
||||
use frame_decode::storage::StorageInfo;
|
||||
use scale_decode::DecodeAsType;
|
||||
use scale_info::PortableRegistry;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// This represents a storage value.
|
||||
#[derive(Debug)]
|
||||
pub struct StorageValue<'info, Value> {
|
||||
pub(crate) info: Arc<StorageInfo<'info, u32>>,
|
||||
pub(crate) types: &'info PortableRegistry,
|
||||
bytes: Vec<u8>,
|
||||
marker: PhantomData<Value>,
|
||||
}
|
||||
|
||||
impl<'info, Value: DecodeAsType> StorageValue<'info, Value> {
|
||||
pub(crate) fn new(
|
||||
info: Arc<StorageInfo<'info, u32>>,
|
||||
types: &'info PortableRegistry,
|
||||
bytes: Vec<u8>,
|
||||
) -> StorageValue<'info, Value> {
|
||||
StorageValue {
|
||||
info,
|
||||
types,
|
||||
bytes,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw bytes for this storage value.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
|
||||
/// Consume this storage value and return the raw bytes.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.bytes.to_vec()
|
||||
}
|
||||
|
||||
/// Decode this storage value into the provided response type.
|
||||
pub fn decode(&self) -> Result<Value, StorageValueError> {
|
||||
self.decode_as::<Value>()
|
||||
}
|
||||
|
||||
/// Decode this storage value into an arbitrary type.
|
||||
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
||||
let cursor = &mut &*self.bytes;
|
||||
|
||||
let value = frame_decode::storage::decode_storage_value_with_info(
|
||||
cursor,
|
||||
&self.info,
|
||||
self.types,
|
||||
T::into_visitor(),
|
||||
)
|
||||
.map_err(StorageValueError::CannotDecode)?;
|
||||
|
||||
if !cursor.is_empty() {
|
||||
return Err(StorageValueError::LeftoverBytes {
|
||||
bytes: cursor.to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -29,21 +29,21 @@ pub use validation_result::{
|
||||
|
||||
/// A client for working with transactions.
|
||||
#[derive(Clone)]
|
||||
pub struct Transactions<T, Client> {
|
||||
pub struct TransactionsClient<T, Client> {
|
||||
client: Client,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> Transactions<T, Client> {
|
||||
impl<T, Client> TransactionsClient<T, Client> {
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Transactions {
|
||||
TransactionsClient {
|
||||
client,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OfflineClientAtBlockT<T>> Transactions<T, Client> {
|
||||
impl<T: Config, Client: OfflineClientAtBlockT<T>> TransactionsClient<T, Client> {
|
||||
/// Run the validation logic against some transaction you'd like to submit. Returns `Ok(())`
|
||||
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
|
||||
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
|
||||
@@ -294,7 +294,7 @@ impl<T: Config, Client: OfflineClientAtBlockT<T>> Transactions<T, Client> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, Client: OnlineClientAtBlockT<T>> Transactions<T, Client> {
|
||||
impl<T: Config, Client: OnlineClientAtBlockT<T>> TransactionsClient<T, Client> {
|
||||
/// Get the account nonce for a given account ID.
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, ExtrinsicError> {
|
||||
account_nonce::get_account_nonce(&self.client, account_id)
|
||||
|
||||
Reference in New Issue
Block a user