mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 08:21:05 +00:00
Second pass over Address type and start impl in Subxt
This commit is contained in:
Generated
+1
@@ -5566,6 +5566,7 @@ dependencies = [
|
||||
"bitvec",
|
||||
"derive-where",
|
||||
"either",
|
||||
"frame-decode",
|
||||
"frame-metadata 23.0.0",
|
||||
"futures",
|
||||
"hex",
|
||||
|
||||
@@ -27,6 +27,8 @@ std = [
|
||||
"tracing/std",
|
||||
"impl-serde/std",
|
||||
"primitive-types/std",
|
||||
"sp-core/std",
|
||||
"sp-keyring/std",
|
||||
"sp-crypto-hashing/std",
|
||||
]
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ impl<T: Config> Extrinsics<T> {
|
||||
// Try to decode the extrinsic.
|
||||
let decoded_info = frame_decode::extrinsics::decode_extrinsic(
|
||||
cursor,
|
||||
metadata.deref(),
|
||||
&metadata,
|
||||
metadata.types(),
|
||||
)
|
||||
.map_err(|error| BlockError::ExtrinsicDecodeError {
|
||||
|
||||
@@ -6,6 +6,19 @@ use crate::error::MetadataError;
|
||||
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::sync::Arc;
|
||||
use frame_decode::extrinsics::{
|
||||
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoError,
|
||||
ExtrinsicSignatureInfo,
|
||||
};
|
||||
use frame_decode::storage::{
|
||||
StorageEntry, StorageInfo, StorageInfoError
|
||||
};
|
||||
use frame_decode::runtime_apis::{
|
||||
RuntimeApi, RuntimeApiInfo, RuntimeApiInfoError
|
||||
};
|
||||
use frame_decode::view_functions::{
|
||||
ViewFunction, ViewFunctionInfo, ViewFunctionInfoError
|
||||
};
|
||||
|
||||
/// A cheaply clone-able representation of the runtime metadata received from a node.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -20,6 +33,79 @@ impl core::ops::Deref for Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
impl frame_decode::storage::StorageTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn storage_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
storage_entry: &str,
|
||||
) -> Result<StorageInfo<'_, Self::TypeId>, StorageInfoError<'_>> {
|
||||
self.inner.storage_info(pallet_name, storage_entry)
|
||||
}
|
||||
|
||||
fn storage_entries(&self) -> impl Iterator<Item = StorageEntry<'_>> {
|
||||
self.inner.storage_entries()
|
||||
}
|
||||
}
|
||||
|
||||
impl frame_decode::runtime_apis::RuntimeApiTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn runtime_api_info(
|
||||
&self,
|
||||
trait_name: &str,
|
||||
method_name: &str,
|
||||
) -> Result<RuntimeApiInfo<'_, Self::TypeId>, RuntimeApiInfoError<'_>> {
|
||||
self.inner.runtime_api_info(trait_name, method_name)
|
||||
}
|
||||
|
||||
fn runtime_apis(&self) -> impl Iterator<Item = RuntimeApi<'_>> {
|
||||
self.inner.runtime_apis()
|
||||
}
|
||||
}
|
||||
|
||||
impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn extrinsic_call_info(
|
||||
&self,
|
||||
pallet_index: u8,
|
||||
call_index: u8,
|
||||
) -> Result<ExtrinsicCallInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
self.inner.extrinsic_call_info(pallet_index, call_index)
|
||||
}
|
||||
|
||||
fn extrinsic_signature_info(
|
||||
&self,
|
||||
) -> Result<ExtrinsicSignatureInfo<Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
self.inner.extrinsic_signature_info()
|
||||
}
|
||||
|
||||
fn extrinsic_extension_info(
|
||||
&self,
|
||||
extension_version: Option<u8>,
|
||||
) -> Result<ExtrinsicExtensionInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
|
||||
self.inner.extrinsic_extension_info(extension_version)
|
||||
}
|
||||
}
|
||||
|
||||
impl frame_decode::view_functions::ViewFunctionTypeInfo for Metadata {
|
||||
type TypeId = u32;
|
||||
|
||||
fn view_function_info(
|
||||
&self,
|
||||
pallet_name: &str,
|
||||
function_name: &str,
|
||||
) -> Result<ViewFunctionInfo<'_, Self::TypeId>, ViewFunctionInfoError<'_>> {
|
||||
self.inner.view_function_info(pallet_name, function_name)
|
||||
}
|
||||
|
||||
fn view_functions(&self) -> impl Iterator<Item = ViewFunction<'_>> {
|
||||
self.inner.view_functions()
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_name_err(
|
||||
|
||||
+38
-166
@@ -5,17 +5,28 @@
|
||||
//! Construct addresses to access storage entries with.
|
||||
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::vec::Vec;
|
||||
use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues};
|
||||
use frame_decode::storage::IntoEncodableValues;
|
||||
use scale_decode::DecodeAsType;
|
||||
use crate::utils::Maybe;
|
||||
|
||||
/// A storage address. Concrete addresses are expected to implement either [`FetchableAddress`]
|
||||
/// or [`IterableAddress`], which extends this to define fetchable and iterable storage keys.
|
||||
/// 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 {
|
||||
/// A set of types we'll hash and append to the prefix to build the storage key.
|
||||
/// 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;
|
||||
/// Type of the storage value at this location.
|
||||
type Value: DecodeAsType;
|
||||
/// Does the address have a default value defined for it.
|
||||
/// Set to [`crate::utils::Yes`] to enable APIs which require one,
|
||||
/// or [`crate::utils::Maybe`] to enable APIs which allow one
|
||||
type HasDefaultValue;
|
||||
/// Does the address point to a map (as opposed to a plain value)?
|
||||
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
|
||||
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
|
||||
type IsMap;
|
||||
|
||||
/// The pallet containing this storage entry.
|
||||
fn pallet_name(&self) -> &str;
|
||||
@@ -23,69 +34,35 @@ pub trait Address {
|
||||
/// The name of the storage entry.
|
||||
fn entry_name(&self) -> &str;
|
||||
|
||||
/// Return the input key parts needed to point to this storage entry / entries.
|
||||
fn key_parts(&self) -> impl IntoEncodableValues;
|
||||
|
||||
/// Return a unique hash for this address which can be used to validate it against metadata.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]>;
|
||||
}
|
||||
|
||||
/// This trait represents any storage address which points to a single value we can fetch.
|
||||
pub trait FetchableAddress: Address {
|
||||
/// Does the address have a default value defined for it.
|
||||
/// Set to [`Yes`] to enable APIs which require one.
|
||||
type HasDefaultValue;
|
||||
}
|
||||
|
||||
/// This trait represents any storage address which points to multiple 0 or more values to iterate over.
|
||||
pub trait IterableAddress: Address {
|
||||
/// The storage key values that we'll decode for each value
|
||||
type OutputKeys: IntoDecodableValues;
|
||||
}
|
||||
|
||||
/// An address which points to an individual storage value.
|
||||
pub struct StaticFetchableAddress<KeyParts, Value, HasDefaultValue> {
|
||||
/// An address which is generated by the static APIs.
|
||||
pub struct StaticAddress<KeyParts, Value, HasDefaultValue, IsMap> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
entry_name: Cow<'static, str>,
|
||||
key_parts: KeyParts,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
marker: core::marker::PhantomData<(Value, HasDefaultValue)>,
|
||||
marker: core::marker::PhantomData<(KeyParts, Value, HasDefaultValue, IsMap)>,
|
||||
}
|
||||
|
||||
impl<KeyParts, Value, HasDefaultValue> StaticFetchableAddress<KeyParts, Value, HasDefaultValue> {
|
||||
/// Create a new [`StaticFetchableAddress`] using static strings for the pallet and call name.
|
||||
impl<KeyParts, Value, HasDefaultValue, IsMap> StaticAddress<KeyParts, Value, HasDefaultValue, IsMap> {
|
||||
/// 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,
|
||||
key_parts: KeyParts,
|
||||
hash: [u8; 32],
|
||||
) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
entry_name: Cow::Borrowed(entry_name),
|
||||
key_parts,
|
||||
validation_hash: Some(hash),
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`StaticFetchableAddress`].
|
||||
pub fn new(
|
||||
pallet_name: impl Into<Cow<'static, str>>,
|
||||
entry_name: impl Into<Cow<'static, str>>,
|
||||
key_parts: KeyParts,
|
||||
) -> Self {
|
||||
Self {
|
||||
pallet_name: pallet_name.into(),
|
||||
entry_name: entry_name.into(),
|
||||
key_parts,
|
||||
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;
|
||||
@@ -93,105 +70,16 @@ impl<KeyParts, Value, HasDefaultValue> StaticFetchableAddress<KeyParts, Value, H
|
||||
}
|
||||
}
|
||||
|
||||
impl<KeyParts, Value, HasDefaultValue> Address
|
||||
for StaticFetchableAddress<KeyParts, Value, HasDefaultValue>
|
||||
impl<KeyParts, Value, HasDefaultValue, IsMap> Address
|
||||
for StaticAddress<KeyParts, Value, HasDefaultValue, IsMap>
|
||||
where
|
||||
KeyParts: IntoEncodableValues,
|
||||
Value: DecodeAsType,
|
||||
{
|
||||
type KeyParts = KeyParts;
|
||||
type Value = Value;
|
||||
|
||||
fn key_parts(&self) -> impl IntoEncodableValues {
|
||||
&self.key_parts
|
||||
}
|
||||
|
||||
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<KeyParts, Value, HasDefaultValue> FetchableAddress
|
||||
for StaticFetchableAddress<KeyParts, Value, HasDefaultValue>
|
||||
where
|
||||
KeyParts: IntoEncodableValues,
|
||||
Value: DecodeAsType,
|
||||
{
|
||||
type HasDefaultValue = HasDefaultValue;
|
||||
}
|
||||
|
||||
/// An address which points to a set of storage values.
|
||||
pub struct StaticIterableAddress<InputKeyParts, OutputKeyParts, Value> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
entry_name: Cow<'static, str>,
|
||||
input_key_parts: InputKeyParts,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
marker: core::marker::PhantomData<(OutputKeyParts, Value)>,
|
||||
}
|
||||
|
||||
impl<InputKeyParts, OutputKeyParts, Value>
|
||||
StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
|
||||
{
|
||||
/// Create a new [`StaticIterableAddress`] 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,
|
||||
input_key_parts: InputKeyParts,
|
||||
hash: [u8; 32],
|
||||
) -> Self {
|
||||
Self {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
entry_name: Cow::Borrowed(entry_name),
|
||||
input_key_parts,
|
||||
validation_hash: Some(hash),
|
||||
marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`StaticIterableAddress`].
|
||||
pub fn new(
|
||||
pallet_name: impl Into<Cow<'static, str>>,
|
||||
entry_name: impl Into<Cow<'static, str>>,
|
||||
input_key_parts: InputKeyParts,
|
||||
) -> Self {
|
||||
Self {
|
||||
pallet_name: pallet_name.into(),
|
||||
entry_name: entry_name.into(),
|
||||
input_key_parts,
|
||||
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<InputKeyParts, OutputKeyParts, Value> Address
|
||||
for StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
|
||||
where
|
||||
InputKeyParts: IntoEncodableValues,
|
||||
Value: DecodeAsType,
|
||||
{
|
||||
type KeyParts = InputKeyParts;
|
||||
type Value = Value;
|
||||
|
||||
fn key_parts(&self) -> impl IntoEncodableValues {
|
||||
&self.input_key_parts
|
||||
}
|
||||
type IsMap = IsMap;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
@@ -206,38 +94,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<InputKeyParts, OutputKeyParts, Value> IterableAddress
|
||||
for StaticIterableAddress<InputKeyParts, OutputKeyParts, Value>
|
||||
where
|
||||
InputKeyParts: IntoEncodableValues,
|
||||
OutputKeyParts: IntoDecodableValues,
|
||||
Value: DecodeAsType,
|
||||
{
|
||||
type OutputKeys = OutputKeyParts;
|
||||
}
|
||||
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
|
||||
/// entry could be a map and could have a default value.
|
||||
pub type DynamicAddress<KeyParts, Value> = StaticAddress<KeyParts, Value, Maybe, Maybe>;
|
||||
|
||||
/// Construct a new dynamic storage fetch address.
|
||||
pub fn dynamic_fetch<Keys: IntoEncodableValues>(
|
||||
/// 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<Cow<'static, str>>,
|
||||
entry_name: impl Into<Cow<'static, str>>,
|
||||
storage_entry_keys: Keys,
|
||||
) -> impl FetchableAddress {
|
||||
StaticFetchableAddress::<Keys, scale_value::Value, ()>::new(
|
||||
pallet_name,
|
||||
entry_name,
|
||||
storage_entry_keys,
|
||||
)
|
||||
) -> DynamicAddress<KeyParts, Value> {
|
||||
DynamicAddress::<KeyParts, Value> {
|
||||
pallet_name: pallet_name.into(),
|
||||
entry_name: entry_name.into(),
|
||||
validation_hash: None,
|
||||
marker: core::marker::PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new dynamic storage iter address.
|
||||
pub fn dynamic_iter<Keys: IntoEncodableValues>(
|
||||
pallet_name: impl Into<Cow<'static, str>>,
|
||||
entry_name: impl Into<Cow<'static, str>>,
|
||||
storage_entry_keys: Keys,
|
||||
) -> impl IterableAddress {
|
||||
StaticIterableAddress::<Keys, Vec<scale_value::Value>, scale_value::Value>::new(
|
||||
pallet_name,
|
||||
entry_name,
|
||||
storage_entry_keys,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
//! println!("Alice's account info: {value:?}");
|
||||
//! ```
|
||||
|
||||
mod prefix_of;
|
||||
|
||||
pub mod address;
|
||||
|
||||
use crate::{
|
||||
@@ -52,6 +54,8 @@ use alloc::vec::Vec;
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
use scale_decode::IntoVisitor;
|
||||
|
||||
pub use prefix_of::{ EqualOrPrefixOf, PrefixOf };
|
||||
|
||||
/// 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.
|
||||
///
|
||||
@@ -78,14 +82,15 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
|
||||
|
||||
/// Given a storage address and some metadata, this encodes the address into bytes which can be
|
||||
/// handed to a node to retrieve the corresponding value.
|
||||
pub fn get_address_bytes<Addr: Address>(
|
||||
pub fn get_address_bytes<Addr: Address, Keys: EqualOrPrefixOf<Addr::KeyParts>>(
|
||||
address: &Addr,
|
||||
metadata: &Metadata,
|
||||
keys: Keys,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
frame_decode::storage::encode_storage_key(
|
||||
address.pallet_name(),
|
||||
address.entry_name(),
|
||||
&address.key_parts(),
|
||||
&keys,
|
||||
&**metadata,
|
||||
metadata.types(),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// 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 {}
|
||||
|
||||
// Any reference which impls PrefixOf<K> also impls PrefixOf<K>
|
||||
impl <'a, K, T: PrefixOf<K>> PrefixOf<K> for &'a 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 <'a, T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for &'a 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([]);
|
||||
}
|
||||
}
|
||||
@@ -73,8 +73,12 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
|
||||
/// as `BTreeMap` which allows us to easily swap the two during codegen.
|
||||
pub type KeyedVec<K, V> = Vec<(K, V)>;
|
||||
|
||||
/// A unit marker struct.
|
||||
pub struct Yes;
|
||||
/// A unit marker enum.
|
||||
pub enum Yes {}
|
||||
/// A unit marker enum.
|
||||
pub enum Maybe {}
|
||||
/// A unit marker enum.
|
||||
pub enum No {}
|
||||
|
||||
/// A quick helper to encode some bytes to hex.
|
||||
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
|
||||
|
||||
@@ -90,6 +90,7 @@ sp-crypto-hashing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
frame-metadata = { workspace = true }
|
||||
frame-decode = { workspace = true }
|
||||
either = { workspace = true }
|
||||
web-time = { workspace = true }
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
error::{BlockError, DecodeError, Error},
|
||||
events,
|
||||
runtime_api::RuntimeApi,
|
||||
storage::Storage,
|
||||
storage::StorageClientAt,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
@@ -104,8 +104,8 @@ where
|
||||
}
|
||||
|
||||
/// Work with storage.
|
||||
pub fn storage(&self) -> Storage<T, C> {
|
||||
Storage::new(self.client.clone(), self.block_ref.clone())
|
||||
pub fn storage(&self) -> StorageClientAt<T, C> {
|
||||
StorageClientAt::new(self.client.clone(), self.block_ref.clone())
|
||||
}
|
||||
|
||||
/// Execute a runtime API call at this block.
|
||||
|
||||
@@ -4,94 +4,12 @@
|
||||
|
||||
//! Types associated with accessing and working with storage items.
|
||||
|
||||
// mod storage_client;
|
||||
// mod storage_type;
|
||||
mod storage_client;
|
||||
mod storage_client_at;
|
||||
|
||||
// pub use storage_client::StorageClient;
|
||||
// pub use storage_type::{Storage, StorageKeyValuePair};
|
||||
// pub use subxt_core::storage::address::{
|
||||
// Address, DefaultAddress, DynamicAddress, StaticAddress, StaticStorageKey, StorageKey, dynamic,
|
||||
// }; // TODO re-add
|
||||
|
||||
use crate::{
|
||||
backend::BlockRef,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
pub use storage_client::StorageClient;
|
||||
pub use storage_client_at::StorageClientAt;
|
||||
pub use subxt_core::storage::address::{
|
||||
Address, StaticAddress, DynamicAddress, dynamic,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct StorageClient<T, Client> {
|
||||
client: Client,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClient<T, Client> {
|
||||
/// Create a new [`StorageClient`]
|
||||
pub fn new(client: Client) -> Self {
|
||||
Self {
|
||||
client,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientT<T>,
|
||||
{
|
||||
/// Run the validation logic against some storage address you'd like to access. Returns `Ok(())`
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or storage entry in question do not exist at all).
|
||||
pub fn validate<Addr: Address>(&self, address: &Addr) -> Result<(), Error> {
|
||||
subxt_core::storage::validate(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve the entries at the root of the associated address.
|
||||
pub fn address_root_bytes<Addr: Address>(&self, address: &Addr) -> Vec<u8> {
|
||||
subxt_core::storage::get_address_root_bytes(address)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve an entry. This fails if [`Address::append_entry_bytes`] does; in the built-in
|
||||
/// implementation this would be if the pallet and storage entry being asked for is not available on the
|
||||
/// node you're communicating with, or if the metadata is missing some type information (which should not
|
||||
/// happen).
|
||||
pub fn address_bytes<Addr: Address>(&self, address: &Addr) -> Result<Vec<u8>, Error> {
|
||||
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClient<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Obtain storage at some block hash.
|
||||
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> Storage<T, Client> {
|
||||
Storage::new(self.client.clone(), block_ref.into())
|
||||
}
|
||||
|
||||
/// Obtain storage at the latest finalized block.
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<Storage<T, Client>, Error>> + Send + 'static {
|
||||
// Clone and pass the client in like this so that we can explicitly
|
||||
// return a Future that's Send + 'static, rather than tied to &self.
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
// get the ref for the latest finalized block and use that.
|
||||
let block_ref = client.backend().latest_finalized_block_ref().await?;
|
||||
|
||||
Ok(Storage::new(client, block_ref))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::storage_type::Storage;
|
||||
use super::storage_client_at::StorageClientAt;
|
||||
use crate::{
|
||||
backend::BlockRef,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use subxt_core::storage::address::Address;
|
||||
use subxt_core::storage::EqualOrPrefixOf;
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -45,7 +46,7 @@ where
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve the entries at the root of the associated address.
|
||||
pub fn address_root_bytes<Addr: Address>(&self, address: &Addr) -> Vec<u8> {
|
||||
pub fn address_root_bytes<Addr: Address>(&self, address: &Addr) -> [u8; 32] {
|
||||
subxt_core::storage::get_address_root_bytes(address)
|
||||
}
|
||||
|
||||
@@ -54,8 +55,8 @@ where
|
||||
/// implementation this would be if the pallet and storage entry being asked for is not available on the
|
||||
/// node you're communicating with, or if the metadata is missing some type information (which should not
|
||||
/// happen).
|
||||
pub fn address_bytes<Addr: Address>(&self, address: &Addr) -> Result<Vec<u8>, Error> {
|
||||
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
|
||||
pub fn address_bytes<Addr: Address, Keys: EqualOrPrefixOf<Addr::KeyParts>>(&self, address: &Addr, keys: Keys) -> Result<Vec<u8>, Error> {
|
||||
subxt_core::storage::get_address_bytes(address, &self.client.metadata(), keys).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,14 +66,14 @@ where
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Obtain storage at some block hash.
|
||||
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> Storage<T, Client> {
|
||||
Storage::new(self.client.clone(), block_ref.into())
|
||||
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> StorageClientAt<T, Client> {
|
||||
StorageClientAt::new(self.client.clone(), block_ref.into())
|
||||
}
|
||||
|
||||
/// Obtain storage at the latest finalized block.
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<Storage<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<StorageClientAt<T, Client>, Error>> + Send + 'static {
|
||||
// Clone and pass the client in like this so that we can explicitly
|
||||
// return a Future that's Send + 'static, rather than tied to &self.
|
||||
let client = self.client.clone();
|
||||
@@ -80,7 +81,7 @@ where
|
||||
// get the ref for the latest finalized block and use that.
|
||||
let block_ref = client.backend().latest_finalized_block_ref().await?;
|
||||
|
||||
Ok(Storage::new(client, block_ref))
|
||||
Ok(StorageClientAt::new(client, block_ref))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,21 @@ use codec::Decode;
|
||||
use derive_where::derive_where;
|
||||
use futures::StreamExt;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use subxt_core::storage::address::{Address, StorageHashers, StorageKey};
|
||||
use subxt_core::utils::Yes;
|
||||
use subxt_core::storage::address::Address;
|
||||
use subxt_core::utils::{Maybe, Yes, No};
|
||||
|
||||
/// This is returned from a couple of storage functions.
|
||||
pub use crate::backend::StreamOfResults;
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct Storage<T: Config, Client> {
|
||||
pub struct StorageClientAt<T: Config, Client> {
|
||||
client: Client,
|
||||
block_ref: BlockRef<HashFor<T>>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config, Client> Storage<T, Client> {
|
||||
impl<T: Config, Client> StorageClientAt<T, Client> {
|
||||
/// Create a new [`Storage`]
|
||||
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> Self {
|
||||
Self {
|
||||
@@ -38,6 +38,134 @@ impl<T: Config, Client> Storage<T, Client> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClientAt<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
pub fn entry<Addr: Address>(&self, address: Addr) -> Result<StorageEntryClient<T, Client, Addr, Addr::IsMap>, Error> {
|
||||
subxt_core::storage::validate(&address, &self.client.metadata())?;
|
||||
|
||||
use frame_decode::storage::StorageTypeInfo;
|
||||
let storage_info = self
|
||||
.client
|
||||
.metadata()
|
||||
.storage_info(address.pallet_name(), address.entry_name())?;
|
||||
|
||||
let value = if storage_info.keys.is_empty() {
|
||||
StorageEntryClientValue::Plain(StorageEntryPlainClient {
|
||||
client: self.client.clone(),
|
||||
block_ref: self.block_ref.clone(),
|
||||
address,
|
||||
_marker: core::marker::PhantomData
|
||||
})
|
||||
} else {
|
||||
StorageEntryClientValue::Map(StorageEntryMapClient {
|
||||
client: self.client.clone(),
|
||||
block_ref: self.block_ref.clone(),
|
||||
address,
|
||||
_marker: core::marker::PhantomData
|
||||
})
|
||||
};
|
||||
|
||||
Ok(StorageEntryClient {
|
||||
value,
|
||||
marker: core::marker::PhantomData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StorageEntryClient<T: Config, Client, Addr, IsMap> {
|
||||
value: StorageEntryClientValue<T, Client, Addr>,
|
||||
marker: core::marker::PhantomData<IsMap>,
|
||||
}
|
||||
|
||||
enum StorageEntryClientValue<T: Config, Client, Addr> {
|
||||
Plain(StorageEntryPlainClient<T, Client, Addr>),
|
||||
Map(StorageEntryMapClient<T, Client, Addr>),
|
||||
}
|
||||
|
||||
impl <T: Config, Client, Addr: Address, IsMap> StorageEntryClient<T, Client, Addr, IsMap> {
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
match &self.value {
|
||||
StorageEntryClientValue::Plain(client) => client.address.pallet_name(),
|
||||
StorageEntryClientValue::Map(client) => client.address.pallet_name(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storage_name(&self) -> &str {
|
||||
match &self.value {
|
||||
StorageEntryClientValue::Plain(client) => client.address.entry_name(),
|
||||
StorageEntryClientValue::Map(client) => client.address.entry_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When IsMap = Yes, we have statically asserted that the entry is a map. This can only be false
|
||||
// if we skip validation of a static call, and the storage entry, while still present, has changed from
|
||||
// plain to map.
|
||||
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, Yes> {
|
||||
pub fn into_map(self) -> StorageEntryMapClient<T, Client, Addr> {
|
||||
match self.value {
|
||||
StorageEntryClientValue::Map(this) => this,
|
||||
StorageEntryClientValue::Plain(_) => panic!("When IsMap = Yes, StorageEntryClient should always be a map.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When IsMap = No, we have statically asserted that the entry is a plain value. This can only be false
|
||||
// if we skip validation of a static call, and the storage entry, while still present, has changed from
|
||||
// map to plain value.
|
||||
impl <T: Config, Client, Addr: Address> StorageEntryClient<T, Client, Addr, No> {
|
||||
pub fn into_plain(self) -> StorageEntryPlainClient<T, Client, Addr> {
|
||||
match self.value {
|
||||
StorageEntryClientValue::Map(_) => panic!("When IsMap = No, StorageEntryClient should always be a plain value."),
|
||||
StorageEntryClientValue::Plain(this) => this,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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> {
|
||||
pub fn into_map(self) -> Option<StorageEntryMapClient<T, Client, Addr>> {
|
||||
match self.value {
|
||||
StorageEntryClientValue::Map(client) => Some(client),
|
||||
StorageEntryClientValue::Plain(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_plain(self) -> Option<StorageEntryPlainClient<T, Client, Addr>> {
|
||||
match self.value {
|
||||
StorageEntryClientValue::Plain(client) => Some(client),
|
||||
StorageEntryClientValue::Map(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_plain(&self) -> bool {
|
||||
matches!(self.value, StorageEntryClientValue::Plain(_))
|
||||
}
|
||||
|
||||
pub fn is_map(&self) -> bool {
|
||||
matches!(self.value, StorageEntryClientValue::Map(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StorageEntryPlainClient<T: Config, Client, Addr> {
|
||||
client: Client,
|
||||
block_ref: BlockRef<HashFor<T>>,
|
||||
address: Addr,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct StorageEntryMapClient<T: Config, Client, Addr> {
|
||||
client: Client,
|
||||
block_ref: BlockRef<HashFor<T>>,
|
||||
address: Addr,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
impl<T, Client> Storage<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
@@ -321,3 +449,4 @@ pub struct StorageKeyValuePair<T: Address> {
|
||||
/// The value of the storage entry.
|
||||
pub value: T::Target,
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user