gear_subxt/error/
dispatch_error.rs

1// Copyright 2019-2023 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! A representation of the dispatch error; an error returned when
6//! something fails in trying to submit/execute a transaction.
7
8use crate::metadata::{DecodeWithMetadata, Metadata};
9use core::fmt::Debug;
10use scale_decode::visitor::DecodeAsTypeResult;
11use std::borrow::Cow;
12
13use super::{Error, MetadataError};
14use crate::error::RootError;
15
16/// An error dispatching a transaction.
17#[derive(Debug, thiserror::Error, PartialEq, Eq)]
18#[non_exhaustive]
19pub enum DispatchError {
20    /// Some error occurred.
21    #[error("Some unknown error occurred.")]
22    Other,
23    /// Failed to lookup some data.
24    #[error("Failed to lookup some data.")]
25    CannotLookup,
26    /// A bad origin.
27    #[error("Bad origin.")]
28    BadOrigin,
29    /// A custom error in a module.
30    #[error("Pallet error: {0}")]
31    Module(ModuleError),
32    /// At least one consumer is remaining so the account cannot be destroyed.
33    #[error("At least one consumer is remaining so the account cannot be destroyed.")]
34    ConsumerRemaining,
35    /// There are no providers so the account cannot be created.
36    #[error("There are no providers so the account cannot be created.")]
37    NoProviders,
38    /// There are too many consumers so the account cannot be created.
39    #[error("There are too many consumers so the account cannot be created.")]
40    TooManyConsumers,
41    /// An error to do with tokens.
42    #[error("Token error: {0}")]
43    Token(TokenError),
44    /// An arithmetic error.
45    #[error("Arithmetic error: {0}")]
46    Arithmetic(ArithmeticError),
47    /// The number of transactional layers has been reached, or we are not in a transactional layer.
48    #[error("Transactional error: {0}")]
49    Transactional(TransactionalError),
50    /// Resources exhausted, e.g. attempt to read/write data which is too large to manipulate.
51    #[error(
52        "Resources exhausted, e.g. attempt to read/write data which is too large to manipulate."
53    )]
54    Exhausted,
55    /// The state is corrupt; this is generally not going to fix itself.
56    #[error("The state is corrupt; this is generally not going to fix itself.")]
57    Corruption,
58    /// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later.
59    #[error(
60        "Some resource (e.g. a preimage) is unavailable right now. This might fix itself later."
61    )]
62    Unavailable,
63}
64
65/// An error relating to tokens when dispatching a transaction.
66#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
67#[non_exhaustive]
68pub enum TokenError {
69    /// Funds are unavailable.
70    #[error("Funds are unavailable.")]
71    FundsUnavailable,
72    /// Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved.
73    #[error("Some part of the balance gives the only provider reference to the account and thus cannot be (re)moved.")]
74    OnlyProvider,
75    /// Account cannot exist with the funds that would be given.
76    #[error("Account cannot exist with the funds that would be given.")]
77    BelowMinimum,
78    /// Account cannot be created.
79    #[error("Account cannot be created.")]
80    CannotCreate,
81    /// The asset in question is unknown.
82    #[error("The asset in question is unknown.")]
83    UnknownAsset,
84    /// Funds exist but are frozen.
85    #[error("Funds exist but are frozen.")]
86    Frozen,
87    /// Operation is not supported by the asset.
88    #[error("Operation is not supported by the asset.")]
89    Unsupported,
90    /// Account cannot be created for a held balance.
91    #[error("Account cannot be created for a held balance.")]
92    CannotCreateHold,
93    /// Withdrawal would cause unwanted loss of account.
94    #[error("Withdrawal would cause unwanted loss of account.")]
95    NotExpendable,
96}
97
98/// An error relating to arithmetic when dispatching a transaction.
99#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
100#[non_exhaustive]
101pub enum ArithmeticError {
102    /// Underflow.
103    #[error("Underflow.")]
104    Underflow,
105    /// Overflow.
106    #[error("Overflow.")]
107    Overflow,
108    /// Division by zero.
109    #[error("Division by zero.")]
110    DivisionByZero,
111}
112
113/// An error relating to thr transactional layers when dispatching a transaction.
114#[derive(scale_decode::DecodeAsType, Debug, thiserror::Error, PartialEq, Eq)]
115#[non_exhaustive]
116pub enum TransactionalError {
117    /// Too many transactional layers have been spawned.
118    #[error("Too many transactional layers have been spawned.")]
119    LimitReached,
120    /// A transactional layer was expected, but does not exist.
121    #[error("A transactional layer was expected, but does not exist.")]
122    NoLayer,
123}
124
125/// Details about a module error that has occurred.
126#[derive(Clone, Debug, thiserror::Error)]
127#[non_exhaustive]
128pub struct ModuleError {
129    metadata: Metadata,
130    raw: RawModuleError,
131}
132
133impl PartialEq for ModuleError {
134    fn eq(&self, other: &Self) -> bool {
135        // A module error is the same if the raw underlying details are the same.
136        self.raw == other.raw
137    }
138}
139
140impl Eq for ModuleError {}
141
142impl std::fmt::Display for ModuleError {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        let Ok(details) = self.details() else {
145            return f.write_str("Unknown pallet error (pallet and error details cannot be retrieved)");
146        };
147
148        let pallet = details.pallet.name();
149        let error = &details.variant.name;
150        write!(f, "Pallet error {pallet}::{error}")
151    }
152}
153
154impl ModuleError {
155    /// Return more details about this error.
156    pub fn details(&self) -> Result<ModuleErrorDetails, MetadataError> {
157        let pallet = self.metadata.pallet_by_index_err(self.raw.pallet_index)?;
158        let variant = pallet
159            .error_variant_by_index(self.raw.error[0])
160            .ok_or_else(|| MetadataError::VariantIndexNotFound(self.raw.error[0]))?;
161
162        Ok(ModuleErrorDetails { pallet, variant })
163    }
164
165    /// Return the underlying module error data that was decoded.
166    pub fn raw(&self) -> RawModuleError {
167        self.raw
168    }
169
170    /// Attempts to decode the ModuleError into a value implementing the trait `RootError`
171    /// where the actual type of value is the generated top level enum `Error`.
172    pub fn as_root_error<E: RootError>(&self) -> Result<E, Error> {
173        E::root_error(
174            &self.raw.error,
175            self.details()?.pallet.name(),
176            &self.metadata,
177        )
178    }
179}
180
181/// Details about the module error.
182pub struct ModuleErrorDetails<'a> {
183    /// The pallet that the error came from
184    pub pallet: crate::metadata::types::PalletMetadata<'a>,
185    /// The variant representing the error
186    pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
187}
188
189/// The error details about a module error that has occurred.
190///
191/// **Note**: Structure used to obtain the underlying bytes of a ModuleError.
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193pub struct RawModuleError {
194    /// Index of the pallet that the error came from.
195    pub pallet_index: u8,
196    /// Raw error bytes.
197    pub error: [u8; 4],
198}
199
200impl RawModuleError {
201    /// Obtain the error index from the underlying byte data.
202    pub fn error_index(&self) -> u8 {
203        // Error index is utilized as the first byte from the error array.
204        self.error[0]
205    }
206}
207
208impl DispatchError {
209    /// Attempt to decode a runtime [`DispatchError`].
210    #[doc(hidden)]
211    pub fn decode_from<'a>(
212        bytes: impl Into<Cow<'a, [u8]>>,
213        metadata: Metadata,
214    ) -> Result<Self, super::Error> {
215        let bytes = bytes.into();
216        let dispatch_error_ty_id = metadata
217            .dispatch_error_ty()
218            .ok_or(MetadataError::DispatchErrorNotFound)?;
219
220        // The aim is to decode our bytes into roughly this shape. This is copied from
221        // `sp_runtime::DispatchError`; we need the variant names and any inner variant
222        // names/shapes to line up in order for decoding to be successful.
223        #[derive(scale_decode::DecodeAsType)]
224        enum DecodedDispatchError {
225            Other,
226            CannotLookup,
227            BadOrigin,
228            Module(DecodedModuleErrorBytes),
229            ConsumerRemaining,
230            NoProviders,
231            TooManyConsumers,
232            Token(TokenError),
233            Arithmetic(ArithmeticError),
234            Transactional(TransactionalError),
235            Exhausted,
236            Corruption,
237            Unavailable,
238        }
239
240        // ModuleError is a bit special; we want to support being decoded from either
241        // a legacy format of 2 bytes, or a newer format of 5 bytes. So, just grab the bytes
242        // out when decoding to manually work with them.
243        struct DecodedModuleErrorBytes(Vec<u8>);
244        struct DecodedModuleErrorBytesVisitor;
245        impl scale_decode::Visitor for DecodedModuleErrorBytesVisitor {
246            type Error = scale_decode::Error;
247            type Value<'scale, 'info> = DecodedModuleErrorBytes;
248            fn unchecked_decode_as_type<'scale, 'info>(
249                self,
250                input: &mut &'scale [u8],
251                _type_id: scale_decode::visitor::TypeId,
252                _types: &'info scale_info::PortableRegistry,
253            ) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>>
254            {
255                DecodeAsTypeResult::Decoded(Ok(DecodedModuleErrorBytes(input.to_vec())))
256            }
257        }
258        impl scale_decode::IntoVisitor for DecodedModuleErrorBytes {
259            type Visitor = DecodedModuleErrorBytesVisitor;
260            fn into_visitor() -> Self::Visitor {
261                DecodedModuleErrorBytesVisitor
262            }
263        }
264
265        // Decode into our temporary error:
266        let decoded_dispatch_err = DecodedDispatchError::decode_with_metadata(
267            &mut &*bytes,
268            dispatch_error_ty_id,
269            &metadata,
270        )?;
271
272        // Convert into the outward-facing error, mainly by handling the Module variant.
273        let dispatch_error = match decoded_dispatch_err {
274            // Mostly we don't change anything from our decoded to our outward-facing error:
275            DecodedDispatchError::Other => DispatchError::Other,
276            DecodedDispatchError::CannotLookup => DispatchError::CannotLookup,
277            DecodedDispatchError::BadOrigin => DispatchError::BadOrigin,
278            DecodedDispatchError::ConsumerRemaining => DispatchError::ConsumerRemaining,
279            DecodedDispatchError::NoProviders => DispatchError::NoProviders,
280            DecodedDispatchError::TooManyConsumers => DispatchError::TooManyConsumers,
281            DecodedDispatchError::Token(val) => DispatchError::Token(val),
282            DecodedDispatchError::Arithmetic(val) => DispatchError::Arithmetic(val),
283            DecodedDispatchError::Transactional(val) => DispatchError::Transactional(val),
284            DecodedDispatchError::Exhausted => DispatchError::Exhausted,
285            DecodedDispatchError::Corruption => DispatchError::Corruption,
286            DecodedDispatchError::Unavailable => DispatchError::Unavailable,
287            // But we apply custom logic to transform the module error into the outward facing version:
288            DecodedDispatchError::Module(module_bytes) => {
289                let module_bytes = module_bytes.0;
290
291                // The old version is 2 bytes; a pallet and error index.
292                // The new version is 5 bytes; a pallet and error index and then 3 extra bytes.
293                let raw = if module_bytes.len() == 2 {
294                    RawModuleError {
295                        pallet_index: module_bytes[0],
296                        error: [module_bytes[1], 0, 0, 0],
297                    }
298                } else if module_bytes.len() == 5 {
299                    RawModuleError {
300                        pallet_index: module_bytes[0],
301                        error: [
302                            module_bytes[1],
303                            module_bytes[2],
304                            module_bytes[3],
305                            module_bytes[4],
306                        ],
307                    }
308                } else {
309                    tracing::warn!("Can't decode error sp_runtime::DispatchError: bytes do not match known shapes");
310                    // Return _all_ of the bytes; every "unknown" return should be consistent.
311                    return Err(super::Error::Unknown(bytes.to_vec()));
312                };
313
314                // And return our outward-facing version:
315                DispatchError::Module(ModuleError { metadata, raw })
316            }
317        };
318
319        Ok(dispatch_error)
320    }
321}