use super::{DispatchErrorDecodeError, ModuleErrorDecodeError, ModuleErrorDetailsError};
use crate::metadata::Metadata;
use core::fmt::Debug;
use scale_decode::{visitor::DecodeAsTypeResult, DecodeAsType, TypeResolver};
use std::{borrow::Cow, marker::PhantomData};
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
pub enum DispatchError {
#[error("Some unknown error occurred.")]
Other,
#[error("Failed to lookup some data.")]
CannotLookup,
#[error("Bad origin.")]
BadOrigin,
#[error("Pallet error: {0}")]
Module(ModuleError),
#[error("At least one consumer is remaining so the account cannot be destroyed.")]
ConsumerRemaining,
#[error("There are no providers so the account cannot be created.")]
NoProviders,
#[error("There are too many consumers so the account cannot be created.")]
TooManyConsumers,
#[error("Token error: {0}")]
Token(TokenError),
#[error("Arithmetic error: {0}")]
Arithmetic(ArithmeticError),
#[error("Transactional error: {0}")]
Transactional(TransactionalError),
#[error(
"Resources exhausted, e.g. attempt to read/write data which is too large to manipulate."
)]
Exhausted,
#[error("The state is corrupt; this is generally not going to fix itself.")]
Corruption,
#[error(
"Some resource (e.g. a preimage) is unavailable right now. This might fix itself later."
)]
Unavailable,
#[error("Root origin is not allowed.")]
RootNotAllowed,
}
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TokenError {
#[error("Funds are unavailable.")]
FundsUnavailable,
#[error(
"Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved."
)]
OnlyProvider,
#[error("Account cannot exist with the funds that would be given.")]
BelowMinimum,
#[error("Account cannot be created.")]
CannotCreate,
#[error("The asset in question is unknown.")]
UnknownAsset,
#[error("Funds exist but are frozen.")]
Frozen,
#[error("Operation is not supported by the asset.")]
Unsupported,
#[error("Account cannot be created for a held balance.")]
CannotCreateHold,
#[error("Withdrawal would cause unwanted loss of account.")]
NotExpendable,
#[error("Account cannot receive the assets.")]
Blocked,
}
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum ArithmeticError {
#[error("Underflow.")]
Underflow,
#[error("Overflow.")]
Overflow,
#[error("Division by zero.")]
DivisionByZero,
}
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransactionalError {
#[error("Too many transactional layers have been spawned.")]
LimitReached,
#[error("A transactional layer was expected, but does not exist.")]
NoLayer,
}
#[derive(Clone, thiserror::Error)]
#[non_exhaustive]
pub struct ModuleError {
metadata: Metadata,
bytes: [u8; 5],
}
impl PartialEq for ModuleError {
fn eq(&self, other: &Self) -> bool {
self.bytes == other.bytes
}
}
impl Eq for ModuleError {}
impl Debug for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let details = self.details_string();
write!(f, "ModuleError(<{details}>)")
}
}
impl std::fmt::Display for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let details = self.details_string();
write!(f, "{details}")
}
}
impl ModuleError {
pub fn details(&self) -> Result<ModuleErrorDetails<'_>, ModuleErrorDetailsError> {
let pallet = self
.metadata
.pallet_by_error_index(self.pallet_index())
.ok_or(ModuleErrorDetailsError::PalletNotFound { pallet_index: self.pallet_index() })?;
let variant = pallet.error_variant_by_index(self.error_index()).ok_or_else(|| {
ModuleErrorDetailsError::ErrorVariantNotFound {
pallet_name: pallet.name().into(),
error_index: self.error_index(),
}
})?;
Ok(ModuleErrorDetails { pallet, variant })
}
pub fn details_string(&self) -> String {
match self.details() {
Ok(details) => format!(
"{pallet_name}::{variant_name}",
pallet_name = details.pallet.name(),
variant_name = details.variant.name,
),
Err(_) => format!(
"Unknown pallet error '{bytes:?}' (pallet and error details cannot be retrieved)",
bytes = self.bytes
),
}
}
pub fn bytes(&self) -> [u8; 5] {
self.bytes
}
pub fn pallet_index(&self) -> u8 {
self.bytes[0]
}
pub fn error_index(&self) -> u8 {
self.bytes[1]
}
pub fn as_root_error<E: DecodeAsType>(&self) -> Result<E, ModuleErrorDecodeError> {
let decoded = E::decode_as_type(
&mut &self.bytes[..],
self.metadata.outer_enums().error_enum_ty(),
self.metadata.types(),
)
.map_err(ModuleErrorDecodeError)?;
Ok(decoded)
}
}
pub struct ModuleErrorDetails<'a> {
pub pallet: pezkuwi_subxt_metadata::PalletMetadata<'a>,
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
impl DispatchError {
#[doc(hidden)]
pub fn decode_from<'a>(
bytes: impl Into<Cow<'a, [u8]>>,
metadata: Metadata,
) -> Result<Self, DispatchErrorDecodeError> {
let bytes = bytes.into();
let dispatch_error_ty_id = metadata
.dispatch_error_ty()
.ok_or(DispatchErrorDecodeError::DispatchErrorTypeIdNotFound)?;
#[derive(scale_decode::DecodeAsType)]
enum DecodedDispatchError {
Other,
CannotLookup,
BadOrigin,
Module(DecodedModuleErrorBytes),
ConsumerRemaining,
NoProviders,
TooManyConsumers,
Token(TokenError),
Arithmetic(ArithmeticError),
Transactional(TransactionalError),
Exhausted,
Corruption,
Unavailable,
RootNotAllowed,
}
struct DecodedModuleErrorBytes(Vec<u8>);
struct DecodedModuleErrorBytesVisitor<R: TypeResolver>(PhantomData<R>);
impl<R: TypeResolver> scale_decode::Visitor for DecodedModuleErrorBytesVisitor<R> {
type Error = scale_decode::Error;
type Value<'scale, 'info> = DecodedModuleErrorBytes;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
_type_id: R::TypeId,
_types: &'info R,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
DecodeAsTypeResult::Decoded(Ok(DecodedModuleErrorBytes(input.to_vec())))
}
}
impl scale_decode::IntoVisitor for DecodedModuleErrorBytes {
type AnyVisitor<R: TypeResolver> = DecodedModuleErrorBytesVisitor<R>;
fn into_visitor<R: TypeResolver>() -> DecodedModuleErrorBytesVisitor<R> {
DecodedModuleErrorBytesVisitor(PhantomData)
}
}
let decoded_dispatch_err = DecodedDispatchError::decode_as_type(
&mut &*bytes,
dispatch_error_ty_id,
metadata.types(),
)
.map_err(DispatchErrorDecodeError::CouldNotDecodeDispatchError)?;
let dispatch_error = match decoded_dispatch_err {
DecodedDispatchError::Other => DispatchError::Other,
DecodedDispatchError::CannotLookup => DispatchError::CannotLookup,
DecodedDispatchError::BadOrigin => DispatchError::BadOrigin,
DecodedDispatchError::ConsumerRemaining => DispatchError::ConsumerRemaining,
DecodedDispatchError::NoProviders => DispatchError::NoProviders,
DecodedDispatchError::TooManyConsumers => DispatchError::TooManyConsumers,
DecodedDispatchError::Token(val) => DispatchError::Token(val),
DecodedDispatchError::Arithmetic(val) => DispatchError::Arithmetic(val),
DecodedDispatchError::Transactional(val) => DispatchError::Transactional(val),
DecodedDispatchError::Exhausted => DispatchError::Exhausted,
DecodedDispatchError::Corruption => DispatchError::Corruption,
DecodedDispatchError::Unavailable => DispatchError::Unavailable,
DecodedDispatchError::RootNotAllowed => DispatchError::RootNotAllowed,
DecodedDispatchError::Module(module_bytes) => {
let module_bytes = module_bytes.0;
let bytes = if module_bytes.len() == 2 {
[module_bytes[0], module_bytes[1], 0, 0, 0]
} else if module_bytes.len() == 5 {
[
module_bytes[0],
module_bytes[1],
module_bytes[2],
module_bytes[3],
module_bytes[4],
]
} else {
tracing::warn!(
"Can't decode error sp_runtime::DispatchError: bytes do not match known shapes"
);
return Err(DispatchErrorDecodeError::CouldNotDecodeModuleError {
bytes: bytes.to_vec(),
});
};
DispatchError::Module(ModuleError { metadata, bytes })
},
};
Ok(dispatch_error)
}
}