pezkuwi-subxt 0.44.0

Submit extrinsics (transactions) to a Pezkuwi/Bizinikiwi node via RPC
Documentation
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

//! A representation of the dispatch error; an error returned when
//! something fails in trying to submit/execute a transaction.

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};

/// An error dispatching a transaction.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
pub enum DispatchError {
	/// Some error occurred.
	#[error("Some unknown error occurred.")]
	Other,
	/// Failed to lookup some data.
	#[error("Failed to lookup some data.")]
	CannotLookup,
	/// A bad origin.
	#[error("Bad origin.")]
	BadOrigin,
	/// A custom error in a module.
	#[error("Pallet error: {0}")]
	Module(ModuleError),
	/// At least one consumer is remaining so the account cannot be destroyed.
	#[error("At least one consumer is remaining so the account cannot be destroyed.")]
	ConsumerRemaining,
	/// There are no providers so the account cannot be created.
	#[error("There are no providers so the account cannot be created.")]
	NoProviders,
	/// There are too many consumers so the account cannot be created.
	#[error("There are too many consumers so the account cannot be created.")]
	TooManyConsumers,
	/// An error to do with tokens.
	#[error("Token error: {0}")]
	Token(TokenError),
	/// An arithmetic error.
	#[error("Arithmetic error: {0}")]
	Arithmetic(ArithmeticError),
	/// The number of transactional layers has been reached, or we are not in a transactional layer.
	#[error("Transactional error: {0}")]
	Transactional(TransactionalError),
	/// Resources exhausted, e.g. attempt to read/write data which is too large to manipulate.
	#[error(
		"Resources exhausted, e.g. attempt to read/write data which is too large to manipulate."
	)]
	Exhausted,
	/// The state is corrupt; this is generally not going to fix itself.
	#[error("The state is corrupt; this is generally not going to fix itself.")]
	Corruption,
	/// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later.
	#[error(
		"Some resource (e.g. a preimage) is unavailable right now. This might fix itself later."
	)]
	Unavailable,
	/// Root origin is not allowed.
	#[error("Root origin is not allowed.")]
	RootNotAllowed,
}

/// An error relating to tokens when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TokenError {
	/// Funds are unavailable.
	#[error("Funds are unavailable.")]
	FundsUnavailable,
	/// Some part of the balance gives the only provider reference to the account and thus cannot be
	/// (re)moved.
	#[error(
		"Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved."
	)]
	OnlyProvider,
	/// Account cannot exist with the funds that would be given.
	#[error("Account cannot exist with the funds that would be given.")]
	BelowMinimum,
	/// Account cannot be created.
	#[error("Account cannot be created.")]
	CannotCreate,
	/// The asset in question is unknown.
	#[error("The asset in question is unknown.")]
	UnknownAsset,
	/// Funds exist but are frozen.
	#[error("Funds exist but are frozen.")]
	Frozen,
	/// Operation is not supported by the asset.
	#[error("Operation is not supported by the asset.")]
	Unsupported,
	/// Account cannot be created for a held balance.
	#[error("Account cannot be created for a held balance.")]
	CannotCreateHold,
	/// Withdrawal would cause unwanted loss of account.
	#[error("Withdrawal would cause unwanted loss of account.")]
	NotExpendable,
	/// Account cannot receive the assets.
	#[error("Account cannot receive the assets.")]
	Blocked,
}

/// An error relating to arithmetic when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum ArithmeticError {
	/// Underflow.
	#[error("Underflow.")]
	Underflow,
	/// Overflow.
	#[error("Overflow.")]
	Overflow,
	/// Division by zero.
	#[error("Division by zero.")]
	DivisionByZero,
}

/// An error relating to the transactional layers when dispatching a transaction.
#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransactionalError {
	/// Too many transactional layers have been spawned.
	#[error("Too many transactional layers have been spawned.")]
	LimitReached,
	/// A transactional layer was expected, but does not exist.
	#[error("A transactional layer was expected, but does not exist.")]
	NoLayer,
}

/// Details about a module error that has occurred.
#[derive(Clone, thiserror::Error)]
#[non_exhaustive]
pub struct ModuleError {
	metadata: Metadata,
	/// Bytes representation:
	///  - `bytes[0]`:   pallet index
	///  - `bytes[1]`:   error index
	///  - `bytes[2..]`: 3 bytes specific for the module error
	bytes: [u8; 5],
}

impl PartialEq for ModuleError {
	fn eq(&self, other: &Self) -> bool {
		// A module error is the same if the raw underlying details are the same.
		self.bytes == other.bytes
	}
}

impl Eq for ModuleError {}

/// Custom `Debug` implementation, ignores the very large `metadata` field, using it instead (as
/// intended) to resolve the actual pallet and error names. This is much more useful for debugging.
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 {
	/// Return more details about this error.
	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 })
	}

	/// 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!(
				"{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
			),
		}
	}

	/// Return the underlying module error data that was decoded.
	pub fn bytes(&self) -> [u8; 5] {
		self.bytes
	}

	/// Obtain the pallet index from the underlying byte data.
	pub fn pallet_index(&self) -> u8 {
		self.bytes[0]
	}

	/// Obtain the error index from the underlying byte data.
	pub fn error_index(&self) -> u8 {
		self.bytes[1]
	}

	/// Attempts to decode the ModuleError into the top outer Error enum.
	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)
	}
}

/// Details about the module error.
pub struct ModuleErrorDetails<'a> {
	/// The pallet that the error is in
	pub pallet: pezkuwi_subxt_metadata::PalletMetadata<'a>,
	/// The variant representing the error
	pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}

impl DispatchError {
	/// Attempt to decode a runtime [`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)?;

		// 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
		// names/shapes to line up in order for decoding to be successful.
		#[derive(scale_decode::DecodeAsType)]
		enum DecodedDispatchError {
			Other,
			CannotLookup,
			BadOrigin,
			Module(DecodedModuleErrorBytes),
			ConsumerRemaining,
			NoProviders,
			TooManyConsumers,
			Token(TokenError),
			Arithmetic(ArithmeticError),
			Transactional(TransactionalError),
			Exhausted,
			Corruption,
			Unavailable,
			RootNotAllowed,
		}

		// ModuleError is a bit special; we want to support being decoded from either
		// a legacy format of 2 bytes, or a newer format of 5 bytes. So, just grab the bytes
		// out when decoding to manually work with them.
		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)
			}
		}

		// Decode into our temporary error:
		let decoded_dispatch_err = DecodedDispatchError::decode_as_type(
			&mut &*bytes,
			dispatch_error_ty_id,
			metadata.types(),
		)
		.map_err(DispatchErrorDecodeError::CouldNotDecodeDispatchError)?;

		// Convert into the outward-facing error, mainly by handling the Module variant.
		let dispatch_error = match decoded_dispatch_err {
			// Mostly we don't change anything from our decoded to our outward-facing error:
			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,
			// But we apply custom logic to transform the module error into the outward facing
			// version:
			DecodedDispatchError::Module(module_bytes) => {
				let module_bytes = module_bytes.0;

				// The old version is 2 bytes; a pallet and error index.
				// The new version is 5 bytes; a pallet and error index and then 3 extra bytes.
				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 _all_ of the bytes; every "unknown" return should be consistent.
					return Err(DispatchErrorDecodeError::CouldNotDecodeModuleError {
						bytes: bytes.to_vec(),
					});
				};

				// And return our outward-facing version:
				DispatchError::Module(ModuleError { metadata, bytes })
			},
		};

		Ok(dispatch_error)
	}
}