From 2b7ce2f773d65b390da2cd4480f9be08dc2e2e96 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 3 Oct 2025 11:45:30 +0100 Subject: [PATCH] Claude pass --- subxt/src/blocks/block_types.rs | 6 +- subxt/src/error/dispatch_error.rs | 24 +- subxt/src/error/mod.rs | 580 ++++++++++++++++++++++--- subxt/src/storage/storage_client_at.rs | 15 +- subxt/src/storage/storage_entry.rs | 8 +- subxt/src/storage/storage_key.rs | 1 + subxt/src/storage/storage_value.rs | 12 +- 7 files changed, 548 insertions(+), 98 deletions(-) diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 6702b74449..13ddf0bd22 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -7,7 +7,7 @@ use crate::{ blocks::Extrinsics, client::{OfflineClientT, OnlineClientT}, config::{Config, HashFor, Header}, - error::{BlockError, DecodeError, Error}, + error::{BlockError, Error}, events, runtime_api::RuntimeApi, storage::StorageClientAt, @@ -173,10 +173,10 @@ where 4 => u32::decode(cursor)?.into(), 8 => u64::decode(cursor)?, _ => { - return Err(Error::Decode(DecodeError::custom_string(format!( + return Err(Error::Other(format!( "state call AccountNonceApi_account_nonce returned an unexpected number of bytes: {} (expected 2, 4 or 8)", account_nonce_bytes.len() - )))); + ))); } }; Ok(account_nonce) diff --git a/subxt/src/error/dispatch_error.rs b/subxt/src/error/dispatch_error.rs index 6ffcb9ad51..e1b4b33937 100644 --- a/subxt/src/error/dispatch_error.rs +++ b/subxt/src/error/dispatch_error.rs @@ -11,7 +11,7 @@ use scale_decode::{DecodeAsType, TypeResolver, visitor::DecodeAsTypeResult}; use std::{borrow::Cow, marker::PhantomData}; -use super::{Error, MetadataError}; +use super::Error; /// An error dispatching a transaction. #[derive(Debug, thiserror::Error, PartialEq, Eq)] @@ -169,24 +169,22 @@ impl std::fmt::Display for ModuleError { impl ModuleError { /// Return more details about this error. - pub fn details(&self) -> Result, MetadataError> { - let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?; - let variant = pallet - .error_variant_by_index(self.error_index()) - .ok_or_else(|| MetadataError::VariantIndexNotFound(self.error_index()))?; + pub fn details(&self) -> Option> { + let pallet = self.metadata.pallet_by_index(self.pallet_index())?; + let variant = pallet.error_variant_by_index(self.error_index())?; - Ok(ModuleErrorDetails { pallet, variant }) + Some(ModuleErrorDetails { pallet, variant }) } /// Return a formatted string of the resolved error details for debugging/display purposes. pub fn details_string(&self) -> String { match self.details() { - Ok(details) => format!( + Some(details) => format!( "{pallet_name}::{variant_name}", pallet_name = details.pallet.name(), variant_name = details.variant.name, ), - Err(_) => format!( + None => format!( "Unknown pallet error '{bytes:?}' (pallet and error details cannot be retrieved)", bytes = self.bytes ), @@ -223,7 +221,7 @@ impl ModuleError { /// Details about the module error. pub struct ModuleErrorDetails<'a> { /// The pallet that the error is in - pub pallet: crate::metadata::types::PalletMetadata<'a>, + pub pallet: crate::metadata::PalletMetadata<'a>, /// The variant representing the error pub variant: &'a scale_info::Variant, } @@ -238,7 +236,7 @@ impl DispatchError { let bytes = bytes.into(); let dispatch_error_ty_id = metadata .dispatch_error_ty() - .ok_or(MetadataError::DispatchErrorNotFound)?; + .ok_or_else(|| super::Error::Other("DispatchError type not found in metadata".to_string()))?; // The aim is to decode our bytes into roughly this shape. This is copied from // `sp_runtime::DispatchError`; we need the variant names and any inner variant @@ -290,10 +288,10 @@ impl DispatchError { } // Decode into our temporary error: - let decoded_dispatch_err = DecodedDispatchError::decode_with_metadata( + let decoded_dispatch_err = DecodedDispatchError::decode_as_type( &mut &*bytes, dispatch_error_ty_id, - &metadata, + metadata.types(), )?; // Convert into the outward-facing error, mainly by handling the Module variant. diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index a1904f0f57..eb7b7e03a1 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -6,7 +6,7 @@ mod dispatch_error; -use subxt_core::error::{BlockError as CoreBlockError, Error as CoreError}; +use subxt_core::error::Error as CoreError; crate::macros::cfg_unstable_light_client! { pub use subxt_lightclient::LightClientError; @@ -19,11 +19,20 @@ pub use dispatch_error::{ // Re-expose the errors we use from other crates here: pub use crate::Metadata; -pub use scale_decode::Error as DecodeError; -pub use scale_encode::Error as EncodeError; -pub use subxt_core::error::{ExtrinsicError, MetadataError, StorageError}; pub use subxt_metadata::TryFromError as MetadataTryFromError; +// Re-export subxt-core error types that we'll use directly: +pub use subxt_core::error::{ + ConstantError as CoreConstantError, + CustomValueError as CoreCustomValueError, + EventsError as CoreEventsError, + ExtrinsicError as CoreExtrinsicError, + ExtrinsicParamsError, + RuntimeApiError as CoreRuntimeApiError, + StorageError as CoreStorageError, + ViewFunctionError as CoreViewFunctionError, +}; + /// The underlying error enum, generic over the type held by the `Runtime` /// variant. Prefer to use the [`Error`] and [`Error`] aliases over /// using this type directly. @@ -31,71 +40,66 @@ pub use subxt_metadata::TryFromError as MetadataTryFromError; #[non_exhaustive] pub enum Error { /// Io error. - #[error("Io error: {0}")] + #[error(transparent)] Io(#[from] std::io::Error), - /// Codec error. - #[error("Scale codec error: {0}")] - Codec(#[from] codec::Error), /// Rpc error. #[error(transparent)] Rpc(#[from] RpcError), /// Serde serialization error - #[error("Serde json error: {0}")] + #[error(transparent)] Serialization(#[from] serde_json::error::Error), - /// Error working with metadata. - #[error("Metadata error: {0}")] - Metadata(#[from] MetadataError), /// Error decoding metadata. - #[error("Metadata Decoding error: {0}")] + #[error(transparent)] MetadataDecoding(#[from] MetadataTryFromError), /// Runtime error. - #[error("Runtime error: {0}")] + #[error(transparent)] Runtime(#[from] DispatchError), - /// Error decoding to a [`crate::dynamic::Value`]. - #[error("Error decoding into dynamic value: {0}")] - Decode(#[from] DecodeError), - /// Error encoding from a [`crate::dynamic::Value`]. - #[error("Error encoding from dynamic value: {0}")] - Encode(#[from] EncodeError), /// Transaction progress error. - #[error("Transaction error: {0}")] + #[error(transparent)] Transaction(#[from] TransactionError), - /// Error constructing the appropriate extrinsic params. - #[error("Extrinsic params error: {0}")] - Extrinsic(#[from] ExtrinsicError), /// Block related error. - #[error("Block error: {0}")] + #[error(transparent)] Block(#[from] BlockError), - /// An error encoding a storage address. - #[error("Error encoding storage address: {0}")] - StorageAddress(#[from] StorageError), + /// Storage error. + #[error(transparent)] + Storage(#[from] StorageError), + /// Storage key error. + #[error(transparent)] + StorageKey(#[from] StorageKeyError), + /// Storage value error. + #[error(transparent)] + StorageValue(#[from] StorageValueError), + /// Constant error. + #[error(transparent)] + Constant(#[from] ConstantError), + /// Custom value error. + #[error(transparent)] + CustomValue(#[from] CustomValueError), + /// Runtime API error. + #[error(transparent)] + RuntimeApi(#[from] RuntimeApiError), + /// View function error. + #[error(transparent)] + ViewFunction(#[from] ViewFunctionError), + /// Events error. + #[error(transparent)] + Events(#[from] EventsError), + /// Extrinsic error. + #[error(transparent)] + Extrinsic(#[from] ExtrinsicError), /// The bytes representing an error that we were unable to decode. #[error("An error occurred but it could not be decoded: {0:?}")] Unknown(Vec), /// Light client error. #[cfg(feature = "unstable-light-client")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-light-client")))] - #[error("An error occurred but it could not be decoded: {0}")] + #[error(transparent)] LightClient(#[from] LightClientError), /// Other error. #[error("Other error: {0}")] Other(String), } -impl From for Error { - fn from(value: CoreError) -> Self { - match value { - CoreError::Codec(e) => Error::Codec(e), - CoreError::Metadata(e) => Error::Metadata(e), - CoreError::StorageError(e) => Error::StorageAddress(e), - CoreError::Decode(e) => Error::Decode(e), - CoreError::Encode(e) => Error::Encode(e), - CoreError::Extrinsic(e) => Error::Extrinsic(e), - CoreError::Block(e) => Error::Block(e.into()), - } - } -} - impl<'a> From<&'a str> for Error { fn from(error: &'a str) -> Self { Error::Other(error.into()) @@ -114,9 +118,17 @@ impl From for Error { } } -impl From for Error { - fn from(value: scale_decode::visitor::DecodeError) -> Self { - Error::Decode(value.into()) +impl From for Error { + fn from(value: codec::Error) -> Self { + // Codec errors typically happen during event/extrinsic decoding, so we map to Other + Error::Other(format!("Codec error: {}", value)) + } +} + +impl From for Error { + fn from(value: scale_decode::Error) -> Self { + // Scale decode errors typically happen during decoding, so we map to Other + Error::Other(format!("Decode error: {}", value)) } } @@ -126,6 +138,62 @@ impl From for Error { } } +// Add From implementations for core error types through their module-specific wrappers +impl From for Error { + fn from(e: CoreStorageError) -> Self { + Error::Storage(StorageError::from(e)) + } +} + +impl From for Error { + fn from(e: CoreExtrinsicError) -> Self { + Error::Extrinsic(ExtrinsicError::from(e)) + } +} + +impl From for Error { + fn from(e: CoreConstantError) -> Self { + Error::Constant(ConstantError::from(e)) + } +} + +impl From for Error { + fn from(e: CoreCustomValueError) -> Self { + Error::CustomValue(CustomValueError::from(e)) + } +} + +impl From for Error { + fn from(e: CoreRuntimeApiError) -> Self { + Error::RuntimeApi(RuntimeApiError::from(e)) + } +} + +impl From for Error { + fn from(e: CoreViewFunctionError) -> Self { + Error::ViewFunction(ViewFunctionError::from(e)) + } +} + +impl From for Error { + fn from(e: CoreEventsError) -> Self { + Error::Events(EventsError::from(e)) + } +} + +// Add From implementations for frame_decode error types that go through StorageError +impl From> for Error { + fn from(e: frame_decode::storage::StorageInfoError<'_>) -> Self { + Error::Storage(StorageError::from(e)) + } +} + +impl From for Error { + fn from(e: frame_decode::storage::StorageKeyEncodeError) -> Self { + Error::Storage(StorageError::from(e)) + } +} + impl Error { /// Checks whether the error was caused by a RPC re-connection. pub fn is_disconnected_will_reconnect(&self) -> bool { @@ -185,31 +253,10 @@ pub enum BlockError { /// Index of the extrinsic that failed to decode. extrinsic_index: usize, /// The decode error. - error: subxt_core::error::ExtrinsicDecodeError, + error: frame_decode::extrinsics::ExtrinsicDecodeError, }, } -impl From for BlockError { - fn from(value: CoreBlockError) -> Self { - match value { - CoreBlockError::LeftoverBytes { - extrinsic_index, - num_leftover_bytes, - } => BlockError::LeftoverBytes { - extrinsic_index, - num_leftover_bytes, - }, - CoreBlockError::ExtrinsicDecodeError { - extrinsic_index, - error, - } => BlockError::ExtrinsicDecodeError { - extrinsic_index, - error, - }, - } - } -} - impl BlockError { /// Produce an error that a block with the given hash cannot be found. pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError { @@ -238,3 +285,400 @@ pub enum TransactionError { #[error("The transaction was dropped: {0}")] Dropped(String), } + +// Module-specific error types following the subxt-core pattern: + +/// Errors that can occur when working with storage. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum StorageError { + #[error("Storage: The static storage address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Storage: Can't find storage value - pallet with name '{0}' not found")] + PalletNameNotFound(String), + #[error("Storage: Entry '{entry_name}' not found in pallet '{pallet_name}'")] + StorageEntryNotFound { + pallet_name: String, + entry_name: String, + }, + #[error("Storage: Cannot obtain storage information from metadata: {0}")] + StorageInfoError(String), + #[error("Storage: Cannot decode storage value: {0}")] + StorageValueDecodeError(String), + #[error("Storage: Cannot encode storage key: {0}")] + StorageKeyEncodeError(#[from] frame_decode::storage::StorageKeyEncodeError), + #[error("Storage: RPC error - {0}")] + Rpc(#[from] RpcError), + #[error("Storage: Could not fetch next entry from storage subscription - {reason}")] + StorageEventError { + reason: String, + }, + #[error( + "Storage: Wrong number of keys provided (expected {num_keys_expected}, got {num_keys_provided})" + )] + WrongNumberOfKeysProvidedForFetch { + num_keys_provided: usize, + num_keys_expected: usize, + }, + #[error( + "Storage: Too many keys provided for iteration (expected at most {max_keys_expected}, got {num_keys_provided})" + )] + TooManyKeysProvidedForIter { + num_keys_provided: usize, + max_keys_expected: usize, + }, +} + +impl From for StorageError { + fn from(e: CoreStorageError) -> Self { + match e { + CoreStorageError::IncompatibleCodegen => StorageError::IncompatibleCodegen, + CoreStorageError::PalletNameNotFound(name) => StorageError::PalletNameNotFound(name), + CoreStorageError::StorageEntryNotFound { pallet_name, entry_name } => { + StorageError::StorageEntryNotFound { pallet_name, entry_name } + } + CoreStorageError::StorageInfoError(e) => StorageError::StorageInfoError(e.to_string()), + CoreStorageError::StorageValueDecodeError(e) => { + StorageError::StorageValueDecodeError(e.to_string()) + } + CoreStorageError::StorageKeyEncodeError(e) => StorageError::StorageKeyEncodeError(e), + _ => StorageError::StorageInfoError(e.to_string()), + } + } +} + +impl From> for StorageError { + fn from(e: frame_decode::storage::StorageInfoError<'_>) -> Self { + StorageError::StorageInfoError(e.to_string()) + } +} + +/// Errors that can occur when working with storage keys. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum StorageKeyError { + #[error("Storage: Could not decode storage key - {reason}")] + DecodeError { + reason: frame_decode::storage::StorageKeyDecodeError, + }, + #[error("Storage: Could not decode storage key - leftover bytes after decoding")] + LeftoverBytes { + leftover_bytes: Vec, + }, + #[error("Storage: Could not decode part of storage key at index {index} - {reason}")] + DecodePartError { + index: usize, + reason: scale_decode::Error, + }, + #[error("Storage: Could not decode values from storage key - {reason}")] + DecodeKeyValueError { + reason: frame_decode::storage::StorageKeyValueDecodeError, + }, +} + +/// Errors that can occur when working with storage values. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum StorageValueError { + #[error("Storage: Could not decode storage value - {reason}")] + DecodeError { + reason: scale_decode::Error, + }, + #[error("Storage: Could not decode storage value - leftover bytes after decoding")] + LeftoverBytes { + leftover_bytes: Vec, + }, +} + +/// Errors that can occur when working with constants. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum ConstantError { + #[error("Constant: The static constant address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Constant: Can't find constant - pallet with name '{0}' not found")] + PalletNameNotFound(String), + #[error("Constant: '{constant_name}' not found in pallet '{pallet_name}'")] + ConstantNameNotFound { + pallet_name: String, + constant_name: String, + }, + #[error("Constant: Failed to decode constant - {0}")] + CouldNotDecodeConstant(String), +} + +impl From for ConstantError { + fn from(e: CoreConstantError) -> Self { + match e { + CoreConstantError::IncompatibleCodegen => ConstantError::IncompatibleCodegen, + CoreConstantError::PalletNameNotFound(name) => ConstantError::PalletNameNotFound(name), + CoreConstantError::ConstantNameNotFound { pallet_name, constant_name } => { + ConstantError::ConstantNameNotFound { pallet_name, constant_name } + } + CoreConstantError::CouldNotDecodeConstant(e) => { + ConstantError::CouldNotDecodeConstant(e.to_string()) + } + _ => ConstantError::CouldNotDecodeConstant(e.to_string()), + } + } +} + +/// Errors that can occur when working with custom values. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum CustomValueError { + #[error("Custom Value: The static custom value address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Custom Value: '{0}' was not found")] + NotFound(String), + #[error("Custom Value: Failed to decode custom value - {0}")] + CouldNotDecodeCustomValue(String), +} + +impl From for CustomValueError { + fn from(e: CoreCustomValueError) -> Self { + match e { + CoreCustomValueError::IncompatibleCodegen => CustomValueError::IncompatibleCodegen, + CoreCustomValueError::NotFound(name) => CustomValueError::NotFound(name), + CoreCustomValueError::CouldNotDecodeCustomValue(e) => { + CustomValueError::CouldNotDecodeCustomValue(e.to_string()) + } + _ => CustomValueError::CouldNotDecodeCustomValue(e.to_string()), + } + } +} + +/// Errors that can occur when working with runtime APIs. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum RuntimeApiError { + #[error("Runtime API: The static Runtime API address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Runtime API: Trait '{0}' not found")] + TraitNotFound(String), + #[error("Runtime API: Method '{method_name}' not found in trait '{trait_name}'")] + MethodNotFound { + trait_name: String, + method_name: String, + }, + #[error("Runtime API: Failed to encode inputs - {0}")] + CouldNotEncodeInputs(String), + #[error("Runtime API: Failed to decode response - {0}")] + CouldNotDecodeResponse(String), + #[error("Runtime API: RPC error - {0}")] + Rpc(#[from] RpcError), +} + +impl From for RuntimeApiError { + fn from(e: CoreRuntimeApiError) -> Self { + match e { + CoreRuntimeApiError::IncompatibleCodegen => RuntimeApiError::IncompatibleCodegen, + CoreRuntimeApiError::TraitNotFound(name) => RuntimeApiError::TraitNotFound(name), + CoreRuntimeApiError::MethodNotFound { trait_name, method_name } => { + RuntimeApiError::MethodNotFound { trait_name, method_name } + } + CoreRuntimeApiError::CouldNotEncodeInputs(e) => { + RuntimeApiError::CouldNotEncodeInputs(e.to_string()) + } + CoreRuntimeApiError::CouldNotDecodeResponse(e) => { + RuntimeApiError::CouldNotDecodeResponse(e.to_string()) + } + _ => RuntimeApiError::CouldNotDecodeResponse(e.to_string()), + } + } +} + +/// Errors that can occur when working with view functions. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum ViewFunctionError { + #[error("View Function: The static View Function address used is not compatible with the live chain")] + IncompatibleCodegen, + #[error("View Function: Pallet '{0}' not found")] + PalletNotFound(String), + #[error("View Function: '{function_name}' not found in pallet '{pallet_name}'")] + ViewFunctionNotFound { + pallet_name: String, + function_name: String, + }, + #[error("View Function: Failed to encode inputs - {0}")] + CouldNotEncodeInputs(String), + #[error("View Function: Failed to decode response - {0}")] + CouldNotDecodeResponse(String), + #[error("View Function: RPC error - {0}")] + Rpc(#[from] RpcError), +} + +impl From for ViewFunctionError { + fn from(e: CoreViewFunctionError) -> Self { + match e { + CoreViewFunctionError::IncompatibleCodegen => ViewFunctionError::IncompatibleCodegen, + CoreViewFunctionError::PalletNotFound(name) => ViewFunctionError::PalletNotFound(name), + CoreViewFunctionError::ViewFunctionNotFound { pallet_name, function_name } => { + ViewFunctionError::ViewFunctionNotFound { pallet_name, function_name } + } + CoreViewFunctionError::CouldNotEncodeInputs(e) => { + ViewFunctionError::CouldNotEncodeInputs(e.to_string()) + } + CoreViewFunctionError::CouldNotDecodeResponse(e) => { + ViewFunctionError::CouldNotDecodeResponse(e.to_string()) + } + _ => ViewFunctionError::CouldNotDecodeResponse(e.to_string()), + } + } +} + +/// Errors that can occur when working with events. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum EventsError { + #[error("Events: Can't decode event - can't decode phase: {0}")] + CannotDecodePhase(codec::Error), + #[error("Events: Can't decode event - can't decode pallet index: {0}")] + CannotDecodePalletIndex(codec::Error), + #[error("Events: Can't decode event - can't decode variant index: {0}")] + CannotDecodeVariantIndex(codec::Error), + #[error("Events: Can't decode event - can't find pallet with index {0}")] + CannotFindPalletWithIndex(u8), + #[error("Events: Can't decode event - can't find variant with index {variant_index} in pallet {pallet_name}")] + CannotFindVariantWithIndex { + pallet_name: String, + variant_index: u8, + }, + #[error("Events: 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("Events: Can't decode event topics: {0}")] + CannotDecodeEventTopics(codec::Error), + #[error("Events: Can't decode fields of event {pallet_name}.{event_name} - {reason}")] + CannotDecodeEventFields { + pallet_name: String, + event_name: String, + reason: scale_decode::Error, + }, + #[error("Events: Can't decode event {pallet_name}.{event_name} to Event enum - {reason}")] + CannotDecodeEventEnum { + pallet_name: String, + event_name: String, + reason: scale_decode::Error, + }, + #[error("Events: RPC error - {0}")] + Rpc(#[from] RpcError), +} + +impl From for EventsError { + fn from(e: CoreEventsError) -> Self { + match e { + CoreEventsError::CannotDecodePhase(err) => EventsError::CannotDecodePhase(err), + CoreEventsError::CannotDecodePalletIndex(err) => EventsError::CannotDecodePalletIndex(err), + CoreEventsError::CannotDecodeVariantIndex(err) => EventsError::CannotDecodeVariantIndex(err), + CoreEventsError::CannotFindPalletWithIndex(idx) => EventsError::CannotFindPalletWithIndex(idx), + CoreEventsError::CannotFindVariantWithIndex { pallet_name, variant_index } => { + EventsError::CannotFindVariantWithIndex { pallet_name, variant_index } + } + CoreEventsError::CannotDecodeFieldInEvent { pallet_name, event_name, field_name, reason } => { + EventsError::CannotDecodeFieldInEvent { pallet_name, event_name, field_name, reason } + } + CoreEventsError::CannotDecodeEventTopics(err) => EventsError::CannotDecodeEventTopics(err), + CoreEventsError::CannotDecodeEventFields { pallet_name, event_name, reason } => { + EventsError::CannotDecodeEventFields { pallet_name, event_name, reason } + } + CoreEventsError::CannotDecodeEventEnum { pallet_name, event_name, reason } => { + EventsError::CannotDecodeEventEnum { pallet_name, event_name, reason } + } + _ => EventsError::CannotDecodeEventTopics(codec::Error::from("Unknown events error")), + } + } +} + +/// Errors that can occur when working with extrinsics/transactions. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum ExtrinsicError { + #[error("Extrinsic: The extrinsic payload is not compatible with the live chain")] + IncompatibleCodegen, + #[error("Extrinsic: Can't find extrinsic - pallet with name '{0}' not found")] + PalletNameNotFound(String), + #[error("Extrinsic: Call '{call_name}' doesn't exist in pallet '{pallet_name}'")] + CallNameNotFound { + pallet_name: String, + call_name: String, + }, + #[error("Extrinsic: Can't encode call data - {0}")] + CannotEncodeCallData(scale_encode::Error), + #[error("Extrinsic: Unsupported extrinsic version")] + UnsupportedVersion, + #[error("Extrinsic: Cannot construct transaction extensions - {0}")] + Params(#[from] ExtrinsicParamsError), + #[error("Extrinsic: Cannot decode transaction extension '{name}' - {error}")] + CouldNotDecodeTransactionExtension { + name: String, + error: scale_decode::Error, + }, + #[error("Extrinsic: Leftover bytes after decoding extrinsic at index {extrinsic_index} ({num_leftover_bytes} bytes remaining)")] + LeftoverBytes { + extrinsic_index: usize, + num_leftover_bytes: usize, + }, + #[error("Extrinsic: Failed to decode extrinsic at index {extrinsic_index} - {error}")] + ExtrinsicDecodeError { + extrinsic_index: usize, + error: frame_decode::extrinsics::ExtrinsicDecodeError, + }, + #[error("Extrinsic: Failed to decode fields of extrinsic at index {extrinsic_index} - {error}")] + CannotDecodeFields { + extrinsic_index: usize, + error: scale_decode::Error, + }, + #[error("Extrinsic: Failed to decode extrinsic at index {extrinsic_index} to root enum - {error}")] + CannotDecodeIntoRootExtrinsic { + extrinsic_index: usize, + error: scale_decode::Error, + }, + #[error("Extrinsic: RPC error - {0}")] + Rpc(#[from] RpcError), +} + +impl From for ExtrinsicError { + fn from(e: CoreExtrinsicError) -> Self { + match e { + CoreExtrinsicError::IncompatibleCodegen => ExtrinsicError::IncompatibleCodegen, + CoreExtrinsicError::PalletNameNotFound(name) => ExtrinsicError::PalletNameNotFound(name), + CoreExtrinsicError::CallNameNotFound { pallet_name, call_name } => { + ExtrinsicError::CallNameNotFound { pallet_name, call_name } + } + CoreExtrinsicError::CannotEncodeCallData(err) => ExtrinsicError::CannotEncodeCallData(err), + CoreExtrinsicError::UnsupportedVersion => ExtrinsicError::UnsupportedVersion, + CoreExtrinsicError::Params(err) => ExtrinsicError::Params(err), + CoreExtrinsicError::CouldNotDecodeTransactionExtension { name, error } => { + ExtrinsicError::CouldNotDecodeTransactionExtension { name, error } + } + CoreExtrinsicError::LeftoverBytes { extrinsic_index, num_leftover_bytes } => { + ExtrinsicError::LeftoverBytes { extrinsic_index, num_leftover_bytes } + } + CoreExtrinsicError::ExtrinsicDecodeError { extrinsic_index, error } => { + ExtrinsicError::ExtrinsicDecodeError { extrinsic_index, error } + } + CoreExtrinsicError::CannotDecodeFields { extrinsic_index, error } => { + ExtrinsicError::CannotDecodeFields { extrinsic_index, error } + } + CoreExtrinsicError::CannotDecodeIntoRootExtrinsic { extrinsic_index, error } => { + ExtrinsicError::CannotDecodeIntoRootExtrinsic { extrinsic_index, error } + } + _ => ExtrinsicError::CannotEncodeCallData(scale_encode::Error::custom_string(e.to_string())), + } + } +} diff --git a/subxt/src/storage/storage_client_at.rs b/subxt/src/storage/storage_client_at.rs index 9b37c7a285..cd6cb678a8 100644 --- a/subxt/src/storage/storage_client_at.rs +++ b/subxt/src/storage/storage_client_at.rs @@ -6,7 +6,7 @@ use crate::{ backend::{BackendExt, BlockRef}, client::{OfflineClientT, OnlineClientT}, config::{Config, HashFor}, - error::{Error, MetadataError, StorageError}, + error::Error, storage::storage_value::StorageValue, }; use codec::Decode; @@ -60,23 +60,24 @@ where T: Config, Client: OfflineClientT, { + /// Access a specific storage entry. This returns a [`StorageEntryClient`] which can be used to + /// interact with the storage entry at this specific block. pub fn entry(&'_ self, address: Addr) -> Result, Error> { - subxt_core::storage::validate(&address, &self.client.metadata())?; + subxt_core::storage::validate(&address, &self.metadata)?; use frame_decode::storage::StorageTypeInfo; let types = self.metadata.types(); let info = self - .client - .metadata() + .metadata .storage_info(address.pallet_name(), address.entry_name())?; Ok(StorageEntryClient { - client: self.client.clone(), - block_ref: self.block_ref.clone(), + client: self.client.clone(), + block_ref: self.block_ref.clone(), address, info, types, - _marker: core::marker::PhantomData + _marker: core::marker::PhantomData }) } } diff --git a/subxt/src/storage/storage_entry.rs b/subxt/src/storage/storage_entry.rs index 0f96b25bbf..1ad0333006 100644 --- a/subxt/src/storage/storage_entry.rs +++ b/subxt/src/storage/storage_entry.rs @@ -4,8 +4,9 @@ use super::storage_value::StorageValue; use super::storage_key::StorageKey; +use crate::error::StorageKeyError; use subxt_core::storage::address::Address; -use frame_decode::storage::StorageInfo; +use frame_decode::storage::{StorageInfo, IntoDecodableValues}; use scale_info::PortableRegistry; use std::borrow::Cow; @@ -42,7 +43,10 @@ impl<'entry, 'atblock, Addr: Address> StorageEntry<'entry, 'atblock, Addr> { /// Decode the key for this storage entry. This gives back a type from which we can /// decode specific parts of the key hash (where applicable). - pub fn key(&'_ self) -> Result, StorageKeyError> { + pub fn key(&'_ self) -> Result, StorageKeyError> + where + Addr::KeyParts: IntoDecodableValues, + { StorageKey::new(self.value.info, self.value.types, &self.key) } diff --git a/subxt/src/storage/storage_key.rs b/subxt/src/storage/storage_key.rs index 30f15d825b..586ef3ed6a 100644 --- a/subxt/src/storage/storage_key.rs +++ b/subxt/src/storage/storage_key.rs @@ -2,6 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use crate::error::StorageKeyError; use frame_decode::storage::{StorageInfo, StorageKey as StorageKeyPartInfo, IntoDecodableValues}; use scale_info::PortableRegistry; use core::marker::PhantomData; diff --git a/subxt/src/storage/storage_value.rs b/subxt/src/storage/storage_value.rs index 85cd19ae04..5e4510f318 100644 --- a/subxt/src/storage/storage_value.rs +++ b/subxt/src/storage/storage_value.rs @@ -8,7 +8,7 @@ use scale_info::PortableRegistry; use core::marker::PhantomData; use std::borrow::Cow; -use crate::Error; +use crate::error::StorageValueError; /// This represents a storage value. pub struct StorageValue<'entry, 'atblock, Value> { @@ -39,22 +39,24 @@ impl<'entry, 'atblock, Value: DecodeAsType> StorageValue<'entry, 'atblock, Value } /// Decode this storage value into the provided response type. - pub fn decode(&self) -> Result { + pub fn decode(&self) -> Result { self.decode_as::() } /// Decode this storage value into an arbitrary type. - pub fn decode_as(&self) -> Result { + pub fn decode_as(&self) -> Result { let cursor = &mut &*self.bytes; let value = T::decode_as_type( cursor, self.info.value_id, self.types, - ).map_err(|e| todo!("Define proper errors"))?; + ).map_err(|reason| StorageValueError::DecodeError { reason })?; if !cursor.is_empty() { - return Err(todo!("Define proper errors")); + return Err(StorageValueError::LeftoverBytes { + leftover_bytes: cursor.to_vec(), + }); } Ok(value)