cosmwasm_std/errors/
std_error.rs

1use alloc::string::{String, ToString};
2use core::fmt;
3
4use super::{impl_from_err, BT};
5
6use crate::errors::{RecoverPubkeyError, VerificationError};
7
8/// Structured error type for init, execute and query.
9///
10/// This can be serialized and passed over the Wasm/VM boundary, which allows us to use structured
11/// error types in e.g. integration tests. In that process backtraces are stripped off.
12///
13/// The prefix "Std" means "the standard error within the standard library". This is not the only
14/// result/error type in cosmwasm-std.
15///
16/// When new cases are added, they should describe the problem rather than what was attempted (e.g.
17/// InvalidBase64 is preferred over Base64DecodingErr). In the long run this allows us to get rid of
18/// the duplication in "StdError::FooErr".
19///
20/// Checklist for adding a new error:
21/// - Add enum case
22/// - Add creator function in std_error_helpers.rs
23#[derive(Debug, thiserror::Error)]
24pub enum StdError {
25    #[error("Verification error: {source}")]
26    VerificationErr {
27        source: VerificationError,
28        backtrace: BT,
29    },
30    #[error("Recover pubkey error: {source}")]
31    RecoverPubkeyErr {
32        source: RecoverPubkeyError,
33        backtrace: BT,
34    },
35    /// Whenever there is no specific error type available
36    #[error("Generic error: {msg}")]
37    GenericErr { msg: String, backtrace: BT },
38    #[error("Invalid Base64 string: {msg}")]
39    InvalidBase64 { msg: String, backtrace: BT },
40    #[error("Invalid data size: expected={expected} actual={actual}")]
41    InvalidDataSize {
42        expected: u64,
43        actual: u64,
44        backtrace: BT,
45    },
46    #[error("Invalid hex string: {msg}")]
47    InvalidHex { msg: String, backtrace: BT },
48    /// Whenever UTF-8 bytes cannot be decoded into a unicode string, e.g. in String::from_utf8 or str::from_utf8.
49    #[error("Cannot decode UTF8 bytes into string: {msg}")]
50    InvalidUtf8 { msg: String, backtrace: BT },
51    #[error("{kind} not found")]
52    NotFound { kind: String, backtrace: BT },
53    #[error("Error parsing into type {target_type}: {msg}")]
54    ParseErr {
55        /// the target type that was attempted
56        target_type: String,
57        msg: String,
58        backtrace: BT,
59    },
60    #[error("Error serializing type {source_type}: {msg}")]
61    SerializeErr {
62        /// the source type that was attempted
63        source_type: String,
64        msg: String,
65        backtrace: BT,
66    },
67    #[error("Overflow: {source}")]
68    Overflow {
69        source: OverflowError,
70        backtrace: BT,
71    },
72    #[error("Divide by zero: {source}")]
73    DivideByZero {
74        source: DivideByZeroError,
75        backtrace: BT,
76    },
77    #[error("Conversion error: ")]
78    ConversionOverflow {
79        source: ConversionOverflowError,
80        backtrace: BT,
81    },
82}
83
84impl_from_err!(
85    ConversionOverflowError,
86    StdError,
87    StdError::ConversionOverflow
88);
89
90impl StdError {
91    pub fn verification_err(source: VerificationError) -> Self {
92        StdError::VerificationErr {
93            source,
94            backtrace: BT::capture(),
95        }
96    }
97
98    pub fn recover_pubkey_err(source: RecoverPubkeyError) -> Self {
99        StdError::RecoverPubkeyErr {
100            source,
101            backtrace: BT::capture(),
102        }
103    }
104
105    pub fn generic_err(msg: impl Into<String>) -> Self {
106        StdError::GenericErr {
107            msg: msg.into(),
108            backtrace: BT::capture(),
109        }
110    }
111
112    pub fn invalid_base64(msg: impl ToString) -> Self {
113        StdError::InvalidBase64 {
114            msg: msg.to_string(),
115            backtrace: BT::capture(),
116        }
117    }
118
119    pub fn invalid_data_size(expected: usize, actual: usize) -> Self {
120        StdError::InvalidDataSize {
121            // Cast is safe because usize is 32 or 64 bit large in all environments we support
122            expected: expected as u64,
123            actual: actual as u64,
124            backtrace: BT::capture(),
125        }
126    }
127
128    pub fn invalid_hex(msg: impl ToString) -> Self {
129        StdError::InvalidHex {
130            msg: msg.to_string(),
131            backtrace: BT::capture(),
132        }
133    }
134
135    pub fn invalid_utf8(msg: impl ToString) -> Self {
136        StdError::InvalidUtf8 {
137            msg: msg.to_string(),
138            backtrace: BT::capture(),
139        }
140    }
141
142    pub fn not_found(kind: impl Into<String>) -> Self {
143        StdError::NotFound {
144            kind: kind.into(),
145            backtrace: BT::capture(),
146        }
147    }
148
149    pub fn parse_err(target: impl Into<String>, msg: impl ToString) -> Self {
150        StdError::ParseErr {
151            target_type: target.into(),
152            msg: msg.to_string(),
153            backtrace: BT::capture(),
154        }
155    }
156
157    pub fn serialize_err(source: impl Into<String>, msg: impl ToString) -> Self {
158        StdError::SerializeErr {
159            source_type: source.into(),
160            msg: msg.to_string(),
161            backtrace: BT::capture(),
162        }
163    }
164
165    pub fn overflow(source: OverflowError) -> Self {
166        StdError::Overflow {
167            source,
168            backtrace: BT::capture(),
169        }
170    }
171
172    pub fn divide_by_zero(source: DivideByZeroError) -> Self {
173        StdError::DivideByZero {
174            source,
175            backtrace: BT::capture(),
176        }
177    }
178}
179
180impl PartialEq<StdError> for StdError {
181    fn eq(&self, rhs: &StdError) -> bool {
182        match self {
183            StdError::VerificationErr {
184                source,
185                backtrace: _,
186            } => {
187                if let StdError::VerificationErr {
188                    source: rhs_source,
189                    backtrace: _,
190                } = rhs
191                {
192                    source == rhs_source
193                } else {
194                    false
195                }
196            }
197            StdError::RecoverPubkeyErr {
198                source,
199                backtrace: _,
200            } => {
201                if let StdError::RecoverPubkeyErr {
202                    source: rhs_source,
203                    backtrace: _,
204                } = rhs
205                {
206                    source == rhs_source
207                } else {
208                    false
209                }
210            }
211            StdError::GenericErr { msg, backtrace: _ } => {
212                if let StdError::GenericErr {
213                    msg: rhs_msg,
214                    backtrace: _,
215                } = rhs
216                {
217                    msg == rhs_msg
218                } else {
219                    false
220                }
221            }
222            StdError::InvalidBase64 { msg, backtrace: _ } => {
223                if let StdError::InvalidBase64 {
224                    msg: rhs_msg,
225                    backtrace: _,
226                } = rhs
227                {
228                    msg == rhs_msg
229                } else {
230                    false
231                }
232            }
233            StdError::InvalidDataSize {
234                expected,
235                actual,
236                backtrace: _,
237            } => {
238                if let StdError::InvalidDataSize {
239                    expected: rhs_expected,
240                    actual: rhs_actual,
241                    backtrace: _,
242                } = rhs
243                {
244                    expected == rhs_expected && actual == rhs_actual
245                } else {
246                    false
247                }
248            }
249            StdError::InvalidHex { msg, backtrace: _ } => {
250                if let StdError::InvalidHex {
251                    msg: rhs_msg,
252                    backtrace: _,
253                } = rhs
254                {
255                    msg == rhs_msg
256                } else {
257                    false
258                }
259            }
260            StdError::InvalidUtf8 { msg, backtrace: _ } => {
261                if let StdError::InvalidUtf8 {
262                    msg: rhs_msg,
263                    backtrace: _,
264                } = rhs
265                {
266                    msg == rhs_msg
267                } else {
268                    false
269                }
270            }
271            StdError::NotFound { kind, backtrace: _ } => {
272                if let StdError::NotFound {
273                    kind: rhs_kind,
274                    backtrace: _,
275                } = rhs
276                {
277                    kind == rhs_kind
278                } else {
279                    false
280                }
281            }
282            StdError::ParseErr {
283                target_type,
284                msg,
285                backtrace: _,
286            } => {
287                if let StdError::ParseErr {
288                    target_type: rhs_target_type,
289                    msg: rhs_msg,
290                    backtrace: _,
291                } = rhs
292                {
293                    target_type == rhs_target_type && msg == rhs_msg
294                } else {
295                    false
296                }
297            }
298            StdError::SerializeErr {
299                source_type,
300                msg,
301                backtrace: _,
302            } => {
303                if let StdError::SerializeErr {
304                    source_type: rhs_source_type,
305                    msg: rhs_msg,
306                    backtrace: _,
307                } = rhs
308                {
309                    source_type == rhs_source_type && msg == rhs_msg
310                } else {
311                    false
312                }
313            }
314            StdError::Overflow {
315                source,
316                backtrace: _,
317            } => {
318                if let StdError::Overflow {
319                    source: rhs_source,
320                    backtrace: _,
321                } = rhs
322                {
323                    source == rhs_source
324                } else {
325                    false
326                }
327            }
328            StdError::DivideByZero {
329                source,
330                backtrace: _,
331            } => {
332                if let StdError::DivideByZero {
333                    source: rhs_source,
334                    backtrace: _,
335                } = rhs
336                {
337                    source == rhs_source
338                } else {
339                    false
340                }
341            }
342            StdError::ConversionOverflow {
343                source,
344                backtrace: _,
345            } => {
346                if let StdError::ConversionOverflow {
347                    source: rhs_source,
348                    backtrace: _,
349                } = rhs
350                {
351                    source == rhs_source
352                } else {
353                    false
354                }
355            }
356        }
357    }
358}
359
360impl From<core::str::Utf8Error> for StdError {
361    fn from(source: core::str::Utf8Error) -> Self {
362        Self::invalid_utf8(source)
363    }
364}
365
366impl From<alloc::string::FromUtf8Error> for StdError {
367    fn from(source: alloc::string::FromUtf8Error) -> Self {
368        Self::invalid_utf8(source)
369    }
370}
371
372impl From<VerificationError> for StdError {
373    fn from(source: VerificationError) -> Self {
374        Self::verification_err(source)
375    }
376}
377
378impl From<RecoverPubkeyError> for StdError {
379    fn from(source: RecoverPubkeyError) -> Self {
380        Self::recover_pubkey_err(source)
381    }
382}
383
384impl From<OverflowError> for StdError {
385    fn from(source: OverflowError) -> Self {
386        Self::overflow(source)
387    }
388}
389
390impl From<DivideByZeroError> for StdError {
391    fn from(source: DivideByZeroError) -> Self {
392        Self::divide_by_zero(source)
393    }
394}
395
396/// The return type for init, execute and query. Since the error type cannot be serialized to JSON,
397/// this is only available within the contract and its unit tests.
398///
399/// The prefix "Core"/"Std" means "the standard result within the core/standard library". This is not the only
400/// result/error type in cosmwasm-core/cosmwasm-std.
401pub type StdResult<T> = core::result::Result<T, StdError>;
402
403#[derive(Debug, PartialEq, Eq)]
404pub enum OverflowOperation {
405    Add,
406    Sub,
407    Mul,
408    Pow,
409    Shr,
410    Shl,
411}
412
413impl fmt::Display for OverflowOperation {
414    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
415        write!(f, "{self:?}")
416    }
417}
418
419#[derive(Debug, PartialEq, Eq, thiserror::Error)]
420#[error("Cannot {operation} with given operands")]
421pub struct OverflowError {
422    pub operation: OverflowOperation,
423}
424
425impl OverflowError {
426    pub fn new(operation: OverflowOperation) -> Self {
427        Self { operation }
428    }
429}
430
431/// The error returned by [`TryFrom`] conversions that overflow, for example
432/// when converting from [`Uint256`] to [`Uint128`].
433///
434/// [`TryFrom`]: core::convert::TryFrom
435/// [`Uint256`]: crate::Uint256
436/// [`Uint128`]: crate::Uint128
437#[derive(Debug, PartialEq, Eq, thiserror::Error)]
438#[error("Error converting {source_type} to {target_type}")]
439pub struct ConversionOverflowError {
440    pub source_type: &'static str,
441    pub target_type: &'static str,
442}
443
444impl ConversionOverflowError {
445    pub fn new(source_type: &'static str, target_type: &'static str) -> Self {
446        Self {
447            source_type,
448            target_type,
449        }
450    }
451}
452
453#[derive(Debug, Default, PartialEq, Eq, thiserror::Error)]
454#[error("Cannot divide by zero")]
455pub struct DivideByZeroError;
456
457impl DivideByZeroError {
458    pub fn new() -> Self {
459        Self
460    }
461}
462
463#[derive(Debug, PartialEq, Eq, thiserror::Error)]
464pub enum DivisionError {
465    #[error("Divide by zero")]
466    DivideByZero,
467
468    #[error("Overflow in division")]
469    Overflow,
470}
471
472#[derive(Debug, PartialEq, Eq, thiserror::Error)]
473pub enum CheckedMultiplyFractionError {
474    #[error("{_0}")]
475    DivideByZero(#[from] DivideByZeroError),
476
477    #[error("{_0}")]
478    ConversionOverflow(#[from] ConversionOverflowError),
479
480    #[error("{_0}")]
481    Overflow(#[from] OverflowError),
482}
483
484#[derive(Debug, PartialEq, Eq, thiserror::Error)]
485pub enum CheckedMultiplyRatioError {
486    #[error("Denominator must not be zero")]
487    DivideByZero,
488
489    #[error("Multiplication overflow")]
490    Overflow,
491}
492
493#[derive(Debug, PartialEq, Eq, thiserror::Error)]
494pub enum CheckedFromRatioError {
495    #[error("Denominator must not be zero")]
496    DivideByZero,
497
498    #[error("Overflow")]
499    Overflow,
500}
501
502#[derive(Debug, PartialEq, Eq, thiserror::Error)]
503#[error("Round up operation failed because of overflow")]
504pub struct RoundUpOverflowError;
505
506#[derive(Debug, PartialEq, Eq, thiserror::Error)]
507#[error("Round down operation failed because of overflow")]
508pub struct RoundDownOverflowError;
509
510#[derive(Debug, PartialEq, Eq, thiserror::Error)]
511pub enum CoinsError {
512    #[error("Duplicate denom")]
513    DuplicateDenom,
514}
515
516impl From<CoinsError> for StdError {
517    fn from(value: CoinsError) -> Self {
518        Self::generic_err(format!("Creating Coins: {value}"))
519    }
520}
521
522#[derive(Debug, PartialEq, Eq, thiserror::Error)]
523pub enum CoinFromStrError {
524    #[error("Missing denominator")]
525    MissingDenom,
526    #[error("Missing amount or non-digit characters in amount")]
527    MissingAmount,
528    #[error("Invalid amount: {_0}")]
529    InvalidAmount(core::num::ParseIntError),
530}
531
532impl From<core::num::ParseIntError> for CoinFromStrError {
533    fn from(value: core::num::ParseIntError) -> Self {
534        Self::InvalidAmount(value)
535    }
536}
537
538impl From<CoinFromStrError> for StdError {
539    fn from(value: CoinFromStrError) -> Self {
540        Self::generic_err(format!("Parsing Coin: {value}"))
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use super::*;
547    use core::str;
548
549    // constructors
550
551    // example of reporting contract errors with format!
552    #[test]
553    fn generic_err_owned() {
554        let guess = 7;
555        let error = StdError::generic_err(format!("{guess} is too low"));
556        match error {
557            StdError::GenericErr { msg, .. } => {
558                assert_eq!(msg, String::from("7 is too low"));
559            }
560            e => panic!("unexpected error, {e:?}"),
561        }
562    }
563
564    // example of reporting static contract errors
565    #[test]
566    fn generic_err_ref() {
567        let error = StdError::generic_err("not implemented");
568        match error {
569            StdError::GenericErr { msg, .. } => assert_eq!(msg, "not implemented"),
570            e => panic!("unexpected error, {e:?}"),
571        }
572    }
573
574    #[test]
575    fn invalid_base64_works_for_strings() {
576        let error = StdError::invalid_base64("my text");
577        match error {
578            StdError::InvalidBase64 { msg, .. } => {
579                assert_eq!(msg, "my text");
580            }
581            _ => panic!("expect different error"),
582        }
583    }
584
585    #[test]
586    fn invalid_base64_works_for_errors() {
587        let original = base64::DecodeError::InvalidLength(10);
588        let error = StdError::invalid_base64(original);
589        match error {
590            StdError::InvalidBase64 { msg, .. } => {
591                assert_eq!(msg, "Invalid input length: 10");
592            }
593            _ => panic!("expect different error"),
594        }
595    }
596
597    #[test]
598    fn invalid_data_size_works() {
599        let error = StdError::invalid_data_size(31, 14);
600        match error {
601            StdError::InvalidDataSize {
602                expected, actual, ..
603            } => {
604                assert_eq!(expected, 31);
605                assert_eq!(actual, 14);
606            }
607            _ => panic!("expect different error"),
608        }
609    }
610
611    #[test]
612    fn invalid_hex_works_for_strings() {
613        let error = StdError::invalid_hex("my text");
614        match error {
615            StdError::InvalidHex { msg, .. } => {
616                assert_eq!(msg, "my text");
617            }
618            _ => panic!("expect different error"),
619        }
620    }
621
622    #[test]
623    fn invalid_hex_works_for_errors() {
624        let original = hex::FromHexError::OddLength;
625        let error = StdError::invalid_hex(original);
626        match error {
627            StdError::InvalidHex { msg, .. } => {
628                assert_eq!(msg, "Odd number of digits");
629            }
630            _ => panic!("expect different error"),
631        }
632    }
633
634    #[test]
635    fn invalid_utf8_works_for_strings() {
636        let error = StdError::invalid_utf8("my text");
637        match error {
638            StdError::InvalidUtf8 { msg, .. } => {
639                assert_eq!(msg, "my text");
640            }
641            _ => panic!("expect different error"),
642        }
643    }
644
645    #[test]
646    fn invalid_utf8_works_for_errors() {
647        let original = String::from_utf8(vec![0x80]).unwrap_err();
648        let error = StdError::invalid_utf8(original);
649        match error {
650            StdError::InvalidUtf8 { msg, .. } => {
651                assert_eq!(msg, "invalid utf-8 sequence of 1 bytes from index 0");
652            }
653            _ => panic!("expect different error"),
654        }
655    }
656
657    #[test]
658    fn not_found_works() {
659        let error = StdError::not_found("gold");
660        match error {
661            StdError::NotFound { kind, .. } => assert_eq!(kind, "gold"),
662            _ => panic!("expect different error"),
663        }
664    }
665
666    #[test]
667    fn parse_err_works() {
668        let error = StdError::parse_err("Book", "Missing field: title");
669        match error {
670            StdError::ParseErr {
671                target_type, msg, ..
672            } => {
673                assert_eq!(target_type, "Book");
674                assert_eq!(msg, "Missing field: title");
675            }
676            _ => panic!("expect different error"),
677        }
678    }
679
680    #[test]
681    fn serialize_err_works() {
682        let error = StdError::serialize_err("Book", "Content too long");
683        match error {
684            StdError::SerializeErr {
685                source_type, msg, ..
686            } => {
687                assert_eq!(source_type, "Book");
688                assert_eq!(msg, "Content too long");
689            }
690            _ => panic!("expect different error"),
691        }
692    }
693
694    #[test]
695    fn underflow_works_for_u128() {
696        let error = StdError::overflow(OverflowError::new(OverflowOperation::Sub));
697        assert!(matches!(
698            error,
699            StdError::Overflow {
700                source: OverflowError {
701                    operation: OverflowOperation::Sub
702                },
703                ..
704            }
705        ));
706    }
707
708    #[test]
709    fn overflow_works_for_i64() {
710        let error = StdError::overflow(OverflowError::new(OverflowOperation::Sub));
711        assert!(matches!(
712            error,
713            StdError::Overflow {
714                source: OverflowError {
715                    operation: OverflowOperation::Sub
716                },
717                ..
718            }
719        ));
720    }
721
722    #[test]
723    fn divide_by_zero_works() {
724        let error = StdError::divide_by_zero(DivideByZeroError);
725        assert!(matches!(
726            error,
727            StdError::DivideByZero {
728                source: DivideByZeroError,
729                ..
730            }
731        ));
732    }
733
734    #[test]
735    fn implements_debug() {
736        let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub));
737        let embedded = format!("Debug: {error:?}");
738        assert!(embedded
739            .starts_with("Debug: Overflow { source: OverflowError { operation: Sub }, backtrace:"));
740    }
741
742    #[test]
743    fn implements_display() {
744        let error: StdError = StdError::from(OverflowError::new(OverflowOperation::Sub));
745        let embedded = format!("Display: {error}");
746        assert_eq!(
747            embedded,
748            "Display: Overflow: Cannot Sub with given operands"
749        );
750    }
751
752    #[test]
753    fn implements_partial_eq() {
754        let u1 = StdError::from(OverflowError::new(OverflowOperation::Sub));
755        let u2 = StdError::from(OverflowError::new(OverflowOperation::Sub));
756        let s1 = StdError::serialize_err("Book", "Content too long");
757        let s2 = StdError::serialize_err("Book", "Content too long");
758        let s3 = StdError::serialize_err("Book", "Title too long");
759        assert_eq!(u1, u2);
760        assert_ne!(u1, s1);
761        assert_eq!(s1, s2);
762        assert_ne!(s1, s3);
763    }
764
765    #[test]
766    fn from_std_str_utf8error_works() {
767        let broken = Vec::from(b"Hello \xF0\x90\x80World" as &[u8]);
768        let error: StdError = str::from_utf8(&broken).unwrap_err().into();
769        match error {
770            StdError::InvalidUtf8 { msg, .. } => {
771                assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6")
772            }
773            err => panic!("Unexpected error: {err:?}"),
774        }
775    }
776
777    #[test]
778    fn from_std_string_from_utf8error_works() {
779        let error: StdError = String::from_utf8(b"Hello \xF0\x90\x80World".to_vec())
780            .unwrap_err()
781            .into();
782        match error {
783            StdError::InvalidUtf8 { msg, .. } => {
784                assert_eq!(msg, "invalid utf-8 sequence of 3 bytes from index 6")
785            }
786            err => panic!("Unexpected error: {err:?}"),
787        }
788    }
789}