diff --git a/core/src/custom_values/mod.rs b/core/src/custom_values/mod.rs index 5b6787ff13..b523e4d390 100644 --- a/core/src/custom_values/mod.rs +++ b/core/src/custom_values/mod.rs @@ -37,6 +37,7 @@ use crate::{Metadata, error::CustomValueError}; use address::Address; use alloc::vec::Vec; use frame_decode::custom_values::CustomValueTypeInfo; +use scale_decode::IntoVisitor; /// 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). diff --git a/core/src/dynamic.rs b/core/src/dynamic.rs index 8f02b2d04c..79fec8feb4 100644 --- a/core/src/dynamic.rs +++ b/core/src/dynamic.rs @@ -5,9 +5,6 @@ //! This module provides the entry points to create dynamic //! transactions, storage and constant lookups. -use crate::metadata::{DecodeWithMetadata, Metadata}; -use alloc::vec::Vec; -use scale_decode::DecodeAsType; pub use scale_value::{At, Value}; /// A [`scale_value::Value`] type endowed with contextual information @@ -30,57 +27,3 @@ pub use crate::runtime_api::payload::dynamic as runtime_api_call; // Execute View Function API function call dynamically. pub use crate::view_functions::payload::dynamic as view_function_call; - -/// This is the result of making a dynamic request to a node. From this, -/// we can return the raw SCALE bytes that we were handed back, or we can -/// complete the decoding of the bytes into a [`DecodedValue`] type. -pub struct DecodedValueThunk { - type_id: u32, - metadata: Metadata, - scale_bytes: Vec, -} - -impl DecodeWithMetadata for DecodedValueThunk { - fn decode_with_metadata( - bytes: &mut &[u8], - type_id: u32, - metadata: &Metadata, - ) -> Result { - let mut v = Vec::with_capacity(bytes.len()); - v.extend_from_slice(bytes); - *bytes = &[]; - Ok(DecodedValueThunk { - type_id, - metadata: metadata.clone(), - scale_bytes: v, - }) - } -} - -impl DecodedValueThunk { - /// Return the SCALE encoded bytes handed back from the node. - pub fn into_encoded(self) -> Vec { - self.scale_bytes - } - /// Return the SCALE encoded bytes handed back from the node without taking ownership of them. - pub fn encoded(&self) -> &[u8] { - &self.scale_bytes - } - /// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type. - pub fn to_value(&self) -> Result { - let val = scale_value::scale::decode_as_type( - &mut &*self.scale_bytes, - self.type_id, - self.metadata.types(), - )?; - Ok(val) - } - /// decode the `DecodedValueThunk` into a concrete type. - pub fn as_type(&self) -> Result { - T::decode_as_type( - &mut &self.scale_bytes[..], - self.type_id, - self.metadata.types(), - ) - } -} diff --git a/core/src/error.rs b/core/src/error.rs index a862c226f8..e82158eb76 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -12,85 +12,79 @@ use thiserror::Error as DeriveError; #[derive(Debug, DeriveError)] #[allow(missing_docs)] pub enum Error { - // /// Codec error. - // #[error("Codec error: {0}")] - // Codec(codec::Error), - #[error(transparent)] - Metadata(#[from] MetadataError), #[error(transparent)] StorageError(#[from] StorageError), - // /// Error decoding to a [`crate::dynamic::Value`]. - // #[error("Error decoding into dynamic value: {0}")] - // Decode(#[from] scale_decode::Error), - // /// Error encoding from a [`crate::dynamic::Value`]. - // #[error("Error encoding from dynamic value: {0}")] - // Encode(#[from] scale_encode::Error), #[error(transparent)] Extrinsic(#[from] ExtrinsicError), #[error(transparent)] Constant(#[from] ConstantError), #[error(transparent)] - CustomValueError(CustomValueError) + CustomValue(#[from] CustomValueError), + #[error(transparent)] + RuntimeApi(#[from] RuntimeApiError), + #[error(transparent)] + ViewFunction(#[from] ViewFunctionError), + #[error(transparent)] + Events(#[from] EventsError), } -// impl From for Error { -// fn from(err: scale_decode::visitor::DecodeError) -> Error { -// Error::Decode(err.into()) -// } -// } - -// // TODO: when `codec::Error` implements `core::Error` -// // remove this impl and replace it by thiserror #[from] -// impl From for Error { -// fn from(err: codec::Error) -> Error { -// Error::Codec(err) -// } -// } - -/// Something went wrong trying to access details in the metadata. -#[derive(Clone, Debug, PartialEq, DeriveError)] +#[derive(Debug, DeriveError)] #[non_exhaustive] #[allow(missing_docs)] -pub enum MetadataError { - // /// The DispatchError type isn't available in the metadata - // #[error("The DispatchError type isn't available")] - // DispatchErrorNotFound, - // /// Type not found in metadata. - // #[error("Type with ID {0} not found")] - // TypeNotFound(u32), - /// Pallet not found (index). - // /// Call type not found in metadata. - // #[error("Call type not found in pallet with index {0}")] - // CallTypeNotFoundInPallet(u8), - // /// Event type not found in metadata. - // #[error("Event type not found in pallet with index {0}")] - // EventTypeNotFoundInPallet(u8), - // // /// Storage details not found in metadata. - // // #[error("Storage details not found in pallet with name {0}")] - // // StorageNotFoundInPallet(String), - // // /// Storage entry not found. - // // #[error("Storage entry {0} not found")] - // // StorageEntryNotFound(String), - #[error("Pallet with index {0} not found")] - PalletIndexNotFound(u8), - #[error("Pallet with name {0} not found")] - PalletNameNotFound(String), - #[error("Variant with index {0} not found")] - VariantIndexNotFound(u8), - #[error("Constant with name {0} not found")] - ConstantNameNotFound(String), - #[error("Call with name {0} not found")] - CallNameNotFound(String), - #[error("Runtime trait with name {0} not found")] - RuntimeTraitNotFound(String), - #[error("Runtime method with name {0} not found")] - RuntimeMethodNotFound(String), - #[error("View Function with query ID {} not found", hex::encode(.0))] - ViewFunctionNotFound([u8; 32]), - #[error("The generated code is not compatible with the node")] +pub enum EventsError { + #[error("Can't decode event: can't decode phase: {0}")] + CannotDecodePhase(codec::Error), + #[error("Can't decode event: can't decode pallet index: {0}")] + CannotDecodePalletIndex(codec::Error), + #[error("Can't decode event: can't decode variant index: {0}")] + CannotDecodeVariantIndex(codec::Error), + #[error("Can't decode event: can't find pallet with index {0}")] + CannotFindPalletWithIndex(u8), + #[error("Can't decode event: can't find variant with index {variant_index} in pallet {pallet_name}")] + CannotFindVariantWithIndex { + pallet_name: String, + variant_index: u8 + }, + #[error("Can't decode field {field_name:?} in event {pallet_name}.{event_name}: {reason}")] + CannotDecodeFieldInEvent { + pallet_name: String, + event_name: String, + field_name: String, + reason: scale_decode::visitor::DecodeError + }, + #[error("Can't decode event topics: {0}")] + CannotDecodeEventTopics(codec::Error), + #[error("Can't decode the fields of event {pallet_name}.{event_name}: {reason}")] + CannotDecodeEventFields { + pallet_name: String, + event_name: String, + reason: scale_decode::Error + }, + #[error("Can't decode event {pallet_name}.{event_name} to Event enum: {reason}")] + CannotDecodeEventEnum { + pallet_name: String, + event_name: String, + reason: scale_decode::Error + } +} + +#[derive(Debug, DeriveError)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum ViewFunctionError { + #[error("The static View Function address used is not compatible with the live chain")] IncompatibleCodegen, - #[error("Custom value with name {0} not found")] - CustomValueNameNotFound(String), + #[error("Can't find View Function: pallet {0} not found")] + PalletNotFound(String), + #[error("Can't find View Function {function_name} in pallet {pallet_name}")] + ViewFunctionNotFound { + pallet_name: String, + function_name: String, + }, + #[error("Failed to encode View Function inputs: {0}")] + CouldNotEncodeInputs(frame_decode::view_functions::ViewFunctionInputsEncodeError), + #[error("Failed to decode View Function: {0}")] + CouldNotDecodeResponse(frame_decode::view_functions::ViewFunctionDecodeError), } #[derive(Debug, DeriveError)] @@ -99,6 +93,13 @@ pub enum MetadataError { pub enum RuntimeApiError { #[error("The static Runtime API address used is not compatible with the live chain")] IncompatibleCodegen, + #[error("Runtime API trait not found: {0}")] + TraitNotFound(String), + #[error("Runtime API method {method_name} not found in trait {trait_name}")] + MethodNotFound { + trait_name: String, + method_name: String, + }, #[error("Failed to encode Runtime API inputs: {0}")] CouldNotEncodeInputs(frame_decode::runtime_apis::RuntimeApiInputsEncodeError), #[error("Failed to decode Runtime API: {0}")] @@ -162,6 +163,17 @@ pub enum StorageError { #[non_exhaustive] #[allow(missing_docs)] pub enum ExtrinsicError { + #[error("The extrinsic payload is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Can't find extrinsic: pallet with name {0} not found")] + PalletNameNotFound(String), + #[error("Can't find extrinsic: call name {call_name} doesn't exist in pallet {pallet_name}")] + CallNameNotFound { + pallet_name: String, + call_name: String + }, + #[error("Can't encode the extrinsic call data: {0}")] + CannotEncodeCallData(scale_encode::Error), #[error("Subxt does not support the extrinsic versions expected by the chain")] UnsupportedVersion, #[error("Cannot construct the required transaction extensions: {0}")] diff --git a/core/src/events.rs b/core/src/events.rs index 192a7ebca5..37081cc3a4 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -46,9 +46,8 @@ use scale_decode::{DecodeAsFields, DecodeAsType}; use subxt_metadata::PalletMetadata; use crate::{ - Error, Metadata, + error::EventsError, Metadata, config::{Config, HashFor}, - error::MetadataError, }; /// Create a new [`Events`] instance from the given bytes. @@ -148,7 +147,7 @@ impl Events { // use of it with our `FilterEvents` stuff. pub fn iter( &self, - ) -> impl Iterator, Error>> + Send + Sync + 'static { + ) -> impl Iterator, EventsError>> + Send + Sync + 'static { // The event bytes ignoring the compact encoded length on the front: let event_bytes = self.event_bytes.clone(); let metadata = self.metadata.clone(); @@ -184,25 +183,25 @@ impl Events { /// Iterate through the events using metadata to dynamically decode and skip /// them, and return only those which should decode to the provided `Ev` type. /// If an error occurs, all subsequent iterations return `None`. - pub fn find(&self) -> impl Iterator> { + pub fn find(&self) -> impl Iterator> { self.iter() .filter_map(|ev| ev.and_then(|ev| ev.as_event::()).transpose()) } /// Iterate through the events using metadata to dynamically decode and skip /// them, and return the first event found which decodes to the provided `Ev` type. - pub fn find_first(&self) -> Result, Error> { + pub fn find_first(&self) -> Result, EventsError> { self.find::().next().transpose() } /// Iterate through the events using metadata to dynamically decode and skip /// them, and return the last event found which decodes to the provided `Ev` type. - pub fn find_last(&self) -> Result, Error> { + pub fn find_last(&self) -> Result, EventsError> { self.find::().last().transpose() } /// Find an event that decodes to the type provided. Returns true if it was found. - pub fn has(&self) -> Result { + pub fn has(&self) -> Result { Ok(self.find::().next().transpose()?.is_some()) } } @@ -246,23 +245,32 @@ impl EventDetails { all_bytes: Arc<[u8]>, start_idx: usize, index: u32, - ) -> Result, Error> { + ) -> Result, EventsError> { let input = &mut &all_bytes[start_idx..]; - let phase = Phase::decode(input)?; + let phase = Phase::decode(input) + .map_err(EventsError::CannotDecodePhase)?; let event_start_idx = all_bytes.len() - input.len(); - let pallet_index = u8::decode(input)?; - let variant_index = u8::decode(input)?; + let pallet_index = u8::decode(input) + .map_err(EventsError::CannotDecodePalletIndex)?; + let variant_index = u8::decode(input) + .map_err(EventsError::CannotDecodeVariantIndex)?; let event_fields_start_idx = all_bytes.len() - input.len(); // Get metadata for the event: - let event_pallet = metadata.pallet_by_index_err(pallet_index)?; + let event_pallet = metadata + .pallet_by_index(pallet_index) + .ok_or_else(|| EventsError::CannotFindPalletWithIndex(pallet_index))?; let event_variant = event_pallet .event_variant_by_index(variant_index) - .ok_or(MetadataError::VariantIndexNotFound(variant_index))?; + .ok_or_else(|| EventsError::CannotFindVariantWithIndex { + pallet_name: event_pallet.name().to_string(), + variant_index: variant_index, + })?; + tracing::debug!( "Decoding Event '{}::{}'", event_pallet.name(), @@ -278,14 +286,20 @@ impl EventDetails { metadata.types(), scale_decode::visitor::IgnoreVisitor::new(), ) - .map_err(scale_decode::Error::from)?; + .map_err(|e| EventsError::CannotDecodeFieldInEvent { + pallet_name: event_pallet.name().to_string(), + event_name: event_variant.name.clone(), + field_name: field_metadata.name.clone().unwrap_or("".to_string()), + reason: e + })?; } // the end of the field bytes. let event_fields_end_idx = all_bytes.len() - input.len(); // topics come after the event data in EventRecord. - let topics = Vec::>::decode(input)?; + let topics = Vec::>::decode(input) + .map_err(EventsError::CannotDecodeEventTopics)?; // what bytes did we skip over in total, including topics. let end_idx = all_bytes.len() - input.len(); @@ -367,7 +381,7 @@ impl EventDetails { /// Decode and provide the event fields back in the form of a [`scale_value::Composite`] /// type which represents the named or unnamed fields that were present in the event. - pub fn field_values(&self) -> Result, Error> { + pub fn decode_fields_as(&self) -> Result { let bytes = &mut self.field_bytes(); let event_metadata = self.event_metadata(); @@ -377,15 +391,19 @@ impl EventDetails { .iter() .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref())); - let decoded = - scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?; + let decoded = E::decode_as_fields(bytes, &mut fields, self.metadata.types()) + .map_err(|e| EventsError::CannotDecodeEventFields { + pallet_name: event_metadata.pallet.name().to_string(), + event_name: event_metadata.variant.name.clone(), + reason: e + })?; Ok(decoded) } /// Attempt to decode these [`EventDetails`] into a type representing the event fields. /// Such types are exposed in the codegen as `pallet_name::events::EventName` types. - pub fn as_event(&self) -> Result, Error> { + pub fn as_event(&self) -> Result, EventsError> { let ev_metadata = self.event_metadata(); if ev_metadata.pallet.name() == E::PALLET && ev_metadata.variant.name == E::EVENT { let mut fields = ev_metadata @@ -394,7 +412,12 @@ impl EventDetails { .iter() .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref())); let decoded = - E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?; + E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types()) + .map_err(|e| EventsError::CannotDecodeEventFields { + pallet_name: E::PALLET.to_string(), + event_name: E::EVENT.to_string(), + reason: e + })?; Ok(Some(decoded)) } else { Ok(None) @@ -404,14 +427,21 @@ impl EventDetails { /// Attempt to decode these [`EventDetails`] into a root event type (which includes /// the pallet and event enum variants as well as the event fields). A compatible /// type for this is exposed via static codegen as a root level `Event` type. - pub fn as_root_event(&self) -> Result { + pub fn as_root_event(&self) -> Result { let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx]; let decoded = E::decode_as_type( &mut &bytes[..], self.metadata.outer_enums().event_enum_ty(), self.metadata.types(), - )?; + ).map_err(|e| { + let md = self.event_metadata(); + EventsError::CannotDecodeEventEnum { + pallet_name: md.pallet.name().to_string(), + event_name: md.variant.name.clone(), + reason: e + } + })?; Ok(decoded) } diff --git a/core/src/lib.rs b/core/src/lib.rs index 7629489951..4cac3a2d21 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -49,6 +49,7 @@ pub mod ext { pub use scale_value; } +/// Re-exports the [`subxt_metadata::Metadata`] type. pub mod metadata { pub use subxt_metadata::Metadata; } \ No newline at end of file diff --git a/core/src/runtime_api/mod.rs b/core/src/runtime_api/mod.rs index 39a2c7ce9d..0fb67354a9 100644 --- a/core/src/runtime_api/mod.rs +++ b/core/src/runtime_api/mod.rs @@ -43,32 +43,39 @@ pub mod payload; -use crate::error::{RuntimeApiError, MetadataError}; +use crate::error::RuntimeApiError; use crate::metadata::Metadata; use alloc::format; use alloc::string::String; use alloc::vec::Vec; use payload::Payload; +use scale_decode::IntoVisitor; /// Run the validation logic against some runtime API payload you'd like to use. Returns `Ok(())` /// 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<(), RuntimeApiError> { - let Some(static_hash) = payload.validation_hash() else { + let Some(hash) = payload.validation_hash() else { return Ok(()); }; - let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?; - let Some(api_method) = api_trait.method_by_name(payload.method_name()) else { - return Err(MetadataError::IncompatibleCodegen.into()); - }; + let trait_name = payload.trait_name(); + let method_name = payload.method_name(); - let runtime_hash = api_method.hash(); - if static_hash != runtime_hash { - return Err(MetadataError::IncompatibleCodegen.into()); + let api_trait = metadata.runtime_api_trait_by_name(trait_name) + .ok_or_else(|| RuntimeApiError::TraitNotFound(trait_name.to_string()))?; + let api_method = api_trait.method_by_name(method_name) + .ok_or_else(|| RuntimeApiError::MethodNotFound { + trait_name: trait_name.to_string(), + method_name: method_name.to_string() + })?; + + if hash != api_method.hash() { + Err(RuntimeApiError::IncompatibleCodegen) + } else { + Ok(()) } - Ok(()) } /// Return the name of the runtime API call from the payload. diff --git a/core/src/runtime_api/payload.rs b/core/src/runtime_api/payload.rs index 99cef157cf..1d98611cd0 100644 --- a/core/src/runtime_api/payload.rs +++ b/core/src/runtime_api/payload.rs @@ -82,8 +82,8 @@ impl StaticPayload { args: ArgsType, ) -> Self { StaticPayload { - trait_name: Cow::Owned(trait_name.into()), - method_name: Cow::Owned(method_name.into()), + trait_name: trait_name.into(), + method_name: method_name.into(), args, validation_hash: None, _marker: PhantomData, diff --git a/core/src/storage/mod.rs b/core/src/storage/mod.rs index df57733dff..7e9711a35c 100644 --- a/core/src/storage/mod.rs +++ b/core/src/storage/mod.rs @@ -92,7 +92,7 @@ pub fn get_address_bytes>( address.pallet_name(), address.entry_name(), &keys, - &**metadata, + metadata, metadata.types(), ) .map_err(|e| StorageError::StorageKeyEncodeError(e).into()) @@ -120,7 +120,7 @@ pub fn decode_value( address.pallet_name(), address.entry_name(), bytes, - &**metadata, + metadata, metadata.types(), Addr::Value::into_visitor(), ) @@ -135,6 +135,7 @@ pub fn default_value( let storage_info = metadata .storage_info(address.pallet_name(), address.entry_name()) .map_err(|e| StorageError::StorageInfoError(e.into_owned()))?; + let value = frame_decode::storage::decode_default_storage_value_with_info( &storage_info, metadata.types(), diff --git a/core/src/tx/mod.rs b/core/src/tx/mod.rs index 5a1c590deb..f78949efbb 100644 --- a/core/src/tx/mod.rs +++ b/core/src/tx/mod.rs @@ -60,10 +60,10 @@ pub mod payload; pub mod signer; use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher}; -use crate::error::{Error, ExtrinsicError, MetadataError}; +use crate::error::ExtrinsicError; use crate::metadata::Metadata; use crate::utils::Encoded; -use alloc::borrow::{Cow, ToOwned}; +use alloc::borrow::Cow; use alloc::vec::Vec; use codec::{Compact, Encode}; use payload::Payload; @@ -77,18 +77,28 @@ pub use crate::client::{ClientState, RuntimeVersion}; /// if the call is valid (or if it's not possible to check since the call has no validation hash). /// Return an error if the call was not valid or something went wrong trying to validate it (ie /// the pallet or call in question do not exist at all). -pub fn validate(call: &Call, metadata: &Metadata) -> Result<(), Error> { - if let Some(details) = call.validation_details() { - let expected_hash = metadata - .pallet_by_name_err(details.pallet_name)? - .call_hash(details.call_name) - .ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?; +pub fn validate(call: &Call, metadata: &Metadata) -> Result<(), ExtrinsicError> { + let Some(details) = call.validation_details() else { + return Ok(()) + }; - if details.hash != expected_hash { - return Err(MetadataError::IncompatibleCodegen.into()); - } + let pallet_name = details.pallet_name; + let call_name = details.call_name; + + let expected_hash = metadata + .pallet_by_name(pallet_name) + .ok_or_else(|| ExtrinsicError::PalletNameNotFound(pallet_name.to_string()))? + .call_hash(call_name) + .ok_or_else(|| ExtrinsicError::CallNameNotFound { + pallet_name: pallet_name.to_string(), + call_name: call_name.to_string() + })?; + + if details.hash != expected_hash { + Err(ExtrinsicError::IncompatibleCodegen) + } else { + Ok(()) } - Ok(()) } /// Returns the suggested transaction versions to build for a given chain, or an error @@ -96,7 +106,7 @@ pub fn validate(call: &Call, metadata: &Metadata) -> Result<(), E /// /// If the result is [`TransactionVersion::V4`], use the `v4` methods in this module. If it's /// [`TransactionVersion::V5`], use the `v5` ones. -pub fn suggested_version(metadata: &Metadata) -> Result { +pub fn suggested_version(metadata: &Metadata) -> Result { let versions = metadata.extrinsic().supported_versions(); if versions.contains(&4) { @@ -104,7 +114,7 @@ pub fn suggested_version(metadata: &Metadata) -> Result(call: &Call, metadata: &Metadata) -> Result, Error> { +pub fn call_data(call: &Call, metadata: &Metadata) -> Result, ExtrinsicError> { let mut bytes = Vec::new(); call.encode_call_data_to(metadata, &mut bytes)?; Ok(bytes) @@ -128,7 +138,7 @@ pub fn call_data(call: &Call, metadata: &Metadata) -> Result( call: &Call, metadata: &Metadata, -) -> Result, Error> { +) -> Result, ExtrinsicError> { create_unsigned_at_version(call, 4, metadata) } @@ -136,7 +146,7 @@ pub fn create_v4_unsigned( pub fn create_v5_bare( call: &Call, metadata: &Metadata, -) -> Result, Error> { +) -> Result, ExtrinsicError> { create_unsigned_at_version(call, 5, metadata) } @@ -145,7 +155,7 @@ fn create_unsigned_at_version( call: &Call, tx_version: u8, metadata: &Metadata, -) -> Result, Error> { +) -> Result, ExtrinsicError> { // 1. Validate this call against the current node metadata if the call comes // with a hash allowing us to do so. validate(call, metadata)?; @@ -176,7 +186,7 @@ pub fn create_v4_signed( call: &Call, client_state: &ClientState, params: >::Params, -) -> Result, Error> { +) -> Result, ExtrinsicError> { // 1. Validate this call against the current node metadata if the call comes // with a hash allowing us to do so. validate(call, &client_state.metadata)?; @@ -200,7 +210,7 @@ pub fn create_v5_general( call: &Call, client_state: &ClientState, params: >::Params, -) -> Result, Error> { +) -> Result, ExtrinsicError> { // 1. Validate this call against the current node metadata if the call comes // with a hash allowing us to do so. validate(call, &client_state.metadata)?; diff --git a/core/src/tx/payload.rs b/core/src/tx/payload.rs index a3842cf03b..16a84bb1a4 100644 --- a/core/src/tx/payload.rs +++ b/core/src/tx/payload.rs @@ -5,10 +5,9 @@ //! This module contains the trait and types used to represent //! transactions that can be submitted. -use crate::Error; -use crate::error::MetadataError; +use crate::error::ExtrinsicError; use crate::metadata::Metadata; -use alloc::borrow::{Cow, ToOwned}; +use alloc::borrow::Cow; use alloc::boxed::Box; use alloc::string::String; @@ -21,11 +20,11 @@ use scale_value::{Composite, Value, ValueDef, Variant}; /// to a node. pub trait Payload { /// Encode call data to the provided output. - fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error>; + fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), ExtrinsicError>; /// Encode call data and return the output. This is a convenience /// wrapper around [`Payload::encode_call_data_to`]. - fn encode_call_data(&self, metadata: &Metadata) -> Result, Error> { + fn encode_call_data(&self, metadata: &Metadata) -> Result, ExtrinsicError> { let mut v = Vec::new(); self.encode_call_data_to(metadata, &mut v)?; Ok(v) @@ -46,10 +45,10 @@ macro_rules! boxed_payload { &self, metadata: &Metadata, out: &mut Vec, - ) -> Result<(), Error> { + ) -> Result<(), ExtrinsicError> { self.as_ref().encode_call_data_to(metadata, out) } - fn encode_call_data(&self, metadata: &Metadata) -> Result, Error> { + fn encode_call_data(&self, metadata: &Metadata) -> Result, ExtrinsicError> { self.as_ref().encode_call_data(metadata) } fn validation_details(&self) -> Option> { @@ -164,11 +163,15 @@ impl DefaultPayload> { } impl Payload for DefaultPayload { - fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error> { - let pallet = metadata.pallet_by_name_err(&self.pallet_name)?; + fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), ExtrinsicError> { + let pallet = metadata.pallet_by_name(&self.pallet_name) + .ok_or_else(|| ExtrinsicError::PalletNameNotFound(self.pallet_name.to_string()))?; let call = pallet .call_variant_by_name(&self.call_name) - .ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?; + .ok_or_else(|| ExtrinsicError::CallNameNotFound { + pallet_name: pallet.name().to_string(), + call_name: self.call_name.to_string() + })?; let pallet_index = pallet.index(); let call_index = call.index; @@ -182,7 +185,8 @@ impl Payload for DefaultPayload { .map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref())); self.call_data - .encode_as_fields_to(&mut fields, metadata.types(), out)?; + .encode_as_fields_to(&mut fields, metadata.types(), out) + .map_err(ExtrinsicError::CannotEncodeCallData)?; Ok(()) } diff --git a/core/src/view_functions/mod.rs b/core/src/view_functions/mod.rs index 3d422804fb..d740bfcaeb 100644 --- a/core/src/view_functions/mod.rs +++ b/core/src/view_functions/mod.rs @@ -7,28 +7,37 @@ pub mod payload; -use crate::error::{Error, MetadataError}; -use crate::metadata::{DecodeWithMetadata, Metadata}; +use crate::error::ViewFunctionError; +use crate::metadata::Metadata; use alloc::vec::Vec; use payload::Payload; +use scale_decode::IntoVisitor; /// Run the validation logic against some View Function payload you'd like to use. Returns `Ok(())` /// 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 View Function in question do not exist at all) -pub fn validate(payload: &P, metadata: &Metadata) -> Result<(), Error> { - let Some(static_hash) = payload.validation_hash() else { +pub fn validate(payload: &P, metadata: &Metadata) -> Result<(), ViewFunctionError> { + let Some(hash) = payload.validation_hash() else { return Ok(()); }; - let view_function = metadata - .view_function_by_query_id(payload.query_id()) - .ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?; - if static_hash != view_function.hash() { - return Err(MetadataError::IncompatibleCodegen.into()); - } + let pallet_name = payload.pallet_name(); + let function_name = payload.function_name(); - Ok(()) + let view_function = metadata.pallet_by_name(pallet_name) + .ok_or_else(|| ViewFunctionError::PalletNotFound(pallet_name.to_string()))? + .view_function_by_name(function_name) + .ok_or_else(|| ViewFunctionError::ViewFunctionNotFound { + pallet_name: pallet_name.to_string(), + function_name: function_name.to_string() + })?; + + if hash != view_function.hash() { + Err(ViewFunctionError::IncompatibleCodegen) + } else { + Ok(()) + } } /// The name of the Runtime API call which can execute @@ -36,17 +45,16 @@ pub const CALL_NAME: &str = "RuntimeViewFunction_execute_view_function"; /// Encode the bytes that will be passed to the "execute_view_function" Runtime API call, /// to execute the View Function represented by the given payload. -pub fn call_args(payload: &P, metadata: &Metadata) -> Result, Error> { - let mut call_args = Vec::with_capacity(32); - call_args.extend_from_slice(payload.query_id()); +pub fn call_args(payload: &P, metadata: &Metadata) -> Result, ViewFunctionError> { + let inputs = frame_decode::view_functions::encode_view_function_inputs( + payload.pallet_name(), + payload.function_name(), + payload.args(), + metadata, + metadata.types() + ).map_err(ViewFunctionError::CouldNotEncodeInputs)?; - let mut call_arg_params = Vec::new(); - payload.encode_args_to(metadata, &mut call_arg_params)?; - - use codec::Encode; - call_arg_params.encode_to(&mut call_args); - - Ok(call_args) + Ok(inputs) } /// Decode the value bytes at the location given by the provided View Function payload. @@ -54,16 +62,15 @@ pub fn decode_value( bytes: &mut &[u8], payload: &P, metadata: &Metadata, -) -> Result { - let view_function = metadata - .view_function_by_query_id(payload.query_id()) - .ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?; - - let val = ::decode_with_metadata( - &mut &bytes[..], - view_function.output_ty(), +) -> Result { + let value = frame_decode::view_functions::decode_view_function_response( + payload.pallet_name(), + payload.function_name(), + bytes, metadata, - )?; + metadata.types(), + P::ReturnType::into_visitor() + ).map_err(ViewFunctionError::CouldNotDecodeResponse)?; - Ok(val) + Ok(value) } diff --git a/core/src/view_functions/payload.rs b/core/src/view_functions/payload.rs index 85f39fa232..149ea14d45 100644 --- a/core/src/view_functions/payload.rs +++ b/core/src/view_functions/payload.rs @@ -5,17 +5,11 @@ //! This module contains the trait and types used to represent //! View Function calls that can be made. -use alloc::vec::Vec; use core::marker::PhantomData; use derive_where::derive_where; -use scale_encode::EncodeAsFields; -use scale_value::Composite; - -use crate::Error; -use crate::dynamic::DecodedValueThunk; -use crate::error::MetadataError; - -use crate::metadata::{DecodeWithMetadata, Metadata}; +use scale_decode::DecodeAsType; +use frame_decode::view_functions::IntoEncodableValues; +use alloc::borrow::Cow; /// This represents a View Function payload that can call into the runtime of node. /// @@ -33,24 +27,19 @@ use crate::metadata::{DecodeWithMetadata, Metadata}; /// /// Each argument of the View Function must be scale-encoded. pub trait Payload { + /// Type of the arguments for this call. + 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 payload target. - fn query_id(&self) -> &[u8; 32]; + /// The View Function pallet name. + fn pallet_name(&self) -> &str; - /// Scale encode the arguments data. - fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error>; + /// The View Function function name. + fn function_name(&self) -> &str; - /// 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 arguments. + fn args(&self) -> &Self::ArgsType; /// Returns the statically generated validation hash. fn validation_hash(&self) -> Option<[u8; 32]> { @@ -59,44 +48,38 @@ pub trait Payload { } /// A View Function payload containing the generic argument data -/// and interpreting the result of the call as `ReturnTy`. +/// and interpreting the result of the call as `ReturnType`. /// /// 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 { - query_id: [u8; 32], - args_data: ArgsData, +#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)] +pub struct StaticPayload { + pallet_name: Cow<'static, str>, + function_name: Cow<'static, str>, + args: ArgsType, validation_hash: Option<[u8; 32]>, - _marker: PhantomData, + _marker: PhantomData, } -/// A statically generated View Function payload. -pub type StaticPayload = DefaultPayload; /// A dynamic View Function 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 query_id(&self) -> &[u8; 32] { - &self.query_id + fn pallet_name(&self) -> &str { + &self.pallet_name } - fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec) -> Result<(), Error> { - let view_function = metadata - .view_function_by_query_id(&self.query_id) - .ok_or(MetadataError::ViewFunctionNotFound(self.query_id))?; - let mut fields = view_function - .inputs() - .map(|input| scale_encode::Field::named(input.id, &input.name)); + fn function_name(&self) -> &str { + &self.function_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]> { @@ -104,30 +87,37 @@ impl Payload } } -impl DefaultPayload { - /// Create a new [`DefaultPayload`] for a View Function call. - pub fn new(query_id: [u8; 32], args_data: ArgsData) -> Self { - DefaultPayload { - query_id, - args_data, +impl StaticPayload { + /// Create a new [`StaticPayload`] for a View Function call. + pub fn new( + pallet_name: impl Into>, + function_name: impl Into>, + args: ArgsType, + ) -> Self { + StaticPayload { + pallet_name: pallet_name.into(), + function_name: function_name.into(), + args, validation_hash: None, _marker: PhantomData, } } - /// Create a new static [`DefaultPayload`] for a View Function call + /// Create a new static [`StaticPayload`] for a View Function call /// using static function name and scale-encoded argument data. /// /// This is only expected to be used from codegen. #[doc(hidden)] pub fn new_static( - query_id: [u8; 32], - args_data: ArgsData, + pallet_name: &'static str, + function_name: &'static str, + args: ArgsType, hash: [u8; 32], - ) -> DefaultPayload { - DefaultPayload { - query_id, - args_data, + ) -> StaticPayload { + StaticPayload { + pallet_name: Cow::Borrowed(pallet_name), + function_name: Cow::Borrowed(function_name), + args, validation_hash: Some(hash), _marker: core::marker::PhantomData, } @@ -140,14 +130,13 @@ impl DefaultPayload { ..self } } - - /// Returns the arguments data. - pub fn args_data(&self) -> &ArgsData { - &self.args_data - } } /// Create a new [`DynamicPayload`] to call a View Function. -pub fn dynamic(query_id: [u8; 32], args_data: impl Into>) -> DynamicPayload { - DefaultPayload::new(query_id, args_data.into()) +pub fn dynamic( + pallet_name: impl Into>, + function_name: impl Into>, + args: ArgsType +) -> DynamicPayload { + DynamicPayload::new(pallet_name, function_name, args) } diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 174a27b421..348b08d70a 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -395,20 +395,6 @@ impl Metadata { }) } - /// Access a view function given its query ID, if any. - pub fn view_function_by_query_id( - &'_ self, - query_id: &[u8; 32], - ) -> Option> { - // Dev note: currently, we only have pallet view functions, and here - // we just do a naive thing of iterating over the pallets to find the one - // we're looking for. Eventually we should construct a separate map of view - // functions for easy querying here. - self.pallets() - .flat_map(|p| p.view_functions()) - .find(|vf| vf.query_id() == query_id) - } - /// Returns custom user defined types pub fn custom(&self) -> CustomMetadata<'_> { CustomMetadata {