mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 09:51:10 +00:00
WIP New storage APIs roughly completed, lots of errors still
This commit is contained in:
@@ -111,11 +111,11 @@ fn generate_storage_entry_fns(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let pallet_name = pallet.name();
|
let pallet_name = pallet.name();
|
||||||
let storage_name = storage_entry.name();
|
let entry_name = storage_entry.name();
|
||||||
let Some(storage_hash) = pallet.storage_hash(storage_name) else {
|
let Some(storage_hash) = pallet.storage_hash(entry_name) else {
|
||||||
return Err(CodegenError::MissingStorageMetadata(
|
return Err(CodegenError::MissingStorageMetadata(
|
||||||
pallet_name.into(),
|
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(
|
#crate_path::storage::address::StaticAddress::new_static(
|
||||||
#pallet_name,
|
#pallet_name,
|
||||||
#storage_name,
|
#entry_name,
|
||||||
#keys,
|
#keys,
|
||||||
[#(#storage_hash,)*]
|
[#(#storage_hash,)*]
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ pub use crate::tx::payload::dynamic as tx;
|
|||||||
pub use crate::constants::address::dynamic as constant;
|
pub use crate::constants::address::dynamic as constant;
|
||||||
|
|
||||||
// Lookup storage values dynamically.
|
// 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.
|
// Execute runtime API function call dynamically.
|
||||||
pub use crate::runtime_api::payload::dynamic as runtime_api_call;
|
pub use crate::runtime_api::payload::dynamic as runtime_api_call;
|
||||||
|
|||||||
+19
-10
@@ -7,7 +7,7 @@
|
|||||||
use alloc::borrow::Cow;
|
use alloc::borrow::Cow;
|
||||||
use frame_decode::storage::IntoEncodableValues;
|
use frame_decode::storage::IntoEncodableValues;
|
||||||
use scale_decode::DecodeAsType;
|
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
|
/// 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
|
/// 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.
|
/// Does the address have a default value defined for it.
|
||||||
/// Set to [`crate::utils::Yes`] to enable APIs which require one,
|
/// Set to [`crate::utils::Yes`] to enable APIs which require one,
|
||||||
/// or [`crate::utils::Maybe`] to enable APIs which allow 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)?
|
/// 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,
|
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
|
||||||
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
|
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
|
||||||
@@ -63,6 +63,19 @@ impl<KeyParts, Value, HasDefaultValue, IsMap> StaticAddress<KeyParts, Value, Has
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new address.
|
||||||
|
pub fn new(
|
||||||
|
pallet_name: impl Into<Cow<'static, str>>,
|
||||||
|
entry_name: impl Into<Cow<'static, str>>,
|
||||||
|
) -> 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.
|
/// Do not validate this storage entry prior to accessing it.
|
||||||
pub fn unvalidated(mut self) -> Self {
|
pub fn unvalidated(mut self) -> Self {
|
||||||
self.validation_hash = None;
|
self.validation_hash = None;
|
||||||
@@ -75,6 +88,7 @@ impl<KeyParts, Value, HasDefaultValue, IsMap> Address
|
|||||||
where
|
where
|
||||||
KeyParts: IntoEncodableValues,
|
KeyParts: IntoEncodableValues,
|
||||||
Value: DecodeAsType,
|
Value: DecodeAsType,
|
||||||
|
HasDefaultValue: YesNoMaybe,
|
||||||
{
|
{
|
||||||
type KeyParts = KeyParts;
|
type KeyParts = KeyParts;
|
||||||
type Value = Value;
|
type Value = Value;
|
||||||
@@ -95,8 +109,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
|
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
|
||||||
/// entry could be a map and could have a default value.
|
/// entry *might* be a map and *might* have a default value.
|
||||||
pub type DynamicAddress<KeyParts, Value> = StaticAddress<KeyParts, Value, Maybe, Maybe>;
|
pub type DynamicAddress<KeyParts = Vec<scale_value::Value>, Value = scale_value::Value> = StaticAddress<KeyParts, Value, Maybe, Maybe>;
|
||||||
|
|
||||||
/// Construct a new dynamic storage address. You can define the type of the
|
/// 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
|
/// storage keys and value yourself here, but have no guarantee that they will
|
||||||
@@ -105,11 +119,6 @@ pub fn dynamic<KeyParts: IntoEncodableValues, Value: DecodeAsType>(
|
|||||||
pallet_name: impl Into<Cow<'static, str>>,
|
pallet_name: impl Into<Cow<'static, str>>,
|
||||||
entry_name: impl Into<Cow<'static, str>>,
|
entry_name: impl Into<Cow<'static, str>>,
|
||||||
) -> DynamicAddress<KeyParts, Value> {
|
) -> DynamicAddress<KeyParts, Value> {
|
||||||
DynamicAddress::<KeyParts, Value> {
|
DynamicAddress::<KeyParts, Value>::new(pallet_name, entry_name)
|
||||||
pallet_name: pallet_name.into(),
|
|
||||||
entry_name: entry_name.into(),
|
|
||||||
validation_hash: None,
|
|
||||||
marker: core::marker::PhantomData
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ mod multi_signature;
|
|||||||
mod static_type;
|
mod static_type;
|
||||||
mod unchecked_extrinsic;
|
mod unchecked_extrinsic;
|
||||||
mod wrapper_opaque;
|
mod wrapper_opaque;
|
||||||
|
mod yesnomaybe;
|
||||||
|
|
||||||
use alloc::borrow::ToOwned;
|
use alloc::borrow::ToOwned;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
@@ -21,6 +22,7 @@ use alloc::vec::Vec;
|
|||||||
use codec::{Compact, Decode, Encode};
|
use codec::{Compact, Decode, Encode};
|
||||||
use derive_where::derive_where;
|
use derive_where::derive_where;
|
||||||
|
|
||||||
|
pub use yesnomaybe::{Yes, No, Maybe, YesNoMaybe};
|
||||||
pub use account_id::AccountId32;
|
pub use account_id::AccountId32;
|
||||||
pub use account_id20::AccountId20;
|
pub use account_id20::AccountId20;
|
||||||
pub use era::Era;
|
pub use era::Era;
|
||||||
@@ -73,13 +75,6 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
|
|||||||
/// as `BTreeMap` which allows us to easily swap the two during codegen.
|
/// as `BTreeMap` which allows us to easily swap the two during codegen.
|
||||||
pub type KeyedVec<K, V> = Vec<(K, V)>;
|
pub type KeyedVec<K, V> = 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.
|
/// A quick helper to encode some bytes to hex.
|
||||||
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
|
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
|
||||||
format!("0x{}", hex::encode(bytes.as_ref()))
|
format!("0x{}", hex::encode(bytes.as_ref()))
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ async fn main() -> Result<(), Error> {
|
|||||||
println!(
|
println!(
|
||||||
" {}: {}",
|
" {}: {}",
|
||||||
field.name(),
|
field.name(),
|
||||||
field.decode::<scale_value::Value>().unwrap()
|
field.decode_as::<scale_value::Value>().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ async fn main() -> Result<(), Error> {
|
|||||||
extrinsic
|
extrinsic
|
||||||
.call()
|
.call()
|
||||||
.fields()
|
.fields()
|
||||||
.decode::<scale_value::Composite<_>>()
|
.decode_as::<scale_value::Composite<_>>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -66,14 +66,14 @@ async fn main() -> Result<(), Error> {
|
|||||||
println!(
|
println!(
|
||||||
" {}: {}",
|
" {}: {}",
|
||||||
extension.name(),
|
extension.name(),
|
||||||
extension.decode::<scale_value::Value>().unwrap()
|
extension.decode_as::<scale_value::Value>().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or all of them at once:
|
// Or all of them at once:
|
||||||
println!(
|
println!(
|
||||||
" All: {}",
|
" All: {}",
|
||||||
extensions.decode::<scale_value::Composite<_>>().unwrap()
|
extensions.decode_as::<scale_value::Composite<_>>().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ async fn main() -> Result<(), Error> {
|
|||||||
.into_map()?;
|
.into_map()?;
|
||||||
|
|
||||||
// We can see the default value for this entry at this block, if one exists.
|
// We can see the default value for this entry at this block, if one exists.
|
||||||
if let Some(default_value) = account_balances.default() {
|
if let Some(default_value) = account_balances.default_value() {
|
||||||
let default_balance_info = default_value.decode::<scale_value::Value>()?;
|
let default_balance_info = default_value.decode_as::<scale_value::Value>()?;
|
||||||
println!(" Default balance info: {default_balance_info}");
|
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? {
|
if let Some(entry) = account_balances.fetch((account_id,)).await? {
|
||||||
// We can decode the value into our generic `scale_value::Value` type, which can
|
// We can decode the value into our generic `scale_value::Value` type, which can
|
||||||
// represent any SCALE-encoded value, like so:
|
// represent any SCALE-encoded value, like so:
|
||||||
let _balance_info = entry.decode::<scale_value::Value>()?;
|
let _balance_info = entry.decode_as::<scale_value::Value>()?;
|
||||||
|
|
||||||
// Or, if we know what shape to expect, we can decode the parts of the value that we care
|
// 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
|
// 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,
|
misc_frozen: u128,
|
||||||
fee_frozen: u128,
|
fee_frozen: u128,
|
||||||
}
|
}
|
||||||
let balance_info = entry.decode::<BalanceInfo>()?;
|
let balance_info = entry.decode_as::<BalanceInfo>()?;
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
" Single balance info from {account_id_hex} => free: {} reserved: {} misc_frozen: {} fee_frozen: {}",
|
" 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);
|
let mut all_balances = account_balances.iter(()).await?.take(10);
|
||||||
while let Some(entry) = all_balances.next().await {
|
while let Some(entry) = all_balances.next().await {
|
||||||
let entry = entry?;
|
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
|
// 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:
|
// with a map which has one value, an account ID, so we just decode that part:
|
||||||
let account_id = key
|
let account_id = key
|
||||||
.part(0)
|
.part(0)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.decode::<[u8; 32]>()?
|
.decode_as::<[u8; 32]>()?
|
||||||
.expect("We expect this key to decode into a 32 byte AccountId");
|
.expect("We expect this key to decode into a 32 byte AccountId");
|
||||||
|
|
||||||
let account_id_hex = hex::encode(account_id);
|
let account_id_hex = hex::encode(account_id);
|
||||||
|
|
||||||
// Decode these values into our generic scale_value::Value type. Less efficient than
|
// 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.
|
// defining a static type as above, but easier for the sake of the example.
|
||||||
let balance_info = entry.decode_value::<scale_value::Value>()?;
|
let balance_info = entry.value().decode_as::<scale_value::Value>()?;
|
||||||
println!(" {account_id_hex} => {balance_info}");
|
println!(" {account_id_hex} => {balance_info}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,22 +214,22 @@ pub enum ExtrinsicCallError {
|
|||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[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 {
|
pub struct StorageEntryIsNotAMap {
|
||||||
/// The pallet containing the storage entry that was not found.
|
/// The pallet containing the storage entry that was not found.
|
||||||
pub pallet_name: String,
|
pub pallet_name: String,
|
||||||
/// The storage entry that was not found.
|
/// The storage entry that was not found.
|
||||||
pub storage_name: String,
|
pub entry_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[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 {
|
pub struct StorageEntryIsNotAPlainValue {
|
||||||
/// The pallet containing the storage entry that was not found.
|
/// The pallet containing the storage entry that was not found.
|
||||||
pub pallet_name: String,
|
pub pallet_name: String,
|
||||||
/// The storage entry that was not found.
|
/// The storage entry that was not found.
|
||||||
pub storage_name: String,
|
pub entry_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to decode the fields into the given type.
|
/// Attempt to decode the fields into the given type.
|
||||||
pub fn decode<T: scale_decode::DecodeAsFields>(&self) -> Result<T, ExtrinsicCallError> {
|
pub fn decode_as<T: scale_decode::DecodeAsFields>(&self) -> Result<T, ExtrinsicCallError> {
|
||||||
with_info!(&self.info => {
|
with_info!(&self.info => {
|
||||||
let cursor = &mut self.bytes();
|
let cursor = &mut self.bytes();
|
||||||
let mut fields = &mut info.info.call_data().map(|named_arg| {
|
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.
|
/// Attempt to decode the value of this field into the given type.
|
||||||
pub fn decode<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
|
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
|
||||||
with_call_field_info!(&self.info => {
|
with_call_field_info!(&self.info => {
|
||||||
let cursor = &mut &*self.field_bytes;
|
let cursor = &mut &*self.field_bytes;
|
||||||
let decoded = T::decode_as_type(cursor, info.info.ty().clone(), info.resolver)
|
let decoded = T::decode_as_type(cursor, info.info.ty().clone(), info.resolver)
|
||||||
|
|||||||
@@ -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
|
/// 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.
|
/// extension and the field value is the decoded extension.
|
||||||
pub fn decode<T: scale_decode::DecodeAsFields>(
|
pub fn decode_as<T: scale_decode::DecodeAsFields>(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<T, ExtrinsicTransactionExtensionError> {
|
) -> Result<T, ExtrinsicTransactionExtensionError> {
|
||||||
with_extensions_info!(&self.info => {
|
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`.
|
/// Decode the bytes for this transaction extension into a type that implements `scale_decode::DecodeAsType`.
|
||||||
pub fn decode<T: scale_decode::DecodeAsType>(
|
pub fn decode_as<T: scale_decode::DecodeAsType>(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<T, ExtrinsicTransactionExtensionError> {
|
) -> Result<T, ExtrinsicTransactionExtensionError> {
|
||||||
with_extension_info!(&self.info => {
|
with_extension_info!(&self.info => {
|
||||||
|
|||||||
+46
-52
@@ -42,14 +42,14 @@ where
|
|||||||
pub fn entry(
|
pub fn entry(
|
||||||
&self,
|
&self,
|
||||||
pallet_name: impl Into<String>,
|
pallet_name: impl Into<String>,
|
||||||
storage_name: impl Into<String>,
|
entry_name: impl Into<String>,
|
||||||
) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
|
) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
|
||||||
let pallet_name = pallet_name.into();
|
let pallet_name = pallet_name.into();
|
||||||
let storage_name = storage_name.into();
|
let entry_name = entry_name.into();
|
||||||
|
|
||||||
let storage_info = AnyStorageInfo::new(
|
let storage_info = AnyStorageInfo::new(
|
||||||
&pallet_name,
|
&pallet_name,
|
||||||
&storage_name,
|
&entry_name,
|
||||||
self.client.metadata(),
|
self.client.metadata(),
|
||||||
self.client.legacy_types(),
|
self.client.legacy_types(),
|
||||||
)?;
|
)?;
|
||||||
@@ -58,7 +58,7 @@ where
|
|||||||
Ok(StorageEntryClient::Map(StorageEntryMapClient {
|
Ok(StorageEntryClient::Map(StorageEntryMapClient {
|
||||||
client: self.client,
|
client: self.client,
|
||||||
pallet_name,
|
pallet_name,
|
||||||
storage_name,
|
entry_name,
|
||||||
info: storage_info,
|
info: storage_info,
|
||||||
marker: std::marker::PhantomData,
|
marker: std::marker::PhantomData,
|
||||||
}))
|
}))
|
||||||
@@ -66,7 +66,7 @@ where
|
|||||||
Ok(StorageEntryClient::Plain(StorageEntryPlainClient {
|
Ok(StorageEntryClient::Plain(StorageEntryPlainClient {
|
||||||
client: self.client,
|
client: self.client,
|
||||||
pallet_name,
|
pallet_name,
|
||||||
storage_name,
|
entry_name,
|
||||||
info: storage_info,
|
info: storage_info,
|
||||||
marker: std::marker::PhantomData,
|
marker: std::marker::PhantomData,
|
||||||
}))
|
}))
|
||||||
@@ -105,7 +105,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The storage entry name.
|
/// The storage entry name.
|
||||||
pub fn storage_name(&self) -> &str {
|
pub fn entry_name(&self) -> &str {
|
||||||
&self.entry.storage_entry
|
&self.entry.storage_entry
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,10 +143,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the storage entry name.
|
/// Get the storage entry name.
|
||||||
pub fn storage_name(&self) -> &str {
|
pub fn entry_name(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
StorageEntryClient::Plain(client) => &client.storage_name,
|
StorageEntryClient::Plain(client) => &client.entry_name,
|
||||||
StorageEntryClient::Map(client) => &client.storage_name,
|
StorageEntryClient::Map(client) => &client.entry_name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ where
|
|||||||
StorageEntryClient::Plain(client) => Ok(client),
|
StorageEntryClient::Plain(client) => Ok(client),
|
||||||
StorageEntryClient::Map(_) => Err(StorageEntryIsNotAPlainValue {
|
StorageEntryClient::Map(_) => Err(StorageEntryIsNotAPlainValue {
|
||||||
pallet_name: self.pallet_name().into(),
|
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 {
|
match self {
|
||||||
StorageEntryClient::Plain(_) => Err(StorageEntryIsNotAMap {
|
StorageEntryClient::Plain(_) => Err(StorageEntryIsNotAMap {
|
||||||
pallet_name: self.pallet_name().into(),
|
pallet_name: self.pallet_name().into(),
|
||||||
storage_name: self.storage_name().into(),
|
entry_name: self.entry_name().into(),
|
||||||
}),
|
}),
|
||||||
StorageEntryClient::Map(client) => Ok(client),
|
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<StorageValue<'_, 'atblock>> {
|
||||||
|
match self {
|
||||||
|
StorageEntryClient::Plain(client) => client.default_value(),
|
||||||
|
StorageEntryClient::Map(client) => client.default_value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A client for working with a plain storage entry.
|
/// A client for working with a plain storage entry.
|
||||||
pub struct StorageEntryPlainClient<'atblock, Client, T> {
|
pub struct StorageEntryPlainClient<'atblock, Client, T> {
|
||||||
client: &'atblock Client,
|
client: &'atblock Client,
|
||||||
pallet_name: String,
|
pallet_name: String,
|
||||||
storage_name: String,
|
entry_name: String,
|
||||||
info: AnyStorageInfo<'atblock>,
|
info: AnyStorageInfo<'atblock>,
|
||||||
marker: std::marker::PhantomData<T>,
|
marker: std::marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
@@ -207,13 +216,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the storage entry name.
|
/// Get the storage entry name.
|
||||||
pub fn storage_name(&self) -> &str {
|
pub fn entry_name(&self) -> &str {
|
||||||
&self.storage_name
|
&self.entry_name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
||||||
/// is no default value.
|
/// is no default value.
|
||||||
pub fn default(&self) -> Option<StorageValue<'_, 'atblock>> {
|
pub fn default_value(&self) -> Option<StorageValue<'_, 'atblock>> {
|
||||||
with_info!(info = &self.info => {
|
with_info!(info = &self.info => {
|
||||||
info.info.default_value.as_ref().map(|default_value| {
|
info.info.default_value.as_ref().map(|default_value| {
|
||||||
StorageValue::new(&self.info, default_value.clone())
|
StorageValue::new(&self.info, default_value.clone())
|
||||||
@@ -227,30 +236,24 @@ where
|
|||||||
T: Config + 'atblock,
|
T: Config + 'atblock,
|
||||||
Client: OnlineClientAtBlockT<'atblock, T>,
|
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<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
pub async fn fetch(&self) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
||||||
let key_bytes = self.key();
|
let key_bytes = self.key();
|
||||||
fetch(self.client, &key_bytes)
|
let value = fetch(self.client, &key_bytes)
|
||||||
.await
|
.await?
|
||||||
.map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))))
|
.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
|
Ok(value)
|
||||||
/// value for the storage entry if one exists and the entry does not exist.
|
|
||||||
pub async fn fetch_or_default(
|
|
||||||
&self,
|
|
||||||
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
|
||||||
self.fetch()
|
|
||||||
.await
|
|
||||||
.map(|option_val| option_val.or_else(|| self.default()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The key for this storage entry.
|
/// The key for this storage entry.
|
||||||
pub fn key(&self) -> [u8; 32] {
|
pub fn key(&self) -> [u8; 32] {
|
||||||
let pallet_name = &*self.pallet_name;
|
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> {
|
pub struct StorageEntryMapClient<'atblock, Client, T> {
|
||||||
client: &'atblock Client,
|
client: &'atblock Client,
|
||||||
pallet_name: String,
|
pallet_name: String,
|
||||||
storage_name: String,
|
entry_name: String,
|
||||||
info: AnyStorageInfo<'atblock>,
|
info: AnyStorageInfo<'atblock>,
|
||||||
marker: std::marker::PhantomData<T>,
|
marker: std::marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
@@ -274,13 +277,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the storage entry name.
|
/// Get the storage entry name.
|
||||||
pub fn storage_name(&self) -> &str {
|
pub fn entry_name(&self) -> &str {
|
||||||
&self.storage_name
|
&self.entry_name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
||||||
/// is no default value.
|
/// is no default value.
|
||||||
pub fn default(&self) -> Option<StorageValue<'_, 'atblock>> {
|
pub fn default_value(&self) -> Option<StorageValue<'_, 'atblock>> {
|
||||||
with_info!(info = &self.info => {
|
with_info!(info = &self.info => {
|
||||||
info.info.default_value.as_ref().map(|default_value| {
|
info.info.default_value.as_ref().map(|default_value| {
|
||||||
StorageValue::new(&self.info, default_value.clone())
|
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
|
/// 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
|
/// 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<Keys: IntoEncodableValues>(
|
pub async fn fetch<Keys: IntoEncodableValues>(
|
||||||
&self,
|
&self,
|
||||||
keys: Keys,
|
keys: Keys,
|
||||||
@@ -313,20 +317,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let key_bytes = self.key(keys)?;
|
let key_bytes = self.key(keys)?;
|
||||||
fetch(self.client, &key_bytes)
|
let value = fetch(self.client, &key_bytes)
|
||||||
.await
|
.await?
|
||||||
.map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))))
|
.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
|
Ok(value)
|
||||||
/// value for the storage entry if one exists and the entry was not found.
|
|
||||||
pub async fn fetch_or_default<Keys: IntoEncodableValues>(
|
|
||||||
&self,
|
|
||||||
keys: Keys,
|
|
||||||
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
|
|
||||||
self.fetch(keys)
|
|
||||||
.await
|
|
||||||
.map(|option_val| option_val.or_else(|| self.default()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the values underneath the provided keys.
|
/// 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.
|
// the key bytes and some metadata about them. Or maybe just fetch_raw and iter_raw.
|
||||||
fn key<Keys: IntoEncodableValues>(&self, keys: Keys) -> Result<Vec<u8>, StorageError> {
|
fn key<Keys: IntoEncodableValues>(&self, keys: Keys) -> Result<Vec<u8>, StorageError> {
|
||||||
with_info!(info = &self.info => {
|
with_info!(info = &self.info => {
|
||||||
let mut key_bytes = Vec::new();
|
let key_bytes = frame_decode::storage::encode_storage_key_with_info(
|
||||||
frame_decode::storage::encode_storage_key_with_info_to(
|
|
||||||
&self.pallet_name,
|
&self.pallet_name,
|
||||||
&self.storage_name,
|
&self.entry_name,
|
||||||
keys,
|
keys,
|
||||||
&info.info,
|
&info.info,
|
||||||
info.resolver,
|
info.resolver,
|
||||||
&mut key_bytes,
|
|
||||||
).map_err(|e| StorageError::KeyEncodeError { reason: e })?;
|
).map_err(|e| StorageError::KeyEncodeError { reason: e })?;
|
||||||
Ok(key_bytes)
|
Ok(key_bytes)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use super::storage_info::AnyStorageInfo;
|
use super::storage_info::AnyStorageInfo;
|
||||||
use super::storage_key::StorageKey;
|
use super::storage_key::StorageKey;
|
||||||
use super::storage_value::StorageValue;
|
use super::storage_value::StorageValue;
|
||||||
use crate::error::{StorageKeyError, StorageValueError};
|
use crate::error::StorageKeyError;
|
||||||
use scale_decode::DecodeAsType;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// This represents a storage entry, which is a key-value pair in the storage.
|
/// 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
|
&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.
|
/// Consume this storage entry and return the raw bytes for the key and value.
|
||||||
pub fn into_key_and_value_bytes(self) -> (Vec<u8>, Vec<u8>) {
|
pub fn into_key_and_value_bytes(self) -> (Vec<u8>, Vec<u8>) {
|
||||||
(self.key, self.value.into_bytes())
|
(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 the key for this storage entry. This gives back a type from which we can
|
||||||
/// decode specific parts of the key hash (where applicable).
|
/// decode specific parts of the key hash (where applicable).
|
||||||
pub fn decode_key(&'_ self) -> Result<StorageKey<'_, 'atblock>, StorageKeyError> {
|
pub fn key(&'_ self) -> Result<StorageKey<'_, 'atblock>, StorageKeyError> {
|
||||||
StorageKey::new(self.value.info, &self.key)
|
StorageKey::new(self.value.info, &self.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode this storage value.
|
/// Return the storage value.
|
||||||
pub fn decode_value<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
pub fn value(&self) -> &StorageValue<'entry, 'atblock> {
|
||||||
self.value.decode::<T>()
|
&self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ impl<'entry, 'atblock> StorageKey<'entry, 'atblock> {
|
|||||||
/// Attempt to decode the values contained within this storage key to the `Target` type
|
/// 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`]
|
/// 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.
|
/// and correspond to each of the key types present, in order.
|
||||||
pub fn decode<Target: IntoDecodableValues>(&self) -> Result<Target,StorageKeyError> {
|
pub fn decode_as<Target: IntoDecodableValues>(&self) -> Result<Target,StorageKeyError> {
|
||||||
with_key_info!(info = &self.info => {
|
with_key_info!(info = &self.info => {
|
||||||
let values = frame_decode::storage::decode_storage_key_values(
|
let values = frame_decode::storage::decode_storage_key_values(
|
||||||
self.bytes,
|
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
|
/// 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
|
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
|
||||||
/// different hasher, this will return `None`.
|
/// different hasher, this will return `None`.
|
||||||
pub fn decode<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
|
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
|
||||||
with_key_info!(info = &self.info => {
|
with_key_info!(info = &self.info => {
|
||||||
let part_info = &info.info[self.index];
|
let part_info = &info.info[self.index];
|
||||||
let Some(value_info) = part_info.value() else {
|
let Some(value_info) = part_info.value() else {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ impl<'entry, 'atblock> StorageValue<'entry, 'atblock> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decode this storage value.
|
/// Decode this storage value.
|
||||||
pub fn decode<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
|
||||||
with_info!(info = &self.info => {
|
with_info!(info = &self.info => {
|
||||||
let cursor = &mut &*self.bytes;
|
let cursor = &mut &*self.bytes;
|
||||||
|
|
||||||
|
|||||||
@@ -74,10 +74,10 @@ fn bench_get_storage_hash(c: &mut Criterion) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for storage in storage_entries.entries() {
|
for storage in storage_entries.entries() {
|
||||||
let storage_name = storage.name();
|
let entry_name = storage.name();
|
||||||
let bench_name = format!("{pallet_name}/{storage_name}");
|
let bench_name = format!("{pallet_name}/{entry_name}");
|
||||||
group.bench_function(&bench_name, |b| {
|
group.bench_function(&bench_name, |b| {
|
||||||
b.iter(|| pallet.storage_hash(storage_name))
|
b.iter(|| pallet.storage_hash(entry_name))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,25 +546,34 @@ async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
|
|||||||
Err(err) => return Some(Err(err)),
|
Err(err) => return Some(Err(err)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let key: Vec<scale_value::Value> = vec![];
|
let addr = crate::dynamic::storage::<(), scale_value::Value>("System", "LastRuntimeUpgrade");
|
||||||
let addr = crate::dynamic::storage("System", "LastRuntimeUpgrade", key);
|
|
||||||
|
|
||||||
let chunk = match client.storage().at(block_ref.hash()).fetch(&addr).await {
|
let client_at = client.storage().at(block_ref.hash());
|
||||||
Ok(Some(v)) => v,
|
let Ok(client) = client_at.entry(addr) else {
|
||||||
Ok(None) => {
|
// The storage `system::lastRuntimeUpgrade` should always exist.
|
||||||
// The storage `system::lastRuntimeUpgrade` should always exist.
|
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
|
||||||
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
|
return Some(Err(Error::Other(
|
||||||
unreachable!("The storage item `system::lastRuntimeUpgrade` should always exist")
|
"The storage item `system::lastRuntimeUpgrade` should always exist (1)".to_string(),
|
||||||
}
|
)));
|
||||||
Err(e) => return Some(Err(e)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let scale_val = match chunk.to_value() {
|
let client = client
|
||||||
Ok(v) => v,
|
.into_plain()
|
||||||
Err(e) => return Some(Err(e.into())),
|
.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::<scale_value::Value>()
|
||||||
|
.expect("Should be able to decode anything into scale_value::Value");
|
||||||
|
|
||||||
|
let Some(Ok(spec_version)) = value
|
||||||
.at("spec_version")
|
.at("spec_version")
|
||||||
.and_then(|v| v.as_u128())
|
.and_then(|v| v.as_u128())
|
||||||
.map(u32::try_from)
|
.map(u32::try_from)
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ pub mod metadata {
|
|||||||
/// Submit dynamic transactions.
|
/// Submit dynamic transactions.
|
||||||
pub mod dynamic {
|
pub mod dynamic {
|
||||||
pub use subxt_core::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,
|
view_function_call,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
mod storage_client;
|
mod storage_client;
|
||||||
mod storage_client_at;
|
mod storage_client_at;
|
||||||
|
mod storage_value;
|
||||||
|
mod storage_entry;
|
||||||
|
mod storage_key;
|
||||||
|
|
||||||
pub use storage_client::StorageClient;
|
pub use storage_client::StorageClient;
|
||||||
pub use storage_client_at::StorageClientAt;
|
pub use storage_client_at::StorageClientAt;
|
||||||
|
|||||||
@@ -4,34 +4,52 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{BackendExt, BlockRef},
|
backend::{BackendExt, BlockRef},
|
||||||
client::OnlineClientT,
|
client::{OfflineClientT, OnlineClientT},
|
||||||
config::{Config, HashFor},
|
config::{Config, HashFor},
|
||||||
error::{Error, MetadataError, StorageAddressError},
|
error::{Error, MetadataError, StorageAddressError},
|
||||||
metadata::DecodeWithMetadata,
|
metadata::DecodeWithMetadata,
|
||||||
|
storage::storage_value::StorageValue,
|
||||||
};
|
};
|
||||||
use codec::Decode;
|
use codec::Decode;
|
||||||
use derive_where::derive_where;
|
use derive_where::derive_where;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use scale_info::PortableRegistry;
|
||||||
use std::{future::Future, marker::PhantomData};
|
use std::{future::Future, marker::PhantomData};
|
||||||
use subxt_core::storage::address::Address;
|
use subxt_core::storage::{address::Address, PrefixOf};
|
||||||
use subxt_core::utils::{Maybe, Yes, No};
|
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.
|
/// This is returned from a couple of storage functions.
|
||||||
pub use crate::backend::StreamOfResults;
|
pub use crate::backend::StreamOf;
|
||||||
|
|
||||||
/// Query the runtime storage.
|
/// Query the runtime storage.
|
||||||
#[derive_where(Clone; Client)]
|
#[derive_where(Clone; Client)]
|
||||||
pub struct StorageClientAt<T: Config, Client> {
|
pub struct StorageClientAt<T: Config, Client> {
|
||||||
client: Client,
|
client: Client,
|
||||||
|
metadata: Metadata,
|
||||||
block_ref: BlockRef<HashFor<T>>,
|
block_ref: BlockRef<HashFor<T>>,
|
||||||
_marker: PhantomData<T>,
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config, Client> StorageClientAt<T, Client> {
|
impl<T, Client> StorageClientAt<T, Client>
|
||||||
/// Create a new [`Storage`]
|
where
|
||||||
|
T: Config,
|
||||||
|
Client: OfflineClientT<T>
|
||||||
|
{
|
||||||
|
/// Create a new [`StorageClientAt`].
|
||||||
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> Self {
|
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> 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 {
|
Self {
|
||||||
client,
|
client,
|
||||||
|
metadata,
|
||||||
block_ref,
|
block_ref,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
@@ -41,22 +59,25 @@ impl<T: Config, Client> StorageClientAt<T, Client> {
|
|||||||
impl<T, Client> StorageClientAt<T, Client>
|
impl<T, Client> StorageClientAt<T, Client>
|
||||||
where
|
where
|
||||||
T: Config,
|
T: Config,
|
||||||
Client: OnlineClientT<T>,
|
Client: OfflineClientT<T>,
|
||||||
{
|
{
|
||||||
pub fn entry<Addr: Address>(&self, address: Addr) -> Result<StorageEntryClient<T, Client, Addr, Addr::IsMap>, Error> {
|
pub fn entry<Addr: Address>(&'_ self, address: Addr) -> Result<StorageEntryClient<'_, T, Client, Addr, Addr::IsMap>, Error> {
|
||||||
subxt_core::storage::validate(&address, &self.client.metadata())?;
|
subxt_core::storage::validate(&address, &self.client.metadata())?;
|
||||||
|
|
||||||
use frame_decode::storage::StorageTypeInfo;
|
use frame_decode::storage::StorageTypeInfo;
|
||||||
let storage_info = self
|
let types = self.metadata.types();
|
||||||
|
let info = self
|
||||||
.client
|
.client
|
||||||
.metadata()
|
.metadata()
|
||||||
.storage_info(address.pallet_name(), address.entry_name())?;
|
.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 {
|
StorageEntryClientValue::Plain(StorageEntryPlainClient {
|
||||||
client: self.client.clone(),
|
client: self.client.clone(),
|
||||||
block_ref: self.block_ref.clone(),
|
block_ref: self.block_ref.clone(),
|
||||||
address,
|
address,
|
||||||
|
info,
|
||||||
|
types,
|
||||||
_marker: core::marker::PhantomData
|
_marker: core::marker::PhantomData
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -64,6 +85,8 @@ where
|
|||||||
client: self.client.clone(),
|
client: self.client.clone(),
|
||||||
block_ref: self.block_ref.clone(),
|
block_ref: self.block_ref.clone(),
|
||||||
address,
|
address,
|
||||||
|
info,
|
||||||
|
types,
|
||||||
_marker: core::marker::PhantomData
|
_marker: core::marker::PhantomData
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -75,28 +98,49 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StorageEntryClient<T: Config, Client, Addr, IsMap> {
|
/// This represents a single storage entry (be it a plain value or map)
|
||||||
value: StorageEntryClientValue<T, Client, Addr>,
|
/// 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<IsMap>,
|
marker: core::marker::PhantomData<IsMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StorageEntryClientValue<T: Config, Client, Addr> {
|
enum StorageEntryClientValue<'atblock, T: Config, Client, Addr: Address> {
|
||||||
Plain(StorageEntryPlainClient<T, Client, Addr>),
|
Plain(StorageEntryPlainClient<'atblock, T, Client, Addr, Addr::HasDefaultValue>),
|
||||||
Map(StorageEntryMapClient<T, Client, Addr>),
|
Map(StorageEntryMapClient<'atblock, T, Client, Addr, Addr::HasDefaultValue>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <T: Config, Client, Addr: Address, IsMap> StorageEntryClient<T, Client, Addr, IsMap> {
|
impl <'atblock, T: Config, Client, Addr: Address, IsMap> StorageEntryClient<'atblock, T, Client, Addr, IsMap> {
|
||||||
pub fn pallet_name(&self) -> &str {
|
pub fn pallet_name(&self) -> &str {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
StorageEntryClientValue::Plain(client) => client.address.pallet_name(),
|
StorageEntryClientValue::Plain(client) => client.pallet_name(),
|
||||||
StorageEntryClientValue::Map(client) => client.address.pallet_name(),
|
StorageEntryClientValue::Map(client) => client.pallet_name(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn storage_name(&self) -> &str {
|
pub fn entry_name(&self) -> &str {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
StorageEntryClientValue::Plain(client) => client.address.entry_name(),
|
StorageEntryClientValue::Plain(client) => client.entry_name(),
|
||||||
StorageEntryClientValue::Map(client) => client.address.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<StorageValue<'_, 'atblock, Addr::Value>> {
|
||||||
|
match &self.value {
|
||||||
|
StorageEntryClientValue::Plain(client) => client.default_value(),
|
||||||
|
StorageEntryClientValue::Map(client) => client.default_value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,11 +148,21 @@ impl <T: Config, Client, Addr: Address, IsMap> StorageEntryClient<T, Client, Add
|
|||||||
// When IsMap = Yes, we have statically asserted that the entry is a map. This can only be false
|
// When IsMap = Yes, we have statically asserted that the entry is a map. This can only be false
|
||||||
// if we skip validation of a static call, and the storage entry, while still present, has changed from
|
// if we skip validation of a static call, and the storage entry, while still present, has changed from
|
||||||
// plain to map.
|
// plain to map.
|
||||||
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, Yes> {
|
impl <'atblock, T: Config, Client, Addr: Address> StorageEntryClient<'atblock, T, Client, Addr, Yes> {
|
||||||
pub fn into_map(self) -> StorageEntryMapClient<T, Client, Addr> {
|
pub fn into_map(self) -> StorageEntryMapClient<'atblock, T, Client, Addr, Addr::HasDefaultValue> {
|
||||||
match self.value {
|
match self.value {
|
||||||
StorageEntryClientValue::Map(this) => this,
|
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 <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, Yes>
|
|||||||
// When IsMap = No, we have statically asserted that the entry is a plain value. This can only be false
|
// 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
|
// if we skip validation of a static call, and the storage entry, while still present, has changed from
|
||||||
// map to plain value.
|
// map to plain value.
|
||||||
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, No> {
|
impl <'atblock, T: Config, Client, Addr: Address> StorageEntryClient<'atblock, T, Client, Addr, No> {
|
||||||
pub fn into_plain(self) -> StorageEntryPlainClient<T, Client, Addr> {
|
pub fn into_plain(self) -> StorageEntryPlainClient<'atblock, T, Client, Addr, Addr::HasDefaultValue> {
|
||||||
match self.value {
|
match self.value {
|
||||||
StorageEntryClientValue::Map(_) => panic!("When IsMap = No, StorageEntryClient should always be a plain value."),
|
|
||||||
StorageEntryClientValue::Plain(this) => this,
|
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.
|
// Regardless, we can do the "safe" thing and try to convert the entry into a map or plain entry.
|
||||||
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, Maybe> {
|
impl <'atblock, T: Config, Client, Addr: Address> StorageEntryClient<'atblock, T, Client, Addr, Maybe> {
|
||||||
pub fn into_map(self) -> Option<StorageEntryMapClient<T, Client, Addr>> {
|
// TODO: In subxt-historic we return Result, not Option, with "StorageEntryIsNotAMapValue" like err.
|
||||||
|
pub fn into_map(self) -> Option<StorageEntryMapClient<'atblock, T, Client, Addr, Addr::HasDefaultValue>> {
|
||||||
match self.value {
|
match self.value {
|
||||||
StorageEntryClientValue::Map(client) => Some(client),
|
StorageEntryClientValue::Map(client) => Some(client),
|
||||||
StorageEntryClientValue::Plain(_) => None,
|
StorageEntryClientValue::Plain(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_plain(self) -> Option<StorageEntryPlainClient<T, Client, Addr>> {
|
pub fn into_plain(self) -> Option<StorageEntryPlainClient<'atblock, T, Client, Addr, Addr::HasDefaultValue>> {
|
||||||
|
// TODO: In subxt-historic we return Result, not Option, with "StorageEntryIsNotAPlainValue" like err.
|
||||||
match self.value {
|
match self.value {
|
||||||
StorageEntryClientValue::Plain(client) => Some(client),
|
StorageEntryClientValue::Plain(client) => Some(client),
|
||||||
StorageEntryClientValue::Map(_) => None,
|
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<T: Config, Client, Addr> {
|
/// This represents a plain storage value at some location.
|
||||||
|
pub struct StorageEntryPlainClient<'atblock, T: Config, Client, Addr, HasDefaultValue> {
|
||||||
client: Client,
|
client: Client,
|
||||||
block_ref: BlockRef<HashFor<T>>,
|
block_ref: BlockRef<HashFor<T>>,
|
||||||
address: Addr,
|
address: Addr,
|
||||||
_marker: PhantomData<T>,
|
info: StorageInfo<'atblock, u32>,
|
||||||
|
types: &'atblock PortableRegistry,
|
||||||
|
_marker: PhantomData<(T, HasDefaultValue)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StorageEntryMapClient<T: Config, Client, Addr> {
|
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<StorageValue<'_, 'atblock, Addr::Value>> {
|
||||||
|
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<T>
|
||||||
|
{
|
||||||
|
pub async fn fetch(&'_ self) -> Result<StorageValue<'_, 'atblock, Addr::Value>, 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<T>
|
||||||
|
{
|
||||||
|
pub async fn try_fetch(&self) -> Result<Option<StorageValue<'_, 'atblock, Addr::Value>>, 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,
|
client: Client,
|
||||||
block_ref: BlockRef<HashFor<T>>,
|
block_ref: BlockRef<HashFor<T>>,
|
||||||
address: Addr,
|
address: Addr,
|
||||||
_marker: PhantomData<T>,
|
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<StorageValue<'_, 'atblock, Addr::Value>> {
|
||||||
|
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<T>
|
||||||
|
{
|
||||||
|
pub async fn fetch(&'_ self, keys: Addr::KeyParts) -> Result<StorageValue<'_, 'atblock, Addr::Value>, Error> {
|
||||||
|
if keys.num_encodable_values() != self.info.keys.len() {
|
||||||
|
// This shouldn't be possible in static cases but if Vec<Value> 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<T>
|
||||||
|
{
|
||||||
|
pub async fn try_fetch(&self, keys: Addr::KeyParts) -> Result<Option<StorageValue<'_, 'atblock, Addr::Value>>, Error> {
|
||||||
|
if keys.num_encodable_values() != self.info.keys.len() {
|
||||||
|
// This shouldn't be possible in static cases but if Vec<Value> 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<Keys: PrefixOf<Addr::KeyParts>>(&self, keys: Keys) -> Result<StreamOf<Result<StorageEntry<'_, 'atblock, Addr>, Error>>, Error> {
|
||||||
|
if keys.num_encodable_values() != self.info.keys.len() {
|
||||||
|
// This shouldn't be possible in static cases but if Vec<Value> 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<Keys: PrefixOf<Addr::KeyParts>>(&self, keys: Keys) -> Result<Vec<u8>, 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<T, Client> Storage<T, Client>
|
impl<T, Client> Storage<T, Client>
|
||||||
|
|||||||
@@ -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<u8>,
|
||||||
|
// 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<u8>,
|
||||||
|
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<u8>, Vec<u8>) {
|
||||||
|
(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<StorageKey<'_, 'atblock, Addr::KeyParts>, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<u32>,
|
||||||
|
types: &'atblock PortableRegistry,
|
||||||
|
bytes: &'entry [u8],
|
||||||
|
marker: PhantomData<KeyParts>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'entry, 'atblock, KeyParts: IntoDecodableValues> StorageKey<'entry, 'atblock, KeyParts> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
info: &StorageInfo<'atblock, u32>,
|
||||||
|
types: &'atblock PortableRegistry,
|
||||||
|
bytes: &'entry [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::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<KeyParts,StorageKeyError> {
|
||||||
|
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<Item = StorageKeyPart<'_, 'entry, 'atblock>> {
|
||||||
|
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<StorageKeyPart<'_, 'entry, 'atblock>> {
|
||||||
|
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<u32>,
|
||||||
|
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<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().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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Value>
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Cow<'entry, [u8]>>,
|
||||||
|
) -> 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<u8> {
|
||||||
|
self.bytes.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode this storage value into the provided response type.
|
||||||
|
pub fn decode(&self) -> Result<Value, Error> {
|
||||||
|
self.decode_as::<Value>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode this storage value into an arbitrary type.
|
||||||
|
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, Error> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user