diff --git a/core/src/constants/address.rs b/core/src/constants/address.rs index 4dd68fdbf2..49473d4a51 100644 --- a/core/src/constants/address.rs +++ b/core/src/constants/address.rs @@ -4,17 +4,16 @@ //! Construct addresses to access constants with. -use crate::dynamic::DecodedValueThunk; -use crate::metadata::DecodeWithMetadata; use alloc::borrow::Cow; use alloc::string::String; use derive_where::derive_where; +use scale_decode::DecodeAsType; /// This represents a constant address. Anything implementing this trait /// can be used to fetch constants. pub trait Address { /// The target type of the value that lives at this address. - type Target: DecodeWithMetadata; + type Target: DecodeAsType; /// The name of the pallet that the constant lives under. fn pallet_name(&self) -> &str; @@ -32,20 +31,18 @@ pub trait Address { /// This represents the address of a constant. #[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] -pub struct DefaultAddress { +pub struct StaticAddress { pallet_name: Cow<'static, str>, constant_name: Cow<'static, str>, constant_hash: Option<[u8; 32]>, _marker: core::marker::PhantomData, } -/// The type of address used by our static codegen. -pub type StaticAddress = DefaultAddress; -/// The type of address typically used to return dynamic constant values. -pub type DynamicAddress = DefaultAddress; +/// A dynamic lookup address to access a constant. +pub type DynamicAddress = StaticAddress; -impl DefaultAddress { - /// Create a new [`DefaultAddress`] to use to look up a constant. +impl StaticAddress { + /// Create a new [`StaticAddress`] 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()), @@ -55,7 +52,7 @@ impl DefaultAddress { } } - /// Create a new [`DefaultAddress`] that will be validated + /// Create a new [`StaticAddress`] that will be validated /// against node metadata using the hash given. #[doc(hidden)] pub fn new_static( @@ -82,7 +79,7 @@ impl DefaultAddress { } } -impl Address for DefaultAddress { +impl Address for StaticAddress { type Target = ReturnTy; fn pallet_name(&self) -> &str { @@ -99,6 +96,6 @@ impl Address for DefaultAddress { } /// Construct a new dynamic constant lookup. -pub fn dynamic(pallet_name: impl Into, constant_name: impl Into) -> DynamicAddress { +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 index f7f0854393..9c320e0dee 100644 --- a/core/src/constants/mod.rs +++ b/core/src/constants/mod.rs @@ -42,24 +42,31 @@ pub mod address; use address::Address; use alloc::borrow::ToOwned; -use crate::{Metadata, metadata::DecodeWithMetadata}; -use crate::error::ConstantsError; +use crate::Metadata; +use crate::error::ConstantError; +use scale_decode::IntoVisitor; /// When the provided `address` is statically generated via the `#[subxt]` macro, this validates /// that the shape of the constant value is the same as the shape expected by the static address. /// /// When the provided `address` is dynamic (and thus does not come with any expectation of the /// shape of the constant value), this just returns `Ok(())` -pub fn validate(address: &Addr, metadata: &Metadata) -> Result<(), ConstantsError> { +pub fn validate(address: &Addr, metadata: &Metadata) -> Result<(), ConstantError> { if let Some(actual_hash) = address.validation_hash() { let expected_hash = metadata - .pallet_by_name_err(address.pallet_name())? + .pallet_by_name(address.pallet_name()) + .ok_or_else(|| { + ConstantError::PalletNameNotFound(address.pallet_name().to_string()) + })? .constant_hash(address.constant_name()) .ok_or_else(|| { - ConstantsError::ConstantNameNotFound(address.constant_name().to_owned()) + ConstantError::ConstantNameNotFound { + pallet_name: address.pallet_name().to_string(), + constant_name: address.constant_name().to_owned() + } })?; if actual_hash != expected_hash { - return Err(ConstantsError::IncompatibleCodegen); + return Err(ConstantError::IncompatibleCodegen); } } Ok(()) @@ -67,19 +74,18 @@ pub fn validate(address: &Addr, metadata: &Metadata) -> Result<() /// Fetch a constant out of the metadata given a constant address. If the `address` has been /// statically generated, this will validate that the constant shape is as expected, too. -pub fn get(address: &Addr, metadata: &Metadata) -> Result { +pub fn get(address: &Addr, metadata: &Metadata) -> Result { // 1. Validate constant shape if hash given: validate(address, metadata)?; // 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(|| ConstantsError::ConstantNameNotFound(address.constant_name().to_owned()))?; - let value = ::decode_with_metadata( - &mut constant.value(), - constant.ty(), + let constant = frame_decode::constants::decode_constant( + address.pallet_name(), + address.constant_name(), metadata, - )?; - Ok(value) + metadata.types(), + Addr::Target::into_visitor() + ).map_err(ConstantError::CouldNotDecodeConstant)?; + + Ok(constant) } diff --git a/core/src/custom_values/address.rs b/core/src/custom_values/address.rs index 2d3ffbbc7b..e5f834adae 100644 --- a/core/src/custom_values/address.rs +++ b/core/src/custom_values/address.rs @@ -4,23 +4,22 @@ //! Construct addresses to access custom values with. -use crate::dynamic::DecodedValueThunk; -use crate::metadata::DecodeWithMetadata; use derive_where::derive_where; -use crate::utils::YesNo; +use scale_decode::DecodeAsType; +use alloc::borrow::Cow; /// Use this with [`Address::IsDecodable`]. -pub use crate::utils::{Yes, No}; +pub use crate::utils::{No, Maybe, NoMaybe}; /// This represents the address of a custom value in the metadata. /// Anything that implements it can be used to fetch custom values from the metadata. /// The trait is implemented by [`str`] for dynamic lookup and [`StaticAddress`] for static queries. pub trait Address { /// The type of the custom value. - type Target: DecodeWithMetadata; + type Target: DecodeAsType; /// Should be set to `Yes` for Dynamic values and static values that have a valid type. /// Should be `No` for custom values, that have an invalid type id. - type IsDecodable: YesNo; + type IsDecodable: NoMaybe; /// the name (key) by which the custom value can be accessed in the metadata. fn name(&self) -> &str; @@ -31,31 +30,34 @@ pub trait Address { } } -impl Address for str { - type Target = DecodedValueThunk; - type IsDecodable = Yes; - - fn name(&self) -> &str { - self - } -} - /// A static address to a custom value. #[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] pub struct StaticAddress { - name: &'static str, + name: Cow<'static, str>, hash: Option<[u8; 32]>, - phantom: core::marker::PhantomData<(ReturnTy, IsDecodable)>, + marker: core::marker::PhantomData<(ReturnTy, IsDecodable)>, } +/// A dynamic address to a custom value. +pub type DynamicAddress = StaticAddress; + impl StaticAddress { #[doc(hidden)] /// Creates a new StaticAddress. - pub fn new_static(name: &'static str, hash: [u8; 32]) -> StaticAddress { - StaticAddress:: { - name, + pub fn new_static(name: &'static str, hash: [u8; 32]) -> Self { + Self { + name: Cow::Borrowed(name), hash: Some(hash), - phantom: core::marker::PhantomData, + marker: core::marker::PhantomData, + } + } + + /// Create a new [`StaticAddress`] + pub fn new(name: impl Into>) -> Self { + Self { + name: name.into(), + hash: None, + marker: core::marker::PhantomData, } } @@ -64,20 +66,35 @@ impl StaticAddress { Self { name: self.name, hash: None, - phantom: self.phantom, + marker: self.marker, } } } -impl Address for StaticAddress { +impl Address for StaticAddress { type Target = Target; type IsDecodable = IsDecodable; fn name(&self) -> &str { - self.name + &self.name } fn validation_hash(&self) -> Option<[u8; 32]> { self.hash } } + +// Support plain strings for looking up custom values (but prefer `dynamic` if you want to pick the return type) +impl Address for &str { + type Target = scale_value::Value; + type IsDecodable = Maybe; + + fn name(&self) -> &str { + self + } +} + +/// Construct a new dynamic custom value lookup. +pub fn dynamic(custom_value_name: impl Into>) -> DynamicAddress { + DynamicAddress::new(custom_value_name) +} diff --git a/core/src/custom_values/mod.rs b/core/src/custom_values/mod.rs index eae390a856..5b6787ff13 100644 --- a/core/src/custom_values/mod.rs +++ b/core/src/custom_values/mod.rs @@ -33,27 +33,25 @@ pub mod address; use crate::utils::Yes; -use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata}; +use crate::{Metadata, error::CustomValueError}; use address::Address; use alloc::vec::Vec; +use frame_decode::custom_values::CustomValueTypeInfo; /// Run the validation logic against some custom value 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). /// Returns an error if the address was not valid (wrong name, type or raw bytes) -pub fn validate(address: &Addr, metadata: &Metadata) -> Result<(), Error> { +pub fn validate(address: &Addr, metadata: &Metadata) -> Result<(), CustomValueError> { if let Some(actual_hash) = address.validation_hash() { let custom = metadata.custom(); let custom_value = custom .get(address.name()) - .ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().into()))?; + .ok_or_else(|| CustomValueError::NotFound(address.name().into()))?; let expected_hash = custom_value.hash(); if actual_hash != expected_hash { - return Err(MetadataError::IncompatibleCodegen.into()); + return Err(CustomValueError::IncompatibleCodegen); } } - if metadata.custom().get(address.name()).is_none() { - return Err(MetadataError::IncompatibleCodegen.into()); - } Ok(()) } @@ -62,17 +60,18 @@ pub fn validate(address: &Addr, metadata: &Metadata) -> pub fn get + ?Sized>( address: &Addr, metadata: &Metadata, -) -> Result { +) -> Result { // 1. Validate custom value shape if hash given: validate(address, metadata)?; // 2. Attempt to decode custom value: - let custom_value = metadata.custom_value_by_name_err(address.name())?; - let value = ::decode_with_metadata( - &mut custom_value.bytes(), - custom_value.type_id(), + let value = frame_decode::custom_values::decode_custom_value( + address.name(), metadata, - )?; + metadata.types(), + Addr::Target::into_visitor() + ).map_err(CustomValueError::CouldNotDecodeCustomValue)?; + Ok(value) } @@ -80,13 +79,14 @@ pub fn get + ?Sized>( pub fn get_bytes( address: &Addr, metadata: &Metadata, -) -> Result, Error> { +) -> Result, CustomValueError> { // 1. Validate custom value shape if hash given: validate(address, metadata)?; // 2. Return the underlying bytes: - let custom_value = metadata.custom_value_by_name_err(address.name())?; - Ok(custom_value.bytes().to_vec()) + let custom_value = metadata.custom_value_info(address.name()) + .map_err(|e| CustomValueError::NotFound(e.not_found))?; + Ok(custom_value.bytes.to_vec()) } #[cfg(test)] diff --git a/core/src/error.rs b/core/src/error.rs index 29eb14ff70..a862c226f8 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -28,7 +28,9 @@ pub enum Error { #[error(transparent)] Extrinsic(#[from] ExtrinsicError), #[error(transparent)] - Constants(#[from] ConstantsError), + Constant(#[from] ConstantError), + #[error(transparent)] + CustomValueError(CustomValueError) } // impl From for Error { @@ -91,15 +93,46 @@ pub enum MetadataError { CustomValueNameNotFound(String), } +#[derive(Debug, DeriveError)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum RuntimeApiError { + #[error("The static Runtime API address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Failed to encode Runtime API inputs: {0}")] + CouldNotEncodeInputs(frame_decode::runtime_apis::RuntimeApiInputsEncodeError), + #[error("Failed to decode Runtime API: {0}")] + CouldNotDecodeResponse(frame_decode::runtime_apis::RuntimeApiDecodeError), +} + +#[derive(Debug, DeriveError)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum CustomValueError { + #[error("The static custom value address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("The custom value '{0}' was not found")] + NotFound(String), + #[error("Failed to decode custom value: {0}")] + CouldNotDecodeCustomValue(frame_decode::custom_values::CustomValueDecodeError), +} + /// Something went wrong working with a constant. #[derive(Debug, DeriveError)] #[non_exhaustive] #[allow(missing_docs)] -pub enum ConstantsError { +pub enum ConstantError { #[error("The static constant address used is not compatible with the live chain")] IncompatibleCodegen, - #[error("Constant with name {0} not found in the live chain metadata")] - ConstantNameNotFound(String), + #[error("Can't find constant: pallet with name {0} not found")] + PalletNameNotFound(String), + #[error("Constant '{constant_name}' not found in pallet {pallet_name} in the live chain metadata")] + ConstantNameNotFound { + pallet_name: String, + constant_name: String + }, + #[error("Failed to decode constant: {0}")] + CouldNotDecodeConstant(frame_decode::constants::ConstantDecodeError) } /// Something went wrong trying to encode or decode a storage address. @@ -107,6 +140,15 @@ pub enum ConstantsError { #[non_exhaustive] #[allow(missing_docs)] pub enum StorageError { + #[error("The static storage address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Can't find storage value: pallet with name {0} not found")] + PalletNameNotFound(String), + #[error("Storage entry '{entry_name}' not found in pallet {pallet_name} in the live chain metadata")] + StorageEntryNotFound { + pallet_name: String, + entry_name: String + }, #[error("Cannot obtain storage information from metadata: {0}")] StorageInfoError(frame_decode::storage::StorageInfoError<'static>), #[error("Cannot decode storage value: {0}")] @@ -145,7 +187,7 @@ pub enum ExtrinsicError { /// Index of the extrinsic that failed to decode. extrinsic_index: usize, /// The decode error. - error: ExtrinsicDecodeError, + error: frame_decode::extrinsics::ExtrinsicDecodeError, }, #[error("Failed to decode the fields of an extrinsic at index {extrinsic_index}: {error}")] CannotDecodeFields { @@ -163,10 +205,6 @@ pub enum ExtrinsicError { } } -/// An alias for [`frame_decode::extrinsics::ExtrinsicDecodeError`]. -/// -pub type ExtrinsicDecodeError = frame_decode::extrinsics::ExtrinsicDecodeError; - /// An error that can be emitted when trying to construct an instance of [`crate::config::ExtrinsicParams`], /// encode data from the instance, or match on signed extensions. #[derive(Debug, DeriveError)] diff --git a/core/src/lib.rs b/core/src/lib.rs index a39d5e1899..7629489951 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -31,7 +31,6 @@ pub mod custom_values; pub mod dynamic; pub mod error; pub mod events; -pub mod metadata; pub mod runtime_api; pub mod storage; pub mod tx; @@ -49,3 +48,7 @@ pub mod ext { pub use scale_encode; pub use scale_value; } + +pub mod metadata { + pub use subxt_metadata::Metadata; +} \ No newline at end of file diff --git a/core/src/metadata/decode_encode_traits.rs b/core/src/metadata/decode_encode_traits.rs deleted file mode 100644 index b9f83f9595..0000000000 --- a/core/src/metadata/decode_encode_traits.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2019-2024 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -use super::Metadata; - -use alloc::vec::Vec; - -/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`]. -pub trait DecodeWithMetadata: Sized { - /// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`. - fn decode_with_metadata( - bytes: &mut &[u8], - type_id: u32, - metadata: &Metadata, - ) -> Result; -} - -impl DecodeWithMetadata for T { - fn decode_with_metadata( - bytes: &mut &[u8], - type_id: u32, - metadata: &Metadata, - ) -> Result { - let val = T::decode_as_type(bytes, type_id, metadata.types())?; - Ok(val) - } -} - -/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`]. -pub trait EncodeWithMetadata { - /// SCALE encode this type to bytes, possibly with the help of metadata. - fn encode_with_metadata( - &self, - type_id: u32, - metadata: &Metadata, - bytes: &mut Vec, - ) -> Result<(), scale_encode::Error>; -} - -impl EncodeWithMetadata for T { - /// SCALE encode this type to bytes, possibly with the help of metadata. - fn encode_with_metadata( - &self, - type_id: u32, - metadata: &Metadata, - bytes: &mut Vec, - ) -> Result<(), scale_encode::Error> { - self.encode_as_type_to(type_id, metadata.types(), bytes)?; - Ok(()) - } -} diff --git a/core/src/metadata/metadata_type.rs b/core/src/metadata/metadata_type.rs deleted file mode 100644 index b30cdc0427..0000000000 --- a/core/src/metadata/metadata_type.rs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2019-2024 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -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)] -pub struct Metadata { - inner: Arc, -} - -impl core::ops::Deref for Metadata { - type Target = subxt_metadata::Metadata; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl frame_decode::storage::StorageTypeInfo for Metadata { - type TypeId = u32; - - fn storage_info( - &self, - pallet_name: &str, - storage_entry: &str, - ) -> Result, StorageInfoError<'_>> { - self.inner.storage_info(pallet_name, storage_entry) - } - - fn storage_entries(&self) -> impl Iterator> { - 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, RuntimeApiInfoError<'_>> { - self.inner.runtime_api_info(trait_name, method_name) - } - - fn runtime_apis(&self) -> impl Iterator> { - 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, ExtrinsicInfoError<'_>> { - self.inner.extrinsic_call_info(pallet_index, call_index) - } - - fn extrinsic_signature_info( - &self, - ) -> Result, ExtrinsicInfoError<'_>> { - self.inner.extrinsic_signature_info() - } - - fn extrinsic_extension_info( - &self, - extension_version: Option, - ) -> Result, 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, ViewFunctionInfoError<'_>> { - self.inner.view_function_info(pallet_name, function_name) - } - - fn view_functions(&self) -> impl Iterator> { - 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( - &self, - name: &str, - ) -> Result, MetadataError> { - self.pallet_by_name(name) - .ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned())) - } - - /// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found. - pub fn pallet_by_index_err( - &self, - index: u8, - ) -> Result, MetadataError> { - self.pallet_by_index(index) - .ok_or(MetadataError::PalletIndexNotFound(index)) - } - - /// 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( - &self, - name: &str, - ) -> Result, MetadataError> { - self.runtime_api_trait_by_name(name) - .ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned())) - } - - /// Identical to `metadata.custom().get(name)`, but returns an error if the trait is not found. - pub fn custom_value_by_name_err( - &self, - name: &str, - ) -> Result, MetadataError> { - self.custom() - .get(name) - .ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned())) - } -} - -impl From for Metadata { - fn from(md: subxt_metadata::Metadata) -> Self { - Metadata { - inner: Arc::new(md), - } - } -} - -impl TryFrom for Metadata { - type Error = subxt_metadata::TryFromError; - fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result { - subxt_metadata::Metadata::try_from(value).map(Metadata::from) - } -} - -impl codec::Decode for Metadata { - fn decode(input: &mut I) -> Result { - subxt_metadata::Metadata::decode(input).map(Metadata::from) - } -} diff --git a/core/src/metadata/mod.rs b/core/src/metadata/mod.rs deleted file mode 100644 index 480b434bfc..0000000000 --- a/core/src/metadata/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2019-2024 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! A [`Metadata`] type, which is used through this crate. -//! -//! This can be decoded from the bytes handed back from a node when asking for metadata. -//! -//! # Examples -//! -//! ```rust -//! use subxt_core::metadata; -//! -//! // We need to fetch the bytes from somewhere, and then we can decode them: -//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale"); -//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap(); -//! ``` - -mod decode_encode_traits; -mod metadata_type; - -use codec::Decode; - -pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata}; -pub use metadata_type::Metadata; - -/// Attempt to decode some bytes into [`Metadata`], returning an error -/// if decoding fails. -/// -/// This is a shortcut for importing [`codec::Decode`] and using the -/// implementation of that on [`Metadata`]. -pub fn decode_from(bytes: &[u8]) -> Result { - Metadata::decode(&mut &*bytes) -} diff --git a/core/src/runtime_api/mod.rs b/core/src/runtime_api/mod.rs index 9bdab6f50a..39a2c7ce9d 100644 --- a/core/src/runtime_api/mod.rs +++ b/core/src/runtime_api/mod.rs @@ -43,9 +43,8 @@ pub mod payload; -use crate::error::{Error, MetadataError}; -use crate::metadata::{DecodeWithMetadata, Metadata}; -use alloc::borrow::ToOwned; +use crate::error::{RuntimeApiError, MetadataError}; +use crate::metadata::Metadata; use alloc::format; use alloc::string::String; use alloc::vec::Vec; @@ -55,7 +54,7 @@ use payload::Payload; /// if the payload is valid (or if it's not possible to check since the payload has no validation hash). /// Return an error if the payload was not valid or something went wrong trying to validate it (ie /// the runtime API in question do not exist at all) -pub fn validate(payload: &P, metadata: &Metadata) -> Result<(), Error> { +pub fn validate(payload: &P, metadata: &Metadata) -> Result<(), RuntimeApiError> { let Some(static_hash) = payload.validation_hash() else { return Ok(()); }; @@ -78,8 +77,16 @@ pub fn call_name(payload: &P) -> String { } /// Return the encoded call args given a runtime API payload. -pub fn call_args(payload: &P, metadata: &Metadata) -> Result, Error> { - payload.encode_args(metadata) +pub fn call_args(payload: &P, metadata: &Metadata) -> Result, RuntimeApiError> { + let value = frame_decode::runtime_apis::encode_runtime_api_inputs( + payload.trait_name(), + payload.method_name(), + payload.args(), + metadata, + metadata.types() + ).map_err(RuntimeApiError::CouldNotEncodeInputs)?; + + Ok(value) } /// Decode the value bytes at the location given by the provided runtime API payload. @@ -87,17 +94,15 @@ pub fn decode_value( bytes: &mut &[u8], payload: &P, metadata: &Metadata, -) -> Result { - let api_method = metadata - .runtime_api_trait_by_name_err(payload.trait_name())? - .method_by_name(payload.method_name()) - .ok_or_else(|| MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned()))?; - - let val = ::decode_with_metadata( - &mut &bytes[..], - api_method.output_ty(), +) -> Result { + let value = frame_decode::runtime_apis::decode_runtime_api_response( + payload.trait_name(), + payload.method_name(), + bytes, metadata, - )?; + metadata.types(), + P::ReturnType::into_visitor() + ).map_err(RuntimeApiError::CouldNotDecodeResponse)?; - Ok(val) + Ok(value) } diff --git a/core/src/runtime_api/payload.rs b/core/src/runtime_api/payload.rs index 8074da4320..99cef157cf 100644 --- a/core/src/runtime_api/payload.rs +++ b/core/src/runtime_api/payload.rs @@ -6,45 +6,18 @@ //! runtime API calls that can be made. use alloc::borrow::Cow; -use alloc::borrow::ToOwned; -use alloc::string::String; -use alloc::vec::Vec; use core::marker::PhantomData; use derive_where::derive_where; -use scale_encode::EncodeAsFields; -use scale_value::Composite; +use scale_decode::DecodeAsType; +use frame_decode::runtime_apis::IntoEncodableValues; -use crate::Error; -use crate::dynamic::DecodedValueThunk; -use crate::error::MetadataError; - -use crate::metadata::{DecodeWithMetadata, Metadata}; - -/// This represents a runtime API payload that can call into the runtime of node. -/// -/// # Components -/// -/// - associated return type -/// -/// Resulting bytes of the call are interpreted into this type. -/// -/// - runtime function name -/// -/// The function name of the runtime API call. This is obtained by concatenating -/// the runtime trait name with the trait's method. -/// -/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745) -/// contains the `metadata_at_version` function. The corresponding runtime function -/// is `Metadata_metadata_at_version`. -/// -/// - encoded arguments -/// -/// Each argument of the runtime function must be scale-encoded. +/// This represents a runtime API payload that can be used to call a Runtime API on +/// a chain and decode the response. pub trait Payload { + /// Type of the arguments. + type ArgsType: IntoEncodableValues; /// The return type of the function call. - // Note: `DecodeWithMetadata` is needed to decode the function call result - // with the `subxt::Metadata. - type ReturnType: DecodeWithMetadata; + type ReturnType: DecodeAsType; /// The runtime API trait name. fn trait_name(&self) -> &str; @@ -52,16 +25,8 @@ pub trait Payload { /// The runtime API method name. fn method_name(&self) -> &str; - /// Scale encode the arguments data. - fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error>; - - /// Encode arguments data and return the output. This is a convenience - /// wrapper around [`Payload::encode_args_to`]. - fn encode_args(&self, metadata: &Metadata) -> Result, Error> { - let mut v = Vec::new(); - self.encode_args_to(metadata, &mut v)?; - Ok(v) - } + /// The input arguments. + fn args(&self) -> &Self::ArgsType; /// Returns the statically generated validation hash. fn validation_hash(&self) -> Option<[u8; 32]> { @@ -74,24 +39,23 @@ pub trait Payload { /// /// This can be created from static values (ie those generated /// via the `subxt` macro) or dynamic values via [`dynamic`]. -#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)] -pub struct DefaultPayload { +#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)] +pub struct StaticPayload { trait_name: Cow<'static, str>, method_name: Cow<'static, str>, - args_data: ArgsData, + args: ArgsType, validation_hash: Option<[u8; 32]>, - _marker: PhantomData, + _marker: PhantomData, } -/// A statically generated runtime API payload. -pub type StaticPayload = DefaultPayload; /// A dynamic runtime API payload. -pub type DynamicPayload = DefaultPayload, DecodedValueThunk>; +pub type DynamicPayload = StaticPayload; -impl Payload - for DefaultPayload +impl Payload + for StaticPayload { - type ReturnType = ReturnTy; + type ArgsType = ArgsType; + type ReturnType = ReturnType; fn trait_name(&self) -> &str { &self.trait_name @@ -101,18 +65,8 @@ impl Payload &self.method_name } - fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error> { - let api_method = metadata - .runtime_api_trait_by_name_err(&self.trait_name)? - .method_by_name(&self.method_name) - .ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?; - let mut fields = api_method - .inputs() - .map(|input| scale_encode::Field::named(input.id, &input.name)); - - self.args_data - .encode_as_fields_to(&mut fields, metadata.types(), out)?; - Ok(()) + fn args(&self) -> &Self::ArgsType { + &self.args } fn validation_hash(&self) -> Option<[u8; 32]> { @@ -120,23 +74,23 @@ impl Payload } } -impl DefaultPayload { - /// Create a new [`DefaultPayload`]. +impl StaticPayload { + /// Create a new [`StaticPayload`]. pub fn new( - trait_name: impl Into, - method_name: impl Into, - args_data: ArgsData, + trait_name: impl Into>, + method_name: impl Into>, + args: ArgsType, ) -> Self { - DefaultPayload { + StaticPayload { trait_name: Cow::Owned(trait_name.into()), method_name: Cow::Owned(method_name.into()), - args_data, + args, validation_hash: None, _marker: PhantomData, } } - /// Create a new static [`DefaultPayload`] using static function name + /// Create a new static [`StaticPayload`] using static function name /// and scale-encoded argument data. /// /// This is only expected to be used from codegen. @@ -144,13 +98,13 @@ impl DefaultPayload { pub fn new_static( trait_name: &'static str, method_name: &'static str, - args_data: ArgsData, + args: ArgsType, hash: [u8; 32], - ) -> DefaultPayload { - DefaultPayload { + ) -> StaticPayload { + StaticPayload { trait_name: Cow::Borrowed(trait_name), method_name: Cow::Borrowed(method_name), - args_data, + args, validation_hash: Some(hash), _marker: core::marker::PhantomData, } @@ -173,18 +127,13 @@ impl DefaultPayload { pub fn method_name(&self) -> &str { &self.method_name } - - /// Returns the arguments data. - pub fn args_data(&self) -> &ArgsData { - &self.args_data - } } /// Create a new [`DynamicPayload`]. -pub fn dynamic( - trait_name: impl Into, - method_name: impl Into, - args_data: impl Into>, -) -> DynamicPayload { - DefaultPayload::new(trait_name, method_name, args_data.into()) +pub fn dynamic( + trait_name: impl Into>, + method_name: impl Into>, + args_data: ArgsType, +) -> DynamicPayload { + DynamicPayload::new(trait_name, method_name, args_data.into()) } diff --git a/core/src/storage/mod.rs b/core/src/storage/mod.rs index 9af1da56a3..df57733dff 100644 --- a/core/src/storage/mod.rs +++ b/core/src/storage/mod.rs @@ -45,10 +45,7 @@ mod prefix_of; pub mod address; -use crate::{ - Error, Metadata, - error::{MetadataError, StorageError}, -}; +use crate::{Metadata, error::StorageError}; use address::Address; use alloc::vec::Vec; use frame_decode::storage::StorageTypeInfo; @@ -61,7 +58,7 @@ pub use prefix_of::{ EqualOrPrefixOf, PrefixOf }; /// /// When the provided `address` is dynamic (and thus does not come with any expectation of the /// shape of the constant value), this just returns `Ok(())` -pub fn validate(address: &Addr, metadata: &Metadata) -> Result<(), Error> { +pub fn validate(address: &Addr, metadata: &Metadata) -> Result<(), StorageError> { let Some(hash) = address.validation_hash() else { return Ok(()); }; @@ -69,15 +66,19 @@ pub fn validate(address: &Addr, metadata: &Metadata) -> Result<() let pallet_name = address.pallet_name(); let entry_name = address.entry_name(); - let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?; + let pallet_metadata = metadata.pallet_by_name(pallet_name) + .ok_or_else(|| StorageError::PalletNameNotFound(pallet_name.to_string()))?; + let storage_hash = pallet_metadata.storage_hash(entry_name) + .ok_or_else(|| StorageError::StorageEntryNotFound { + pallet_name: pallet_name.to_string(), + entry_name: entry_name.to_string(), + })?; - let Some(expected_hash) = pallet_metadata.storage_hash(entry_name) else { - return Err(MetadataError::IncompatibleCodegen.into()); - }; - if expected_hash != hash { - return Err(MetadataError::IncompatibleCodegen.into()); + if storage_hash != hash { + Err(StorageError::IncompatibleCodegen) + } else { + Ok(()) } - Ok(()) } /// Given a storage address and some metadata, this encodes the address into bytes which can be @@ -86,7 +87,7 @@ pub fn get_address_bytes>( address: &Addr, metadata: &Metadata, keys: Keys, -) -> Result, Error> { +) -> Result, StorageError> { frame_decode::storage::encode_storage_key( address.pallet_name(), address.entry_name(), @@ -101,7 +102,10 @@ pub fn get_address_bytes>( /// and storage entry part) into bytes. If the entry being addressed is inside a map, this returns /// the bytes needed to iterate over all of the entries within it. pub fn get_address_root_bytes(address: &Addr) -> [u8; 32] { - frame_decode::storage::encode_storage_key_prefix(address.pallet_name(), address.entry_name()) + frame_decode::storage::encode_storage_key_prefix( + address.pallet_name(), + address.entry_name() + ) } /// Given some storage value that we've retrieved from a node, the address used to retrieve it, and @@ -111,7 +115,7 @@ pub fn decode_value( bytes: &mut &[u8], address: &Addr, metadata: &Metadata, -) -> Result { +) -> Result { frame_decode::storage::decode_storage_value( address.pallet_name(), address.entry_name(), @@ -127,7 +131,7 @@ pub fn decode_value( pub fn default_value( address: &Addr, metadata: &Metadata, -) -> Result, Error> { +) -> Result, StorageError> { let storage_info = metadata .storage_info(address.pallet_name(), address.entry_name()) .map_err(|e| StorageError::StorageInfoError(e.into_owned()))?; diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index d441cab3c6..6d6e254da4 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -22,7 +22,7 @@ use alloc::vec::Vec; use codec::{Compact, Decode, Encode}; use derive_where::derive_where; -pub use yesnomaybe::{Yes, No, Maybe, YesMaybe, YesNo}; +pub use yesnomaybe::{Yes, No, Maybe, YesMaybe, NoMaybe, YesNo}; pub use account_id::AccountId32; pub use account_id20::AccountId20; pub use era::Era; diff --git a/core/src/utils/yesnomaybe.rs b/core/src/utils/yesnomaybe.rs index d28a54f627..334a7968b4 100644 --- a/core/src/utils/yesnomaybe.rs +++ b/core/src/utils/yesnomaybe.rs @@ -39,4 +39,20 @@ impl YesMaybe for Yes { } impl YesMaybe for Maybe { fn is_maybe() -> bool { true } +} + +/// This is implemented for [`No`] and [`Maybe`] and +/// allows us to check at runtime which of these types is present. +pub trait NoMaybe { + /// [`No`] + fn is_no() -> bool { false } + /// [`Maybe`] + fn is_maybe() -> bool { false } +} + +impl NoMaybe for No { + fn is_no() -> bool { true } +} +impl NoMaybe for Maybe { + fn is_maybe() -> bool { true } } \ No newline at end of file diff --git a/historic/src/extrinsics.rs b/historic/src/extrinsics.rs index 30239da842..cfe1acb34e 100644 --- a/historic/src/extrinsics.rs +++ b/historic/src/extrinsics.rs @@ -7,7 +7,7 @@ mod extrinsic_info; mod extrinsic_transaction_extensions; mod extrinsics_type; -pub use extrinsic_transaction_extensions::ExtrinsicTransactionExtensions; +pub use extrinsic_transaction_extensions::ExtrinsicExtrinsicParams; pub use extrinsics_type::{Extrinsic, Extrinsics}; /// Work with extrinsics. diff --git a/historic/src/extrinsics/extrinsic_transaction_extensions.rs b/historic/src/extrinsics/extrinsic_transaction_extensions.rs index 9a0a8aa081..a2c3aca84e 100644 --- a/historic/src/extrinsics/extrinsic_transaction_extensions.rs +++ b/historic/src/extrinsics/extrinsic_transaction_extensions.rs @@ -16,7 +16,7 @@ struct ExtrinsicExtensionsInfo<'extrinsics, 'atblock, TypeId, Resolver> { } /// This represents the transaction extensions of an extrinsic. -pub struct ExtrinsicTransactionExtensions<'extrinsics, 'atblock> { +pub struct ExtrinsicExtrinsicParams<'extrinsics, 'atblock> { all_bytes: &'extrinsics [u8], info: AnyExtrinsicExtensionsInfo<'extrinsics, 'atblock>, } @@ -31,7 +31,7 @@ macro_rules! with_extensions_info { }; } -impl<'extrinsics, 'atblock> ExtrinsicTransactionExtensions<'extrinsics, 'atblock> { +impl<'extrinsics, 'atblock> ExtrinsicExtrinsicParams<'extrinsics, 'atblock> { pub(crate) fn new( all_bytes: &'extrinsics [u8], info: &'extrinsics AnyExtrinsicInfo<'atblock>, diff --git a/historic/src/extrinsics/extrinsics_type.rs b/historic/src/extrinsics/extrinsics_type.rs index 4119f9b6b1..4f0dfe9289 100644 --- a/historic/src/extrinsics/extrinsics_type.rs +++ b/historic/src/extrinsics/extrinsics_type.rs @@ -1,6 +1,6 @@ use super::extrinsic_call::ExtrinsicCall; use super::extrinsic_info::{AnyExtrinsicInfo, with_info}; -use super::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions; +use super::extrinsic_transaction_extensions::ExtrinsicExtrinsicParams; use crate::client::OfflineClientAtBlockT; use crate::config::Config; use crate::error::ExtrinsicsError; @@ -106,8 +106,8 @@ impl<'extrinsics, 'atblock> Extrinsic<'extrinsics, 'atblock> { /// Get information about the transaction extensions of this extrinsic. pub fn transaction_extensions( &self, - ) -> Option> { - ExtrinsicTransactionExtensions::new(self.bytes, self.info) + ) -> Option> { + ExtrinsicExtrinsicParams::new(self.bytes, self.info) } } diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 74739d6a29..174a27b421 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -39,6 +39,12 @@ use frame_decode::runtime_apis::{ use frame_decode::view_functions::{ ViewFunction, ViewFunctionInfo, ViewFunctionInfoError, ViewFunctionInput }; +use frame_decode::constants::{ + ConstantInfo, ConstantInfoError, Constant +}; +use frame_decode::custom_values::{ + CustomValueInfo, CustomValueInfoError, CustomValue +}; use hashbrown::HashMap; use scale_info::{PortableRegistry, Variant, form::PortableForm}; @@ -256,6 +262,64 @@ impl frame_decode::view_functions::ViewFunctionTypeInfo for Metadata { }) } } +impl frame_decode::constants::ConstantTypeInfo for Metadata { + type TypeId = u32; + + fn constant_info( + &self, + pallet_name: &str, + constant_name: &str, + ) -> Result, ConstantInfoError<'_>> { + let pallet = self.pallet_by_name("pallet_name") + .ok_or_else(|| ConstantInfoError::PalletNotFound { pallet_name: pallet_name.to_string() })?; + let constant = pallet.constant_by_name(constant_name) + .ok_or_else(|| ConstantInfoError::ConstantNotFound { pallet_name: Cow::Borrowed(pallet.name()), constant_name: constant_name.to_string() })?; + + let info = ConstantInfo { + bytes: &constant.value, + type_id: constant.ty + }; + + Ok(info) + } + + fn constants(&self) -> impl Iterator> { + self.pallets().flat_map(|pallet| { + let pallet_name = pallet.name(); + pallet.constants().map(|constant| { + Constant { + pallet_name: Cow::Borrowed(pallet_name), + constant_name: Cow::Borrowed(constant.name()) + } + }) + }) + } +} +impl frame_decode::custom_values::CustomValueTypeInfo for Metadata { + type TypeId = u32; + + fn custom_value_info( + &self, + name: &str, + ) -> Result, CustomValueInfoError> { + let custom_value = self.custom() + .get(name) + .ok_or_else(|| CustomValueInfoError { not_found: name.to_string() })?; + + let info = CustomValueInfo { + bytes: &custom_value.data, + type_id: custom_value.type_id + }; + + Ok(info) + } + + fn custom_values(&self) -> impl Iterator> { + self.custom.map.iter().map(|(name, _)| { + CustomValue { name: Cow::Borrowed(name) } + }) + } +} impl Metadata { /// Access the underlying type registry. diff --git a/subxt/src/blocks/extrinsic_types.rs b/subxt/src/blocks/extrinsic_types.rs index 4ba491a7ba..dfb5cca915 100644 --- a/subxt/src/blocks/extrinsic_types.rs +++ b/subxt/src/blocks/extrinsic_types.rs @@ -16,7 +16,7 @@ use subxt_core::blocks::{ExtrinsicDetails as CoreExtrinsicDetails, Extrinsics as // Re-export anything that's directly returned/used in the APIs below. pub use subxt_core::blocks::{ - ExtrinsicMetadataDetails, ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions, + ExtrinsicMetadataDetails, ExtrinsicTransactionExtension, ExtrinsicExtrinsicParams, StaticExtrinsic, }; @@ -198,7 +198,7 @@ where } /// See [`subxt_core::blocks::ExtrinsicDetails::transaction_extensions()`]. - pub fn transaction_extensions(&self) -> Option> { + pub fn transaction_extensions(&self) -> Option> { self.inner.transaction_extensions() } diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index a28b2a5919..235332047b 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -15,7 +15,7 @@ pub use block_types::Block; pub use blocks_client::BlocksClient; pub use extrinsic_types::{ ExtrinsicDetails, ExtrinsicEvents, ExtrinsicTransactionExtension, - ExtrinsicTransactionExtensions, Extrinsics, FoundExtrinsic, StaticExtrinsic, + ExtrinsicExtrinsicParams, Extrinsics, FoundExtrinsic, StaticExtrinsic, }; // We get account nonce info in tx_client, too, so re-use the logic: diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index e6e714a649..6bf3394b0a 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -64,7 +64,7 @@ //! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last). //! //! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network. -//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce()) +//! We statically decode them and access the [tip](crate::blocks::ExtrinsicExtrinsicParams::tip()) and [account nonce](crate::blocks::ExtrinsicExtrinsicParams::nonce()) //! transaction extensions. //! //! ```rust,ignore @@ -90,10 +90,10 @@ //! The [Config](crate::Config) implementation for your chain defines which transaction extensions you expect. //! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in, //! you can try to [get its transaction extensions](crate::blocks::ExtrinsicDetails::transaction_extensions()). -//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find), -//! in the returned [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions). +//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to [find a specific transaction extension](crate::blocks::ExtrinsicExtrinsicParams::find), +//! in the returned [transaction extensions](crate::blocks::ExtrinsicExtrinsicParams). //! -//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and the -//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::tip()) associated with an extrinsic, given its transaction extensions. +//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicExtrinsicParams::tip()) and the +//! [account nonce](crate::blocks::ExtrinsicExtrinsicParams::tip()) associated with an extrinsic, given its transaction extensions. //! If you prefer to do things dynamically you can get the data of the transaction extension as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()). //! diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index be292c012e..e8131e1ab5 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -68,7 +68,7 @@ pub mod config { /// Types representing the metadata obtained from a node. pub mod metadata { - pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata}; + pub use subxt_core::metadata::{DecodeWithMetadata, Metadata}; // Expose metadata types under a sub module in case somebody needs to reference them: pub use subxt_metadata as types; }