diff --git a/core/src/constants/constant_address.rs b/core/src/constants/constant_address.rs new file mode 100644 index 0000000000..a8ad4a4058 --- /dev/null +++ b/core/src/constants/constant_address.rs @@ -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 { + pallet_name: Cow<'static, str>, + constant_name: Cow<'static, str>, + constant_hash: Option<[u8; 32]>, + _marker: core::marker::PhantomData, +} + +/// The type of address typically used to return dynamic constant values. +pub type DynamicAddress = Address; + +impl Address { + /// Create a new [`Address`] to use to look up a constant. + pub fn new(pallet_name: impl Into, constant_name: impl Into) -> 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 ConstantAddress for Address { + 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, constant_name: impl Into) -> DynamicAddress { + DynamicAddress::new(pallet_name, constant_name) +} diff --git a/core/src/constants/mod.rs b/core/src/constants/mod.rs new file mode 100644 index 0000000000..a9195d9e14 --- /dev/null +++ b/core/src/constants/mod.rs @@ -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( + 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( + metadata: &Metadata, + address: &Address, +) -> Result { + // 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 = ::decode_with_metadata( + &mut constant.value(), + constant.ty(), + &metadata, + )?; + Ok(value) +} diff --git a/core/src/dynamic.rs b/core/src/dynamic.rs index c3686b483c..bfab442e65 100644 --- a/core/src/dynamic.rs +++ b/core/src/dynamic.rs @@ -20,10 +20,10 @@ pub type DecodedValue = scale_value::Value; 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; diff --git a/core/src/lib.rs b/core/src/lib.rs index 11b8a23eec..a8b1af0795 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -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; diff --git a/core/src/marker.rs b/core/src/marker.rs deleted file mode 100644 index aaf7f841f0..0000000000 --- a/core/src/marker.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct Yes; diff --git a/core/src/metadata/metadata_type.rs b/core/src/metadata/metadata_type.rs index 7bd5c5c080..07e74d9b36 100644 --- a/core/src/metadata/metadata_type.rs +++ b/core/src/metadata/metadata_type.rs @@ -25,9 +25,28 @@ impl Metadata { inner: Arc::new(md), } } +} +pub trait MetadatExt { + fn pallet_by_name_err( + &self, + name: &str, + ) -> Result; + + fn pallet_by_index_err( + &self, + index: u8, + ) -> Result; + + fn runtime_api_trait_by_name_err( + &self, + name: &str, + ) -> Result; +} + +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 { @@ -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 { @@ -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 { diff --git a/core/src/metadata/mod.rs b/core/src/metadata/mod.rs index a6ef00ae6f..b268486bb4 100644 --- a/core/src/metadata/mod.rs +++ b/core/src/metadata/mod.rs @@ -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; diff --git a/core/src/storage/mod.rs b/core/src/storage/mod.rs index a1976ecd16..8bf68024e1 100644 --- a/core/src/storage/mod.rs +++ b/core/src/storage/mod.rs @@ -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( - addr: &Address, - out: &mut Vec, -) { - 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( - addr: &Address, - metadata: &Metadata, -) -> Result, 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( + addr: &Address, + out: &mut Vec, + ) { + 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(addr: &Address) -> Vec { - 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( + addr: &Address, + metadata: &Metadata, + ) -> Result, 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(addr: &Address) -> Vec { + 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: &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( + bytes: &mut &[u8], + metadata: &Metadata, + storage_metadata: &StorageEntryMetadata, + ) -> Result { + let return_ty = storage_metadata.entry_type().value_ty(); + let val = T::decode_with_metadata(bytes, return_ty, metadata)?; + Ok(val) + } } diff --git a/core/src/storage/storage_address.rs b/core/src/storage/storage_address.rs index 49d24598a3..fafc306ff5 100644 --- a/core/src/storage/storage_address.rs +++ b/core/src/storage/storage_address.rs @@ -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 { - super::storage_address_root_bytes(self) + super::utils::storage_address_root_bytes(self) } } diff --git a/core/src/tx/mod.rs b/core/src/tx/mod.rs index 0b7975671c..bcf66f93cf 100644 --- a/core/src/tx/mod.rs +++ b/core/src/tx/mod.rs @@ -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}; diff --git a/subxt/src/storage/storage_type.rs b/subxt/src/storage/storage_type.rs index 26b7c60ccf..d31991c6ad 100644 --- a/subxt/src/storage/storage_type.rs +++ b/subxt/src/storage/storage_type.rs @@ -289,65 +289,3 @@ where }) } } - -/// Validate a storage address against the metadata. -pub(crate) fn validate_storage_address( - 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( - bytes: &mut &[u8], - metadata: &Metadata, - storage_metadata: &StorageEntryMetadata, -) -> Result { - 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) -}