cosmwasm_std/errors/
std_error.rs

1use alloc::string::ToString;
2use core::fmt;
3use std::{error::Error, ops::Deref, str, string};
4
5use super::BT;
6
7use crate::{
8    errors::{RecoverPubkeyError, VerificationError},
9    Decimal256RangeExceeded, DecimalRangeExceeded, SignedDecimal256RangeExceeded,
10    SignedDecimalRangeExceeded,
11};
12
13mod sealed {
14    pub trait Sealed {}
15
16    impl<T> Sealed for Result<T, super::StdError> {}
17}
18
19pub trait StdResultExt<T>: sealed::Sealed {
20    fn unwrap_std_error(self) -> Result<T, Box<dyn Error + Send + Sync>>;
21}
22
23impl<T> StdResultExt<T> for Result<T, super::StdError> {
24    fn unwrap_std_error(self) -> Result<T, Box<dyn Error + Send + Sync>> {
25        self.map_err(|err| err.0.inner)
26    }
27}
28
29/// Structured error type for init, execute and query.
30///
31/// This can be serialized and passed over the Wasm/VM boundary, which allows us to use structured
32/// error types in e.g. integration tests. In that process backtraces are stripped off.
33///
34/// The prefix "Std" means "the standard error within the standard library". This is not the only
35/// result/error type in cosmwasm-std.
36///
37/// When new cases are added, they should describe the problem rather than what was attempted (e.g.
38/// InvalidBase64 is preferred over Base64DecodingErr). In the long run this allows us to get rid of
39/// the duplication in "StdError::FooErr".
40///
41/// Checklist for adding a new error:
42/// - Add enum case
43/// - Add creator function in std_error_helpers.rs
44#[derive(Debug)]
45pub struct StdError(Box<InnerError>);
46
47#[derive(Debug)]
48struct InnerError {
49    backtrace: BT,
50    kind: ErrorKind,
51    inner: Box<dyn Error + Send + Sync>,
52}
53
54const _: () = {
55    // Assert smolness (˶ᵔ ᵕ ᵔ˶)
56    assert!(std::mem::size_of::<StdError>() == std::mem::size_of::<usize>());
57};
58
59impl AsRef<dyn Error + Send + Sync> for StdError {
60    fn as_ref(&self) -> &(dyn Error + Send + Sync + 'static) {
61        &*self.0.inner
62    }
63}
64
65impl Deref for StdError {
66    type Target = dyn Error + Send + Sync;
67
68    fn deref(&self) -> &Self::Target {
69        &*self.0.inner
70    }
71}
72
73impl StdError {
74    pub fn msg<D>(msg: D) -> Self
75    where
76        D: fmt::Display,
77    {
78        Self(Box::new(InnerError {
79            backtrace: BT::capture(),
80            kind: ErrorKind::Other,
81            inner: msg.to_string().into(),
82        }))
83    }
84
85    pub fn backtrace(&self) -> &BT {
86        &self.0.backtrace
87    }
88
89    pub fn is<T>(&self) -> bool
90    where
91        T: Error + 'static,
92    {
93        self.0.inner.is::<T>()
94    }
95
96    pub fn kind(&self) -> ErrorKind {
97        self.0.kind
98    }
99
100    pub fn with_kind(mut self, kind: ErrorKind) -> Self {
101        self.0.kind = kind;
102        self
103    }
104}
105
106impl fmt::Display for StdError {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, "kind: {:?}, error: {}", self.0.kind, self.0.inner)
109    }
110}
111
112// Impossible to implement because of blanket `From` impls :(
113/*impl Error for StdError {
114    fn source(&self) -> Option<&(dyn Error + 'static)> {
115        self.0.inner.source()
116    }
117}*/
118
119impl<E> From<E> for StdError
120where
121    E: Error + Send + Sync + 'static,
122{
123    fn from(value: E) -> Self {
124        let inner: Box<dyn Error + Send + Sync> = Box::new(value);
125
126        // "mom, can we have specialization?"
127        // "we have specialization at home"
128        // specialization at home:
129        let kind = if inner.is::<str::Utf8Error>()
130            || inner.is::<string::FromUtf8Error>()
131            || inner.is::<core::num::ParseIntError>()
132            || inner.is::<CoinFromStrError>()
133        {
134            ErrorKind::Parsing
135        } else if inner.is::<ConversionOverflowError>()
136            || inner.is::<OverflowError>()
137            || inner.is::<RoundUpOverflowError>()
138            || inner.is::<RoundDownOverflowError>()
139            || inner.is::<DecimalRangeExceeded>()
140            || inner.is::<Decimal256RangeExceeded>()
141            || inner.is::<SignedDecimalRangeExceeded>()
142            || inner.is::<SignedDecimal256RangeExceeded>()
143        {
144            ErrorKind::Overflow
145        } else if inner.is::<serde_json::Error>()
146            || inner.is::<rmp_serde::encode::Error>()
147            || inner.is::<rmp_serde::decode::Error>()
148        {
149            ErrorKind::Serialization
150        } else if inner.is::<RecoverPubkeyError>() || inner.is::<VerificationError>() {
151            ErrorKind::Cryptography
152        } else if inner.is::<hex::FromHexError>() || inner.is::<base64::DecodeError>() {
153            ErrorKind::Encoding
154        } else {
155            ErrorKind::Other
156        };
157
158        Self(Box::new(InnerError {
159            backtrace: BT::capture(),
160            kind,
161            inner,
162        }))
163    }
164}
165
166#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
167#[non_exhaustive]
168pub enum ErrorKind {
169    Cryptography,
170    Encoding,
171    InvalidData,
172    Overflow,
173    Parsing,
174    Serialization,
175
176    Other,
177}
178
179/// The return type for init, execute and query. Since the error type cannot be serialized to JSON,
180/// this is only available within the contract and its unit tests.
181///
182/// The prefix "Core"/"Std" means "the standard result within the core/standard library". This is not the only
183/// result/error type in cosmwasm-core/cosmwasm-std.
184pub type StdResult<T, E = StdError> = core::result::Result<T, E>;
185
186#[derive(Debug, PartialEq, Eq)]
187pub enum OverflowOperation {
188    Add,
189    Sub,
190    Mul,
191    Pow,
192    Shr,
193    Shl,
194}
195
196impl fmt::Display for OverflowOperation {
197    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198        write!(f, "{self:?}")
199    }
200}
201
202#[derive(Debug, PartialEq, Eq, thiserror::Error)]
203#[error("Cannot {operation} with given operands")]
204pub struct OverflowError {
205    pub operation: OverflowOperation,
206}
207
208impl OverflowError {
209    pub fn new(operation: OverflowOperation) -> Self {
210        Self { operation }
211    }
212}
213
214/// The error returned by [`TryFrom`] conversions that overflow, for example
215/// when converting from [`Uint256`] to [`Uint128`].
216///
217/// [`TryFrom`]: core::convert::TryFrom
218/// [`Uint256`]: crate::Uint256
219/// [`Uint128`]: crate::Uint128
220#[derive(Debug, PartialEq, Eq, thiserror::Error)]
221#[error("Error converting {source_type} to {target_type}")]
222pub struct ConversionOverflowError {
223    pub source_type: &'static str,
224    pub target_type: &'static str,
225}
226
227impl ConversionOverflowError {
228    pub fn new(source_type: &'static str, target_type: &'static str) -> Self {
229        Self {
230            source_type,
231            target_type,
232        }
233    }
234}
235
236#[derive(Debug, Default, PartialEq, Eq, thiserror::Error)]
237#[error("Cannot divide by zero")]
238pub struct DivideByZeroError;
239
240impl DivideByZeroError {
241    pub fn new() -> Self {
242        Self
243    }
244}
245
246#[derive(Debug, PartialEq, Eq, thiserror::Error)]
247pub enum DivisionError {
248    #[error("Divide by zero")]
249    DivideByZero,
250
251    #[error("Overflow in division")]
252    Overflow,
253}
254
255#[derive(Debug, PartialEq, Eq, thiserror::Error)]
256pub enum CheckedMultiplyFractionError {
257    #[error("{_0}")]
258    DivideByZero(#[from] DivideByZeroError),
259
260    #[error("{_0}")]
261    ConversionOverflow(#[from] ConversionOverflowError),
262
263    #[error("{_0}")]
264    Overflow(#[from] OverflowError),
265}
266
267#[derive(Debug, PartialEq, Eq, thiserror::Error)]
268pub enum CheckedMultiplyRatioError {
269    #[error("Denominator must not be zero")]
270    DivideByZero,
271
272    #[error("Multiplication overflow")]
273    Overflow,
274}
275
276#[derive(Debug, PartialEq, Eq, thiserror::Error)]
277pub enum CheckedFromRatioError {
278    #[error("Denominator must not be zero")]
279    DivideByZero,
280
281    #[error("Overflow")]
282    Overflow,
283}
284
285#[derive(Debug, PartialEq, Eq, thiserror::Error)]
286#[error("Round up operation failed because of overflow")]
287pub struct RoundUpOverflowError;
288
289#[derive(Debug, PartialEq, Eq, thiserror::Error)]
290#[error("Round down operation failed because of overflow")]
291pub struct RoundDownOverflowError;
292
293#[derive(Debug, PartialEq, Eq, thiserror::Error)]
294pub enum CoinsError {
295    #[error("Duplicate denom")]
296    DuplicateDenom,
297}
298
299#[derive(Debug, PartialEq, Eq, thiserror::Error)]
300pub enum CoinFromStrError {
301    #[error("Missing denominator")]
302    MissingDenom,
303    #[error("Missing amount or non-digit characters in amount")]
304    MissingAmount,
305    #[error("Invalid amount: {_0}")]
306    InvalidAmount(core::num::ParseIntError),
307}
308
309impl From<core::num::ParseIntError> for CoinFromStrError {
310    fn from(value: core::num::ParseIntError) -> Self {
311        Self::InvalidAmount(value)
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318    use core::str;
319    use std::string;
320
321    #[derive(Debug, thiserror::Error)]
322    enum AssertThiserrorWorks {
323        #[error(transparent)]
324        Std(#[from] StdError),
325    }
326
327    #[test]
328    fn implements_debug() {
329        let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub));
330        let embedded = format!("Debug: {error:?}");
331        assert!(embedded
332            .starts_with("Debug: StdError(InnerError { backtrace: <disabled>, kind: Overflow, inner: OverflowError { operation: Sub } })"), "{embedded}");
333    }
334
335    #[test]
336    fn implements_display() {
337        let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub));
338        let embedded = format!("Display: {error}");
339        assert_eq!(
340            embedded, "Display: kind: Overflow, error: Cannot Sub with given operands",
341            "{embedded}"
342        );
343    }
344
345    #[test]
346    fn from_std_str_utf8error_works() {
347        let broken = Vec::from(b"Hello \xF0\x90\x80World" as &[u8]);
348        let error: StdError = str::from_utf8(&broken).unwrap_err().into();
349        assert!(error.is::<str::Utf8Error>());
350
351        assert!(error
352            .to_string()
353            .ends_with("invalid utf-8 sequence of 3 bytes from index 6"));
354    }
355
356    #[test]
357    fn from_std_string_from_utf8error_works() {
358        let error: StdError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec())
359            .unwrap_err()
360            .into();
361
362        assert!(error.is::<string::FromUtf8Error>());
363        assert!(error
364            .to_string()
365            .ends_with("invalid utf-8 sequence of 3 bytes from index 6"));
366    }
367}