prov_cosmwasm_std/errors/
std_error.rs

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