Get subxt-core compiling

This commit is contained in:
James Wilson
2025-10-02 15:01:02 +01:00
parent e1d8cca2e9
commit 8257e28c57
13 changed files with 295 additions and 304 deletions
+1
View File
@@ -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).
-57
View File
@@ -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<u8>,
}
impl DecodeWithMetadata for DecodedValueThunk {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self, scale_decode::Error> {
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<u8> {
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<DecodedValue, scale_decode::Error> {
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<T: DecodeAsType>(&self) -> Result<T, scale_decode::Error> {
T::decode_as_type(
&mut &self.scale_bytes[..],
self.type_id,
self.metadata.types(),
)
}
}
+79 -67
View File
@@ -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<scale_decode::visitor::DecodeError> 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<codec::Error> 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<u32>),
}
#[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}")]
+52 -22
View File
@@ -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<T: Config> Events<T> {
// use of it with our `FilterEvents` stuff.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
) -> impl Iterator<Item = Result<EventDetails<T>, 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<T: Config> Events<T> {
/// 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<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
self.iter()
.filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).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<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().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<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().last().transpose()
}
/// Find an event that decodes to the type provided. Returns true if it was found.
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, EventsError> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
@@ -246,23 +245,32 @@ impl<T: Config> EventDetails<T> {
all_bytes: Arc<[u8]>,
start_idx: usize,
index: u32,
) -> Result<EventDetails<T>, Error> {
) -> Result<EventDetails<T>, 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<T: Config> EventDetails<T> {
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("<unknown>".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::<HashFor<T>>::decode(input)?;
let topics = Vec::<HashFor<T>>::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<T: Config> EventDetails<T> {
/// 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<scale_value::Composite<u32>, Error> {
pub fn decode_fields_as<E: DecodeAsFields>(&self) -> Result<E, EventsError> {
let bytes = &mut self.field_bytes();
let event_metadata = self.event_metadata();
@@ -377,15 +391,19 @@ impl<T: Config> EventDetails<T> {
.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<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, 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<T: Config> EventDetails<T> {
.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<T: Config> EventDetails<T> {
/// 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<E: DecodeAsType>(&self) -> Result<E, Error> {
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, EventsError> {
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)
}
+1
View File
@@ -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;
}
+17 -10
View File
@@ -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<P: Payload>(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.
+2 -2
View File
@@ -82,8 +82,8 @@ impl<ArgsType, ReturnTy> StaticPayload<ArgsType, ReturnTy> {
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,
+3 -2
View File
@@ -92,7 +92,7 @@ pub fn get_address_bytes<Addr: Address, Keys: EqualOrPrefixOf<Addr::KeyParts>>(
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<Addr: Address>(
address.pallet_name(),
address.entry_name(),
bytes,
&**metadata,
metadata,
metadata.types(),
Addr::Value::into_visitor(),
)
@@ -135,6 +135,7 @@ pub fn default_value<Addr: Address>(
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(),
+30 -20
View File
@@ -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: Payload>(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: Payload>(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: Payload>(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<TransactionVersion, Error> {
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, ExtrinsicError> {
let versions = metadata.extrinsic().supported_versions();
if versions.contains(&4) {
@@ -104,7 +114,7 @@ pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Erro
} else if versions.contains(&5) {
Ok(TransactionVersion::V5)
} else {
Err(ExtrinsicError::UnsupportedVersion.into())
Err(ExtrinsicError::UnsupportedVersion)
}
}
@@ -118,7 +128,7 @@ pub enum TransactionVersion {
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, 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: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<
pub fn create_v4_unsigned<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 4, metadata)
}
@@ -136,7 +146,7 @@ pub fn create_v4_unsigned<T: Config, Call: Payload>(
pub fn create_v5_bare<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 5, metadata)
}
@@ -145,7 +155,7 @@ fn create_unsigned_at_version<T: Config, Call: Payload>(
call: &Call,
tx_version: u8,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, 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<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV4<T>, Error> {
) -> Result<PartialTransactionV4<T>, 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<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV5<T>, Error> {
) -> Result<PartialTransactionV5<T>, 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)?;
+15 -11
View File
@@ -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<u8>) -> Result<(), Error>;
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> 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<Vec<u8>, Error> {
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, 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<u8>,
) -> Result<(), Error> {
) -> Result<(), ExtrinsicError> {
self.as_ref().encode_call_data_to(metadata, out)
}
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
self.as_ref().encode_call_data(metadata)
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
@@ -164,11 +163,15 @@ impl DefaultPayload<Composite<()>> {
}
impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> 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<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
.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(())
}
+38 -31
View File
@@ -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<P: Payload>(payload: &P, metadata: &Metadata) -> Result<(), Error> {
let Some(static_hash) = payload.validation_hash() else {
pub fn validate<P: Payload>(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<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut call_args = Vec::with_capacity(32);
call_args.extend_from_slice(payload.query_id());
pub fn call_args<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, 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<P: Payload>(
bytes: &mut &[u8],
payload: &P,
metadata: &Metadata,
) -> Result<P::ReturnType, Error> {
let view_function = metadata
.view_function_by_query_id(payload.query_id())
.ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?;
let val = <P::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
view_function.output_ty(),
) -> Result<P::ReturnType, ViewFunctionError> {
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)
}
+57 -68
View File
@@ -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<u8>) -> 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<Vec<u8>, 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<ArgsData, ReturnTy> {
query_id: [u8; 32],
args_data: ArgsData,
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
pub struct StaticPayload<ArgsType, ReturnType> {
pallet_name: Cow<'static, str>,
function_name: Cow<'static, str>,
args: ArgsType,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
_marker: PhantomData<ReturnType>,
}
/// A statically generated View Function payload.
pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
/// A dynamic View Function payload.
pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
for DefaultPayload<ArgsData, ReturnTy>
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
for StaticPayload<ArgsType, ReturnType>
{
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<u8>) -> 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<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
}
}
impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
/// 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<ReturnTy, ArgsType> StaticPayload<ArgsType, ReturnTy> {
/// Create a new [`StaticPayload`] for a View Function call.
pub fn new(
pallet_name: impl Into<Cow<'static, str>>,
function_name: impl Into<Cow<'static, str>>,
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<ArgsData, ReturnTy> {
DefaultPayload {
query_id,
args_data,
) -> StaticPayload<ArgsType, ReturnTy> {
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<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
..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<Composite<()>>) -> DynamicPayload {
DefaultPayload::new(query_id, args_data.into())
pub fn dynamic<ArgsType, ReturnType>(
pallet_name: impl Into<Cow<'static, str>>,
function_name: impl Into<Cow<'static, str>>,
args: ArgsType
) -> DynamicPayload<ArgsType, ReturnType> {
DynamicPayload::new(pallet_name, function_name, args)
}
-14
View File
@@ -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<ViewFunctionMetadata<'_>> {
// 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 {