constants and storage entries

This commit is contained in:
Tadeo hepperle
2024-02-02 10:44:57 +01:00
parent e427529341
commit e855aac44b
11 changed files with 282 additions and 113 deletions
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use alloc::borrow::Cow;
use alloc::string::String;
use derivative::Derivative;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait ConstantAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
/// The name of the constant in a given pallet.
fn constant_name(&self) -> &str;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents the address of a constant.
#[derive(Derivative)]
#[derivative(Clone(bound = ""), Debug(bound = ""))]
pub struct Address<ReturnTy> {
pallet_name: Cow<'static, str>,
constant_name: Cow<'static, str>,
constant_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<ReturnTy>,
}
/// The type of address typically used to return dynamic constant values.
pub type DynamicAddress = Address<DecodedValueThunk>;
impl<ReturnTy> Address<ReturnTy> {
/// Create a new [`Address`] to use to look up a constant.
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
constant_name: Cow::Owned(constant_name.into()),
constant_hash: None,
_marker: core::marker::PhantomData,
}
}
/// Create a new [`Address`] that will be validated
/// against node metadata using the hash given.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
constant_name: &'static str,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
constant_name: Cow::Borrowed(constant_name),
constant_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
/// Do not validate this constant prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
pallet_name: self.pallet_name,
constant_name: self.constant_name,
constant_hash: None,
_marker: self._marker,
}
}
}
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for Address<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn constant_name(&self) -> &str {
&self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// Construct a new dynamic constant lookup.
pub fn dynamic(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> DynamicAddress {
DynamicAddress::new(pallet_name, constant_name)
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing constants.
mod constant_address;
pub use constant_address::{dynamic, Address, ConstantAddress, DynamicAddress};
use alloc::borrow::ToOwned;
use crate::{
metadata::{DecodeWithMetadata, MetadatExt},
Error, Metadata, MetadataError,
};
/// Run validation logic against some constant 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 constant in question do not exist at all).
pub fn validate_constant<Address: ConstantAddress>(
metadata: &subxt_metadata::Metadata,
address: &Address,
) -> Result<(), Error> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = metadata
.pallet_by_name_err(address.pallet_name())?
.constant_hash(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
})?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
}
pub fn access_constant<Address: ConstantAddress>(
metadata: &Metadata,
address: &Address,
) -> Result<Address::Target, Error> {
// 1. Validate constant shape if hash given:
validate_constant(metadata, address)?;
// 2. Attempt to decode the constant into the type given:
let constant = metadata
.pallet_by_name_err(address.pallet_name())?
.constant_by_name(address.constant_name())
.ok_or_else(|| MetadataError::ConstantNameNotFound(address.constant_name().to_owned()))?;
let value = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
&mut constant.value(),
constant.ty(),
&metadata,
)?;
Ok(value)
}
+2 -2
View File
@@ -20,10 +20,10 @@ pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
pub use crate::tx::dynamic as tx;
// // Lookup constants dynamically.
// pub use crate::constants::dynamic as constant;
pub use crate::constants::dynamic as constant;
// // Lookup storage values dynamically.
// pub use crate::storage::dynamic as storage;
pub use crate::storage::dynamic as storage;
// // Execute runtime API function call dynamically.
// pub use crate::runtime_api::dynamic as runtime_api_call;
+4 -1
View File
@@ -12,6 +12,7 @@ extern crate alloc;
pub mod client;
pub mod config;
pub mod constants;
pub mod dynamic;
mod error;
pub mod metadata;
@@ -35,6 +36,8 @@ pub use metadata::Metadata;
mod macros;
mod marker {
/// A unit marker struct that is only used for specialized generics.
/// A unit marker struct signalling that some property is true
pub struct Yes;
}
pub use marker::Yes;
-1
View File
@@ -1 +0,0 @@
pub struct Yes;
+22 -3
View File
@@ -25,9 +25,28 @@ impl Metadata {
inner: Arc::new(md),
}
}
}
pub trait MetadatExt {
fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError>;
}
impl MetadatExt for subxt_metadata::Metadata {
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
pub fn pallet_by_name_err(
fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
@@ -36,7 +55,7 @@ impl Metadata {
}
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
pub fn pallet_by_index_err(
fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
@@ -45,7 +64,7 @@ impl Metadata {
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
pub fn runtime_api_trait_by_name_err(
fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError> {
+1 -1
View File
@@ -8,7 +8,7 @@ mod decode_encode_traits;
mod metadata_type;
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::Metadata;
pub use metadata_type::{MetadatExt, Metadata};
// Expose metadata types under a sub module in case somebody needs to reference them:
pub use subxt_metadata as types;
+89 -37
View File
@@ -4,51 +4,103 @@
//! Types associated with accessing and working with storage items.
use alloc::vec::Vec;
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod storage_address;
pub mod address {
pub use super::storage_address::{
dynamic, make_static_storage_map_key, Address, DynamicAddress, StaticStorageMapKey,
StorageAddress, Yes,
};
}
use crate::Error;
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
use crate::Metadata;
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub(crate) fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(sp_core_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_core_hashing::twox_128(addr.entry_name().as_bytes()));
}
pub mod utils {
use crate::{
metadata::{DecodeWithMetadata, MetadatExt},
Error, Metadata, MetadataError,
};
use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use subxt_metadata::{PalletMetadata, StorageEntryMetadata};
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub(crate) fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
use super::StorageAddress;
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(sp_core_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_core_hashing::twox_128(addr.entry_name().as_bytes()));
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub(crate) fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
/// Return details about the given storage entry.
pub fn lookup_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a subxt_metadata::Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let storage_metadata = pallet_metadata
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
let storage_entry = storage_metadata
.entry_by_name(entry_name)
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
/// Validate a storage address against the metadata.
pub fn validate_storage_address<Address: StorageAddress>(
address: &Address,
pallet: PalletMetadata<'_>,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(pallet, address.entry_name(), hash)?;
}
Ok(())
}
/// Validate a storage entry against the metadata.
pub fn validate_storage(
pallet: PalletMetadata<'_>,
storage_name: &str,
hash: [u8; 32],
) -> Result<(), Error> {
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Given some bytes, a pallet and storage name, decode the response.
pub fn decode_storage_with_metadata<T: DecodeWithMetadata>(
bytes: &mut &[u8],
metadata: &Metadata,
storage_metadata: &StorageEntryMetadata,
) -> Result<T, Error> {
let return_ty = storage_metadata.entry_type().value_ty();
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
Ok(val)
}
}
+4 -6
View File
@@ -5,8 +5,9 @@
use crate::{
dynamic::DecodedValueThunk,
error::StorageAddressError,
metadata::MetadatExt,
utils::{Encoded, Static},
MetadataError,
MetadataError, Yes,
};
use crate::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata};
@@ -14,6 +15,7 @@ use crate::Error;
use alloc::borrow::{Cow, ToOwned};
use alloc::string::String;
use alloc::vec::Vec;
use derivative::Derivative;
use scale_info::TypeDef;
use subxt_metadata::{StorageEntryType, StorageHasher};
@@ -51,10 +53,6 @@ pub trait StorageAddress {
}
}
/// Used to signal whether a [`StorageAddress`] can be iterated,
/// fetched and returned with a default value in the type system.
pub struct Yes;
/// A concrete storage address. This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive(Derivative)]
@@ -125,7 +123,7 @@ where
/// the pallet and entry name). Use [`crate::storage::StorageClient::address_bytes()`]
/// to obtain the bytes representing the entire address.
pub fn to_root_bytes(&self) -> Vec<u8> {
super::storage_address_root_bytes(self)
super::utils::storage_address_root_bytes(self)
}
}
+1
View File
@@ -5,6 +5,7 @@
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::metadata::MetadatExt;
use crate::Error;
use crate::MetadataError;
use crate::{dynamic::Value, metadata::Metadata};
-62
View File
@@ -289,65 +289,3 @@ where
})
}
}
/// Validate a storage address against the metadata.
pub(crate) fn validate_storage_address<Address: StorageAddress>(
address: &Address,
pallet: PalletMetadata<'_>,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(pallet, address.entry_name(), hash)?;
}
Ok(())
}
/// Return details about the given storage entry.
fn lookup_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let storage_metadata = pallet_metadata
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
let storage_entry = storage_metadata
.entry_by_name(entry_name)
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
/// Validate a storage entry against the metadata.
fn validate_storage(
pallet: PalletMetadata<'_>,
storage_name: &str,
hash: [u8; 32],
) -> Result<(), Error> {
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Fetch the return type out of a [`StorageEntryType`].
fn return_type_from_storage_entry_type(entry: &StorageEntryType) -> u32 {
match entry {
StorageEntryType::Plain(ty) => *ty,
StorageEntryType::Map { value_ty, .. } => *value_ty,
}
}
/// Given some bytes, a pallet and storage name, decode the response.
fn decode_storage_with_metadata<T: DecodeWithMetadata>(
bytes: &mut &[u8],
metadata: &Metadata,
storage_metadata: &StorageEntryMetadata,
) -> Result<T, Error> {
let ty = storage_metadata.entry_type();
let return_ty = return_type_from_storage_entry_type(ty);
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
Ok(val)
}