Skip to main content

bitcoin_units/amount/
error.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Error types for bitcoin amounts.
4
5use core::convert::Infallible;
6use core::fmt;
7
8use internals::error::InputString;
9use internals::write_err;
10
11use super::INPUT_STRING_LEN_LIMIT;
12use crate::parse_int::{PrefixedHexError, UnprefixedHexError};
13
14/// Error returned when parsing an amount with denomination fails.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ParseError(pub(crate) ParseErrorInner);
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub(crate) enum ParseErrorInner {
20    /// Invalid amount.
21    Amount(ParseAmountError),
22    /// Invalid denomination.
23    Denomination(ParseDenominationError),
24    /// The denomination was not identified.
25    MissingDenomination(MissingDenominationError),
26}
27
28impl From<Infallible> for ParseError {
29    fn from(never: Infallible) -> Self { match never {} }
30}
31
32impl From<Infallible> for ParseErrorInner {
33    fn from(never: Infallible) -> Self { match never {} }
34}
35
36impl fmt::Display for ParseError {
37    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38        match self.0 {
39            ParseErrorInner::Amount(ref e) => write_err!(f, "invalid amount"; e),
40            ParseErrorInner::Denomination(ref e) => write_err!(f, "invalid denomination"; e),
41            // We consider this to not be a source because it currently doesn't contain useful info.
42            ParseErrorInner::MissingDenomination(ref e) => write_err!(f, "missing denomination"; e),
43        }
44    }
45}
46
47#[cfg(feature = "std")]
48impl std::error::Error for ParseError {
49    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
50        match self.0 {
51            ParseErrorInner::Amount(ref e) => Some(e),
52            ParseErrorInner::Denomination(ref e) => Some(e),
53            // We consider this to not be a source because it currently doesn't contain useful info.
54            ParseErrorInner::MissingDenomination(ref e) => Some(e),
55        }
56    }
57}
58
59/// Error returned when parsing an amount fails.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct ParseAmountError(pub(crate) ParseAmountErrorInner);
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub(crate) enum ParseAmountErrorInner {
65    /// The amount is too big or too small.
66    OutOfRange(OutOfRangeError),
67    /// Amount has higher precision than supported by the type.
68    TooPrecise(TooPreciseError),
69    /// A digit was expected but not found.
70    MissingDigits(MissingDigitsError),
71    /// Input string was too large.
72    InputTooLarge(InputTooLargeError),
73    /// Invalid character in input.
74    InvalidCharacter(InvalidCharacterError),
75    /// A valid character is in an invalid position.
76    BadPosition(BadPositionError),
77    /// An error parsing a prefixed hex amount.
78    PrefixedHex(PrefixedHexError),
79    /// An error parsing an unprefixed hex amount.
80    UnprefixedHex(UnprefixedHexError),
81}
82
83impl From<Infallible> for ParseAmountError {
84    fn from(never: Infallible) -> Self { match never {} }
85}
86
87impl From<Infallible> for ParseAmountErrorInner {
88    fn from(never: Infallible) -> Self { match never {} }
89}
90
91impl fmt::Display for ParseAmountError {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        use ParseAmountErrorInner as E;
94
95        match self.0 {
96            E::OutOfRange(ref error) => write_err!(f, "amount out of range"; error),
97            E::TooPrecise(ref error) => write_err!(f, "amount has a too high precision"; error),
98            E::MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error),
99            E::InputTooLarge(ref error) => write_err!(f, "the input is too large"; error),
100            E::InvalidCharacter(ref error) => {
101                write_err!(f, "invalid character in the input"; error)
102            }
103            E::BadPosition(ref error) => write_err!(f, "valid character in bad position"; error),
104            E::PrefixedHex(ref error) => write_err!(f, "prefixed hex is invalid"; error),
105            E::UnprefixedHex(ref error) => write_err!(f, "unprefixed hex is invalid"; error),
106        }
107    }
108}
109
110#[cfg(feature = "std")]
111impl std::error::Error for ParseAmountError {
112    #[inline]
113    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
114        use ParseAmountErrorInner as E;
115
116        match self.0 {
117            E::TooPrecise(ref error) => Some(error),
118            E::InputTooLarge(ref error) => Some(error),
119            E::OutOfRange(ref error) => Some(error),
120            E::MissingDigits(ref error) => Some(error),
121            E::InvalidCharacter(ref error) => Some(error),
122            E::BadPosition(ref error) => Some(error),
123            E::PrefixedHex(ref error) => Some(error),
124            E::UnprefixedHex(ref error) => Some(error),
125        }
126    }
127}
128
129/// Error returned when a parsed amount is too big or too small.
130#[derive(Debug, Copy, Clone, Eq, PartialEq)]
131pub struct OutOfRangeError {
132    pub(super) is_signed: bool,
133    pub(super) is_greater_than_max: bool,
134}
135
136impl OutOfRangeError {
137    /// Returns the minimum and maximum allowed values for the type that was parsed.
138    ///
139    /// This can be used to give a hint to the user which values are allowed.
140    #[inline]
141    pub fn valid_range(self) -> (i64, u64) {
142        match self.is_signed {
143            true => (i64::MIN, i64::MAX as u64),
144            false => (0, u64::MAX),
145        }
146    }
147
148    /// Returns true if the input value was larger than the maximum allowed value.
149    #[inline]
150    pub fn is_above_max(self) -> bool { self.is_greater_than_max }
151
152    /// Returns true if the input value was smaller than the minimum allowed value.
153    #[inline]
154    pub fn is_below_min(self) -> bool { !self.is_greater_than_max }
155
156    #[cfg(test)]
157    #[inline]
158    pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
159
160    #[cfg(test)]
161    #[inline]
162    pub(crate) fn too_small() -> Self {
163        Self {
164            // implied - negative() is used for the other
165            is_signed: true,
166            is_greater_than_max: false,
167        }
168    }
169
170    #[inline]
171    pub(crate) fn negative() -> Self {
172        Self {
173            // implied - too_small() is used for the other
174            is_signed: false,
175            is_greater_than_max: false,
176        }
177    }
178}
179
180impl From<Infallible> for OutOfRangeError {
181    fn from(never: Infallible) -> Self { match never {} }
182}
183
184impl fmt::Display for OutOfRangeError {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        if self.is_greater_than_max {
187            write!(f, "the amount is greater than {}", self.valid_range().1)
188        } else {
189            write!(f, "the amount is less than {}", self.valid_range().0)
190        }
191    }
192}
193
194#[cfg(feature = "std")]
195impl std::error::Error for OutOfRangeError {
196    #[inline]
197    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
198        let Self { is_signed: _, is_greater_than_max: _ } = self;
199        None
200    }
201}
202
203/// Error returned when the input string has higher precision than satoshis.
204#[derive(Debug, Clone, Eq, PartialEq)]
205pub struct TooPreciseError {
206    pub(super) position: usize,
207}
208
209impl From<Infallible> for TooPreciseError {
210    fn from(never: Infallible) -> Self { match never {} }
211}
212
213impl fmt::Display for TooPreciseError {
214    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215        match self.position {
216            0 => f.write_str("the amount is less than 1 satoshi but it's not zero"),
217            pos => write!(
218                f,
219                "the digits starting from position {} represent a sub-satoshi amount",
220                pos
221            ),
222        }
223    }
224}
225
226#[cfg(feature = "std")]
227impl std::error::Error for TooPreciseError {
228    #[inline]
229    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
230        let Self { position: _ } = self;
231        None
232    }
233}
234
235/// Error returned when the input string is too large.
236#[derive(Debug, Clone, Eq, PartialEq)]
237pub struct InputTooLargeError {
238    pub(super) len: usize,
239}
240
241impl From<Infallible> for InputTooLargeError {
242    fn from(never: Infallible) -> Self { match never {} }
243}
244
245impl fmt::Display for InputTooLargeError {
246    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
247        match self.len - INPUT_STRING_LEN_LIMIT {
248            1 => write!(
249                f,
250                "the input is one character longer than the maximum allowed length ({})",
251                INPUT_STRING_LEN_LIMIT
252            ),
253            n => write!(
254                f,
255                "the input is {} characters longer than the maximum allowed length ({})",
256                n, INPUT_STRING_LEN_LIMIT
257            ),
258        }
259    }
260}
261
262#[cfg(feature = "std")]
263impl std::error::Error for InputTooLargeError {
264    #[inline]
265    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
266        let Self { len: _ } = self;
267        None
268    }
269}
270
271/// Error returned when digits were expected in the input but there were none.
272///
273/// In particular, this is currently returned when the string is empty or only contains the minus sign.
274#[derive(Debug, Clone, Eq, PartialEq)]
275pub struct MissingDigitsError {
276    pub(super) kind: MissingDigitsKind,
277}
278
279impl From<Infallible> for MissingDigitsError {
280    fn from(never: Infallible) -> Self { match never {} }
281}
282
283impl fmt::Display for MissingDigitsError {
284    #[inline]
285    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
286        match self.kind {
287            MissingDigitsKind::Empty => f.write_str("the input is empty"),
288            MissingDigitsKind::OnlyMinusSign =>
289                f.write_str("there are no digits following the minus (-) sign"),
290        }
291    }
292}
293
294#[cfg(feature = "std")]
295impl std::error::Error for MissingDigitsError {
296    #[inline]
297    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
298        let Self { kind: _ } = self;
299        None
300    }
301}
302
303#[derive(Debug, Clone, Eq, PartialEq)]
304pub(super) enum MissingDigitsKind {
305    Empty,
306    OnlyMinusSign,
307}
308
309/// Error returned when the input contains an invalid character.
310#[derive(Debug, Clone, PartialEq, Eq)]
311pub struct InvalidCharacterError {
312    pub(super) invalid_char: char,
313    pub(super) position: usize,
314}
315
316impl From<Infallible> for InvalidCharacterError {
317    fn from(never: Infallible) -> Self { match never {} }
318}
319
320impl fmt::Display for InvalidCharacterError {
321    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
322        match self.invalid_char {
323            '.' => f.write_str("there is more than one decimal separator (dot) in the input"),
324            '-' => f.write_str("there is more than one minus sign (-) in the input"),
325            c => write!(
326                f,
327                "the character '{}' at position {} is not a valid digit",
328                c, self.position
329            ),
330        }
331    }
332}
333
334#[cfg(feature = "std")]
335impl std::error::Error for InvalidCharacterError {
336    #[inline]
337    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
338        let Self { invalid_char: _, position: _ } = self;
339        None
340    }
341}
342
343/// Error returned when a valid character (e.g. '_') is in an invalid/bad position.
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct BadPositionError {
346    pub(super) char: char,
347    pub(super) position: usize,
348}
349
350impl From<Infallible> for BadPositionError {
351    fn from(never: Infallible) -> Self { match never {} }
352}
353
354impl fmt::Display for BadPositionError {
355    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
356        match self.char {
357            '_' => match self.position {
358                0 => f.write_str("the input amount is prefixed with an underscore (_)"),
359                // Position 1 can only occur when the amount begins with a minus (-), since an underscore
360                // at position 0 will cause an error with position 0, and this error marks the position of
361                // the second underscore.
362                1 => f.write_str("the input amount is prefixed with an underscore (_)"),
363                _ => f.write_str("there are consecutive underscores (_) in the input"),
364            },
365            c => write!(f, "The character '{}' is at a bad position: {}", c, self.position),
366        }
367    }
368}
369
370#[cfg(feature = "std")]
371impl std::error::Error for BadPositionError {
372    #[inline]
373    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
374        let Self { char: _, position: _ } = self;
375        None
376    }
377}
378
379/// An error during amount parsing.
380#[derive(Debug, Clone, PartialEq, Eq)]
381#[non_exhaustive]
382pub enum ParseDenominationError {
383    /// The denomination was unknown.
384    Unknown(UnknownDenominationError),
385    /// The denomination has multiple possible interpretations.
386    PossiblyConfusing(PossiblyConfusingDenominationError),
387}
388
389impl From<Infallible> for ParseDenominationError {
390    fn from(never: Infallible) -> Self { match never {} }
391}
392
393impl fmt::Display for ParseDenominationError {
394    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395        match *self {
396            Self::Unknown(ref e) => write_err!(f, "denomination parse error"; e),
397            Self::PossiblyConfusing(ref e) => write_err!(f, "denomination parse error"; e),
398        }
399    }
400}
401
402#[cfg(feature = "std")]
403impl std::error::Error for ParseDenominationError {
404    #[inline]
405    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
406        match *self {
407            Self::Unknown(ref e) => Some(e),
408            Self::PossiblyConfusing(ref e) => Some(e),
409        }
410    }
411}
412
413/// Error returned when the denomination is empty.
414#[derive(Debug, Clone, PartialEq, Eq)]
415#[non_exhaustive]
416pub struct MissingDenominationError;
417
418impl From<Infallible> for MissingDenominationError {
419    fn from(never: Infallible) -> Self { match never {} }
420}
421
422impl fmt::Display for MissingDenominationError {
423    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
424        write!(f, "the input does not contain a denomination")
425    }
426}
427
428#[cfg(feature = "std")]
429impl std::error::Error for MissingDenominationError {
430    #[inline]
431    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
432        let Self {} = self;
433        None
434    }
435}
436
437/// Error returned when parsing an unknown denomination.
438#[derive(Debug, Clone, PartialEq, Eq)]
439#[non_exhaustive]
440pub struct UnknownDenominationError(pub(super) InputString);
441
442impl From<Infallible> for UnknownDenominationError {
443    fn from(never: Infallible) -> Self { match never {} }
444}
445
446impl fmt::Display for UnknownDenominationError {
447    #[inline]
448    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449        self.0.unknown_variant("bitcoin denomination", f)
450    }
451}
452
453#[cfg(feature = "std")]
454impl std::error::Error for UnknownDenominationError {
455    #[inline]
456    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
457        let Self(_) = self;
458        None
459    }
460}
461
462/// Error returned when parsing a possibly confusing denomination.
463#[derive(Debug, Clone, PartialEq, Eq)]
464#[non_exhaustive]
465pub struct PossiblyConfusingDenominationError(pub(super) InputString);
466
467impl From<Infallible> for PossiblyConfusingDenominationError {
468    fn from(never: Infallible) -> Self { match never {} }
469}
470
471impl fmt::Display for PossiblyConfusingDenominationError {
472    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473        write!(f, "{}: possibly confusing denomination - we intentionally do not support 'M' and 'P' so as to not confuse mega/milli and peta/pico", self.0.display_cannot_parse("bitcoin denomination"))
474    }
475}
476
477#[cfg(feature = "std")]
478impl std::error::Error for PossiblyConfusingDenominationError {
479    #[inline]
480    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
481        let Self(_) = self;
482        None
483    }
484}
485
486/// An error consensus decoding an `Amount`.
487#[cfg(feature = "encoding")]
488#[derive(Debug, Clone, PartialEq, Eq)]
489pub struct AmountDecoderError(pub(super) AmountDecoderErrorInner);
490
491#[cfg(feature = "encoding")]
492impl AmountDecoderError {
493    /// Constructs an EOF error.
494    #[inline]
495    pub(super) fn eof(e: encoding::UnexpectedEofError) -> Self {
496        Self(AmountDecoderErrorInner::UnexpectedEof(e))
497    }
498
499    /// Constructs an out of range (`Amount::from_sat`) error.
500    #[inline]
501    pub(super) fn out_of_range(e: OutOfRangeError) -> Self {
502        Self(AmountDecoderErrorInner::OutOfRange(e))
503    }
504}
505
506#[cfg(feature = "encoding")]
507#[derive(Debug, Clone, PartialEq, Eq)]
508pub(super) enum AmountDecoderErrorInner {
509    /// Not enough bytes given to decoder.
510    UnexpectedEof(encoding::UnexpectedEofError),
511    /// Decoded amount is too big.
512    OutOfRange(OutOfRangeError),
513}
514
515#[cfg(feature = "encoding")]
516impl From<Infallible> for AmountDecoderError {
517    fn from(never: Infallible) -> Self { match never {} }
518}
519
520#[cfg(feature = "encoding")]
521impl fmt::Display for AmountDecoderError {
522    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
523        use AmountDecoderErrorInner as E;
524
525        match self.0 {
526            E::UnexpectedEof(ref e) => write_err!(f, "decode error"; e),
527            E::OutOfRange(ref e) => write_err!(f, "decode error"; e),
528        }
529    }
530}
531
532#[cfg(feature = "encoding")]
533#[cfg(feature = "std")]
534impl std::error::Error for AmountDecoderError {
535    #[inline]
536    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
537        use AmountDecoderErrorInner as E;
538
539        match self.0 {
540            E::UnexpectedEof(ref e) => Some(e),
541            E::OutOfRange(ref e) => Some(e),
542        }
543    }
544}
545
546#[cfg(test)]
547mod tests {
548    #[cfg(feature = "alloc")]
549    use alloc::string::ToString;
550    #[cfg(feature = "alloc")]
551    use core::str::FromStr;
552    #[cfg(feature = "std")]
553    use std::error::Error;
554
555    #[cfg(feature = "encoding")]
556    use encoding::{Decode as _, Decoder as _};
557
558    #[cfg(feature = "alloc")]
559    use super::{ParseAmountError, ParseAmountErrorInner, ParseErrorInner};
560    #[cfg(feature = "alloc")]
561    use crate::amount::{Amount, Denomination, ParseDenominationError, ParseError};
562
563    #[test]
564    #[cfg(feature = "alloc")]
565    #[allow(clippy::too_many_lines)] // Test could be refactored ...
566    fn error_display_is_non_empty() {
567        // A helper macro to break out a ParseAmountErrorInner type and assert display down the chain.
568        macro_rules! assert_amount_err {
569            ($e:expr, $enum_arm:ident, $err_msg:expr) => {
570                assert!(!$e.to_string().is_empty());
571                let ParseError(ParseErrorInner::Amount(err)) = $e else { panic!($err_msg) };
572                assert!(!err.to_string().is_empty());
573                #[cfg(feature = "std")]
574                assert!(err.source().is_some());
575
576                let ParseAmountError(ParseAmountErrorInner::$enum_arm(err)) = err else {
577                    panic!($err_msg)
578                };
579                assert!(!err.to_string().is_empty());
580                // The inner-most types have no source
581                #[cfg(feature = "std")]
582                assert!(err.source().is_none());
583            };
584        }
585
586        // InputTooLargeError
587        // one char too long
588        let long_input = alloc::format!("{} BTC", "1".repeat(51));
589        let e = Amount::from_str(&long_input).unwrap_err();
590        assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
591        // n chars too long
592        let long_input = alloc::format!("{} BTC", "1".repeat(52));
593        let e = Amount::from_str(&long_input).unwrap_err();
594        assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
595
596        // InvalidCharacterError
597        // invalid character in amount string
598        let e = Amount::from_str("12x34 BTC").unwrap_err();
599        assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
600        // too many decimal points
601        let e = Amount::from_str("12.3.4 BTC").unwrap_err();
602        assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
603        // too many minus signs
604        let e = Amount::from_str("--1234 BTC").unwrap_err();
605        assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
606
607        // MissingDigitsError
608        // no numeric value
609        let e = Amount::from_str("BTC").unwrap_err();
610        assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
611        // Only a minus sign
612        let e = Amount::from_str("- BTC").unwrap_err();
613        assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
614
615        // OutOfRangeError
616        // amount too large
617        let e = Amount::from_str("21000001 BTC").unwrap_err();
618        assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
619        // less than 0
620        let e = Amount::from_str("-10 BTC").unwrap_err();
621        assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
622
623        // TooPreciseError - sub-satoshi precision
624        let e = Amount::from_str("0.000000001 BTC").unwrap_err();
625        assert_amount_err!(e, TooPrecise, "error should be TooPreciseError");
626
627        // BadPositionError
628        // underscore in bad position
629        let e = Amount::from_str("_123 BTC").unwrap_err();
630        assert_amount_err!(e, BadPosition, "error should be BadPositionError");
631        // underscore in bad position (negative)
632        let e = Amount::from_str("-_123 BTC").unwrap_err();
633        assert_amount_err!(e, BadPosition, "error should be BadPositionError");
634        // consecutive underscores
635        let e = Amount::from_str("1__23 BTC").unwrap_err();
636        assert_amount_err!(e, BadPosition, "error should be BadPositionError");
637
638        // ParseAmountError - parent type for the errors above
639        let e = Amount::from_str_in("invalid", Denomination::Bitcoin).unwrap_err();
640        assert!(!e.to_string().is_empty());
641        #[cfg(feature = "std")]
642        assert!(e.source().is_some());
643
644        // UnknownDenominationError - amount with unknown denomination string
645        let e = Denomination::from_str("XYZ").unwrap_err();
646        #[cfg(feature = "std")]
647        assert!(e.source().is_some());
648        let ParseDenominationError::Unknown(e) = e else {
649            panic!("error should be UnknownDenominationError")
650        };
651        assert!(!e.to_string().is_empty());
652        #[cfg(feature = "std")]
653        assert!(e.source().is_none());
654
655        // PossiblyConfusingDenominationError - confusing denomination like "MBTC"
656        let e = Denomination::from_str("MBTC").unwrap_err();
657        #[cfg(feature = "std")]
658        assert!(e.source().is_some());
659        let ParseDenominationError::PossiblyConfusing(e) = e else {
660            panic!("error should be PossiblyConfusingDenominationError")
661        };
662        assert!(!e.to_string().is_empty());
663        #[cfg(feature = "std")]
664        assert!(e.source().is_none());
665
666        // ParseDenominationError - parent error for the above *DenominationError types
667        // Unknown type
668        let e = Denomination::from_str("UNKNOWN").unwrap_err();
669        assert!(!e.to_string().is_empty());
670        #[cfg(feature = "std")]
671        assert!(e.source().is_some());
672        // Possibly confusing type
673        let e = Denomination::from_str("MBTC").unwrap_err();
674        assert!(!e.to_string().is_empty());
675        #[cfg(feature = "std")]
676        assert!(e.source().is_some());
677
678        // ParseError - parent type for all of the above
679        // Amount type
680        let e = "invalid BTC".parse::<Amount>().unwrap_err();
681        assert!(!e.to_string().is_empty());
682        #[cfg(feature = "std")]
683        assert!(e.source().is_some());
684        // bad denomination type
685        let e = "123 GBTC".parse::<Amount>().unwrap_err();
686        assert!(!e.to_string().is_empty());
687        #[cfg(feature = "std")]
688        assert!(e.source().is_some());
689        // missing denomination type
690        let e = "123".parse::<Amount>().unwrap_err();
691        assert!(!e.to_string().is_empty());
692        #[cfg(feature = "std")]
693        assert!(e.source().is_some());
694
695        #[cfg(feature = "encoding")]
696        {
697            // AmountDecoderError
698            // EOF type
699            let mut decoder = Amount::decoder();
700            let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
701            let e = decoder.end().unwrap_err();
702            assert!(!e.to_string().is_empty());
703            #[cfg(feature = "std")]
704            assert!(e.source().is_some());
705
706            // Out of range type
707            let mut decoder = Amount::decoder();
708            let _ =
709                decoder.push_bytes(&mut (21_000_001 * 100_000_000_u64).to_le_bytes().as_slice());
710            let e = decoder.end().unwrap_err();
711            assert!(!e.to_string().is_empty());
712            #[cfg(feature = "std")]
713            assert!(e.source().is_some());
714        }
715    }
716}