1#[cfg(feature = "alloc")]
9use alloc::string::{String, ToString};
10use core::cmp::Ordering;
11use core::str::FromStr;
12use core::{default, fmt, ops};
13
14#[cfg(feature = "serde")]
15use ::serde::{Deserialize, Serialize};
16#[cfg(feature = "arbitrary")]
17use arbitrary::{Arbitrary, Unstructured};
18use internals::error::InputString;
19use internals::write_err;
20
21#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
49#[non_exhaustive]
50pub enum Denomination {
51 Bitcoin,
53 CentiBitcoin,
55 MilliBitcoin,
57 MicroBitcoin,
59 Bit,
61 Satoshi,
63}
64
65impl Denomination {
66 pub const BTC: Self = Denomination::Bitcoin;
68
69 pub const SAT: Self = Denomination::Satoshi;
71
72 fn precision(self) -> i8 {
74 match self {
75 Denomination::Bitcoin => -8,
76 Denomination::CentiBitcoin => -6,
77 Denomination::MilliBitcoin => -5,
78 Denomination::MicroBitcoin => -2,
79 Denomination::Bit => -2,
80 Denomination::Satoshi => 0,
81 }
82 }
83
84 fn as_str(self) -> &'static str {
86 match self {
87 Denomination::Bitcoin => "BTC",
88 Denomination::CentiBitcoin => "cBTC",
89 Denomination::MilliBitcoin => "mBTC",
90 Denomination::MicroBitcoin => "uBTC",
91 Denomination::Bit => "bits",
92 Denomination::Satoshi => "satoshi",
93 }
94 }
95
96 fn forms(s: &str) -> Option<Self> {
98 match s {
99 "BTC" | "btc" => Some(Denomination::Bitcoin),
100 "cBTC" | "cbtc" => Some(Denomination::CentiBitcoin),
101 "mBTC" | "mbtc" => Some(Denomination::MilliBitcoin),
102 "uBTC" | "ubtc" => Some(Denomination::MicroBitcoin),
103 "bit" | "bits" | "BIT" | "BITS" => Some(Denomination::Bit),
104 "SATOSHI" | "satoshi" | "SATOSHIS" | "satoshis" | "SAT" | "sat" | "SATS" | "sats" =>
105 Some(Denomination::Satoshi),
106 _ => None,
107 }
108 }
109}
110
111const CONFUSING_FORMS: [&str; 6] = ["MBTC", "Mbtc", "CBTC", "Cbtc", "UBTC", "Ubtc"];
114
115impl fmt::Display for Denomination {
116 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_str()) }
117}
118
119impl FromStr for Denomination {
120 type Err = ParseDenominationError;
121
122 fn from_str(s: &str) -> Result<Self, Self::Err> {
131 use self::ParseDenominationError::*;
132
133 if CONFUSING_FORMS.contains(&s) {
134 return Err(PossiblyConfusing(PossiblyConfusingDenominationError(s.into())));
135 };
136
137 let form = self::Denomination::forms(s);
138
139 form.ok_or_else(|| Unknown(UnknownDenominationError(s.into())))
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145#[non_exhaustive]
146pub enum ParseError {
147 Amount(ParseAmountError),
149
150 Denomination(ParseDenominationError),
152
153 MissingDenomination(MissingDenominationError),
155}
156
157internals::impl_from_infallible!(ParseError);
158
159impl From<ParseAmountError> for ParseError {
160 fn from(e: ParseAmountError) -> Self { Self::Amount(e) }
161}
162
163impl From<ParseDenominationError> for ParseError {
164 fn from(e: ParseDenominationError) -> Self { Self::Denomination(e) }
165}
166
167impl From<OutOfRangeError> for ParseError {
168 fn from(e: OutOfRangeError) -> Self { Self::Amount(e.into()) }
169}
170
171impl From<TooPreciseError> for ParseError {
172 fn from(e: TooPreciseError) -> Self { Self::Amount(e.into()) }
173}
174
175impl From<MissingDigitsError> for ParseError {
176 fn from(e: MissingDigitsError) -> Self { Self::Amount(e.into()) }
177}
178
179impl From<InputTooLargeError> for ParseError {
180 fn from(e: InputTooLargeError) -> Self { Self::Amount(e.into()) }
181}
182
183impl From<InvalidCharacterError> for ParseError {
184 fn from(e: InvalidCharacterError) -> Self { Self::Amount(e.into()) }
185}
186
187impl fmt::Display for ParseError {
188 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189 match self {
190 ParseError::Amount(error) => write_err!(f, "invalid amount"; error),
191 ParseError::Denomination(error) => write_err!(f, "invalid denomination"; error),
192 ParseError::MissingDenomination(_) =>
195 f.write_str("the input doesn't contain a denomination"),
196 }
197 }
198}
199
200#[cfg(feature = "std")]
201impl std::error::Error for ParseError {
202 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
203 match self {
204 ParseError::Amount(error) => Some(error),
205 ParseError::Denomination(error) => Some(error),
206 ParseError::MissingDenomination(_) => None,
209 }
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
215#[non_exhaustive]
216pub enum ParseAmountError {
217 OutOfRange(OutOfRangeError),
219 TooPrecise(TooPreciseError),
221 MissingDigits(MissingDigitsError),
223 InputTooLarge(InputTooLargeError),
225 InvalidCharacter(InvalidCharacterError),
227}
228
229impl From<TooPreciseError> for ParseAmountError {
230 fn from(value: TooPreciseError) -> Self { Self::TooPrecise(value) }
231}
232
233impl From<MissingDigitsError> for ParseAmountError {
234 fn from(value: MissingDigitsError) -> Self { Self::MissingDigits(value) }
235}
236
237impl From<InputTooLargeError> for ParseAmountError {
238 fn from(value: InputTooLargeError) -> Self { Self::InputTooLarge(value) }
239}
240
241impl From<InvalidCharacterError> for ParseAmountError {
242 fn from(value: InvalidCharacterError) -> Self { Self::InvalidCharacter(value) }
243}
244
245internals::impl_from_infallible!(ParseAmountError);
246
247impl fmt::Display for ParseAmountError {
248 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249 use ParseAmountError::*;
250
251 match *self {
252 OutOfRange(ref error) => write_err!(f, "amount out of range"; error),
253 TooPrecise(ref error) => write_err!(f, "amount has a too high precision"; error),
254 MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error),
255 InputTooLarge(ref error) => write_err!(f, "the input is too large"; error),
256 InvalidCharacter(ref error) => write_err!(f, "invalid character in the input"; error),
257 }
258 }
259}
260
261#[cfg(feature = "std")]
262impl std::error::Error for ParseAmountError {
263 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
264 use ParseAmountError::*;
265
266 match *self {
267 TooPrecise(ref error) => Some(error),
268 InputTooLarge(ref error) => Some(error),
269 OutOfRange(ref error) => Some(error),
270 MissingDigits(ref error) => Some(error),
271 InvalidCharacter(ref error) => Some(error),
272 }
273 }
274}
275
276#[derive(Debug, Copy, Clone, Eq, PartialEq)]
278pub struct OutOfRangeError {
279 is_signed: bool,
280 is_greater_than_max: bool,
281}
282
283impl OutOfRangeError {
284 pub fn valid_range(&self) -> (i64, u64) {
288 match self.is_signed {
289 true => (i64::MIN, i64::MAX as u64),
290 false => (0, u64::MAX),
291 }
292 }
293
294 pub fn is_above_max(&self) -> bool { self.is_greater_than_max }
296
297 pub fn is_below_min(&self) -> bool { !self.is_greater_than_max }
299
300 pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
301
302 pub(crate) fn too_small() -> Self {
303 Self {
304 is_signed: true,
306 is_greater_than_max: false,
307 }
308 }
309
310 pub(crate) fn negative() -> Self {
311 Self {
312 is_signed: false,
314 is_greater_than_max: false,
315 }
316 }
317}
318
319impl fmt::Display for OutOfRangeError {
320 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
321 if self.is_greater_than_max {
322 write!(f, "the amount is greater than {}", self.valid_range().1)
323 } else {
324 write!(f, "the amount is less than {}", self.valid_range().0)
325 }
326 }
327}
328
329#[cfg(feature = "std")]
330impl std::error::Error for OutOfRangeError {}
331
332impl From<OutOfRangeError> for ParseAmountError {
333 fn from(value: OutOfRangeError) -> Self { ParseAmountError::OutOfRange(value) }
334}
335
336#[derive(Debug, Clone, Eq, PartialEq)]
338pub struct TooPreciseError {
339 position: usize,
340}
341
342impl fmt::Display for TooPreciseError {
343 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
344 match self.position {
345 0 => f.write_str("the amount is less than 1 satoshi but it's not zero"),
346 pos => write!(
347 f,
348 "the digits starting from position {} represent a sub-satoshi amount",
349 pos
350 ),
351 }
352 }
353}
354
355#[cfg(feature = "std")]
356impl std::error::Error for TooPreciseError {}
357
358#[derive(Debug, Clone, Eq, PartialEq)]
360pub struct InputTooLargeError {
361 len: usize,
362}
363
364impl fmt::Display for InputTooLargeError {
365 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
366 match self.len - INPUT_STRING_LEN_LIMIT {
367 1 => write!(
368 f,
369 "the input is one character longer than the maximum allowed length ({})",
370 INPUT_STRING_LEN_LIMIT
371 ),
372 n => write!(
373 f,
374 "the input is {} characters longer than the maximum allowed length ({})",
375 n, INPUT_STRING_LEN_LIMIT
376 ),
377 }
378 }
379}
380
381#[cfg(feature = "std")]
382impl std::error::Error for InputTooLargeError {}
383
384#[derive(Debug, Clone, Eq, PartialEq)]
388pub struct MissingDigitsError {
389 kind: MissingDigitsKind,
390}
391
392impl fmt::Display for MissingDigitsError {
393 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
394 match self.kind {
395 MissingDigitsKind::Empty => f.write_str("the input is empty"),
396 MissingDigitsKind::OnlyMinusSign =>
397 f.write_str("there are no digits following the minus (-) sign"),
398 }
399 }
400}
401
402#[cfg(feature = "std")]
403impl std::error::Error for MissingDigitsError {}
404
405#[derive(Debug, Clone, Eq, PartialEq)]
406enum MissingDigitsKind {
407 Empty,
408 OnlyMinusSign,
409}
410
411#[derive(Debug, Clone, PartialEq, Eq)]
413pub struct InvalidCharacterError {
414 invalid_char: char,
415 position: usize,
416}
417
418impl fmt::Display for InvalidCharacterError {
419 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
420 match self.invalid_char {
421 '.' => f.write_str("there is more than one decimal separator (dot) in the input"),
422 '-' => f.write_str("there is more than one minus sign (-) in the input"),
423 c => write!(
424 f,
425 "the character '{}' at position {} is not a valid digit",
426 c, self.position
427 ),
428 }
429 }
430}
431
432#[cfg(feature = "std")]
433impl std::error::Error for InvalidCharacterError {}
434
435#[derive(Debug, Clone, PartialEq, Eq)]
437#[non_exhaustive]
438pub enum ParseDenominationError {
439 Unknown(UnknownDenominationError),
441 PossiblyConfusing(PossiblyConfusingDenominationError),
443}
444
445internals::impl_from_infallible!(ParseDenominationError);
446
447impl fmt::Display for ParseDenominationError {
448 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449 use ParseDenominationError::*;
450
451 match *self {
452 Unknown(ref e) => write_err!(f, "denomination parse error"; e),
453 PossiblyConfusing(ref e) => write_err!(f, "denomination parse error"; e),
454 }
455 }
456}
457
458#[cfg(feature = "std")]
459impl std::error::Error for ParseDenominationError {
460 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
461 use ParseDenominationError::*;
462
463 match *self {
464 Unknown(_) | PossiblyConfusing(_) => None,
465 }
466 }
467}
468
469#[derive(Debug, Clone, PartialEq, Eq)]
471#[non_exhaustive]
472pub struct MissingDenominationError;
473
474#[derive(Debug, Clone, PartialEq, Eq)]
476#[non_exhaustive]
477pub struct UnknownDenominationError(InputString);
478
479impl fmt::Display for UnknownDenominationError {
480 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
481 self.0.unknown_variant("bitcoin denomination", f)
482 }
483}
484
485#[cfg(feature = "std")]
486impl std::error::Error for UnknownDenominationError {
487 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
488}
489
490#[derive(Debug, Clone, PartialEq, Eq)]
492#[non_exhaustive]
493pub struct PossiblyConfusingDenominationError(InputString);
494
495impl fmt::Display for PossiblyConfusingDenominationError {
496 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
497 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"))
498 }
499}
500
501#[cfg(feature = "std")]
502impl std::error::Error for PossiblyConfusingDenominationError {
503 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
504}
505
506fn is_too_precise(s: &str, precision: usize) -> Option<usize> {
510 match s.find('.') {
511 Some(pos) if precision >= pos => Some(0),
512 Some(pos) => s[..pos]
513 .char_indices()
514 .rev()
515 .take(precision)
516 .find(|(_, d)| *d != '0')
517 .map(|(i, _)| i)
518 .or_else(|| {
519 s[(pos + 1)..].char_indices().find(|(_, d)| *d != '0').map(|(i, _)| i + pos + 1)
520 }),
521 None if precision >= s.len() => Some(0),
522 None => s.char_indices().rev().take(precision).find(|(_, d)| *d != '0').map(|(i, _)| i),
523 }
524}
525
526const INPUT_STRING_LEN_LIMIT: usize = 50;
527
528fn parse_signed_to_satoshi(
531 mut s: &str,
532 denom: Denomination,
533) -> Result<(bool, u64), InnerParseError> {
534 if s.is_empty() {
535 return Err(InnerParseError::MissingDigits(MissingDigitsError {
536 kind: MissingDigitsKind::Empty,
537 }));
538 }
539 if s.len() > INPUT_STRING_LEN_LIMIT {
540 return Err(InnerParseError::InputTooLarge(s.len()));
541 }
542
543 let is_negative = s.starts_with('-');
544 if is_negative {
545 if s.len() == 1 {
546 return Err(InnerParseError::MissingDigits(MissingDigitsError {
547 kind: MissingDigitsKind::OnlyMinusSign,
548 }));
549 }
550 s = &s[1..];
551 }
552
553 let max_decimals = {
554 let precision_diff = -denom.precision();
557 if precision_diff <= 0 {
558 let last_n = precision_diff.unsigned_abs().into();
563 if let Some(position) = is_too_precise(s, last_n) {
564 match s.parse::<i64>() {
565 Ok(0) => return Ok((is_negative, 0)),
566 _ =>
567 return Err(InnerParseError::TooPrecise(TooPreciseError {
568 position: position + is_negative as usize,
569 })),
570 }
571 }
572 s = &s[0..s.find('.').unwrap_or(s.len()) - last_n];
573 0
574 } else {
575 precision_diff
576 }
577 };
578
579 let mut decimals = None;
580 let mut value: u64 = 0; for (i, c) in s.char_indices() {
582 match c {
583 '0'..='9' => {
584 match 10_u64.checked_mul(value) {
586 None => return Err(InnerParseError::Overflow { is_negative }),
587 Some(val) => match val.checked_add((c as u8 - b'0') as u64) {
588 None => return Err(InnerParseError::Overflow { is_negative }),
589 Some(val) => value = val,
590 },
591 }
592 decimals = match decimals {
594 None => None,
595 Some(d) if d < max_decimals => Some(d + 1),
596 _ =>
597 return Err(InnerParseError::TooPrecise(TooPreciseError {
598 position: i + is_negative as usize,
599 })),
600 };
601 }
602 '.' => match decimals {
603 None if max_decimals <= 0 => break,
604 None => decimals = Some(0),
605 _ =>
607 return Err(InnerParseError::InvalidCharacter(InvalidCharacterError {
608 invalid_char: '.',
609 position: i + is_negative as usize,
610 })),
611 },
612 c =>
613 return Err(InnerParseError::InvalidCharacter(InvalidCharacterError {
614 invalid_char: c,
615 position: i + is_negative as usize,
616 })),
617 }
618 }
619
620 let scale_factor = max_decimals - decimals.unwrap_or(0);
622 for _ in 0..scale_factor {
623 value = match 10_u64.checked_mul(value) {
624 Some(v) => v,
625 None => return Err(InnerParseError::Overflow { is_negative }),
626 };
627 }
628
629 Ok((is_negative, value))
630}
631
632enum InnerParseError {
633 Overflow { is_negative: bool },
634 TooPrecise(TooPreciseError),
635 MissingDigits(MissingDigitsError),
636 InputTooLarge(usize),
637 InvalidCharacter(InvalidCharacterError),
638}
639
640internals::impl_from_infallible!(InnerParseError);
641
642impl InnerParseError {
643 fn convert(self, is_signed: bool) -> ParseAmountError {
644 match self {
645 Self::Overflow { is_negative } =>
646 OutOfRangeError { is_signed, is_greater_than_max: !is_negative }.into(),
647 Self::TooPrecise(error) => ParseAmountError::TooPrecise(error),
648 Self::MissingDigits(error) => ParseAmountError::MissingDigits(error),
649 Self::InputTooLarge(len) => ParseAmountError::InputTooLarge(InputTooLargeError { len }),
650 Self::InvalidCharacter(error) => ParseAmountError::InvalidCharacter(error),
651 }
652 }
653}
654
655fn split_amount_and_denomination(s: &str) -> Result<(&str, Denomination), ParseError> {
656 let (i, j) = if let Some(i) = s.find(' ') {
657 (i, i + 1)
658 } else {
659 let i = s
660 .find(|c: char| c.is_alphabetic())
661 .ok_or(ParseError::MissingDenomination(MissingDenominationError))?;
662 (i, i)
663 };
664 Ok((&s[..i], s[j..].parse()?))
665}
666
667struct FormatOptions {
669 fill: char,
670 align: Option<fmt::Alignment>,
671 width: Option<usize>,
672 precision: Option<usize>,
673 sign_plus: bool,
674 sign_aware_zero_pad: bool,
675}
676
677impl FormatOptions {
678 fn from_formatter(f: &fmt::Formatter) -> Self {
679 FormatOptions {
680 fill: f.fill(),
681 align: f.align(),
682 width: f.width(),
683 precision: f.precision(),
684 sign_plus: f.sign_plus(),
685 sign_aware_zero_pad: f.sign_aware_zero_pad(),
686 }
687 }
688}
689
690impl Default for FormatOptions {
691 fn default() -> Self {
692 FormatOptions {
693 fill: ' ',
694 align: None,
695 width: None,
696 precision: None,
697 sign_plus: false,
698 sign_aware_zero_pad: false,
699 }
700 }
701}
702
703fn dec_width(mut num: u64) -> usize {
704 let mut width = 1;
705 loop {
706 num /= 10;
707 if num == 0 {
708 break;
709 }
710 width += 1;
711 }
712 width
713}
714
715fn repeat_char(f: &mut dyn fmt::Write, c: char, count: usize) -> fmt::Result {
716 for _ in 0..count {
717 f.write_char(c)?;
718 }
719 Ok(())
720}
721
722fn fmt_satoshi_in(
724 mut satoshi: u64,
725 negative: bool,
726 f: &mut dyn fmt::Write,
727 denom: Denomination,
728 show_denom: bool,
729 options: FormatOptions,
730) -> fmt::Result {
731 let precision = denom.precision();
732 let mut num_after_decimal_point = 0;
735 let mut norm_nb_decimals = 0;
736 let mut num_before_decimal_point = satoshi;
737 let trailing_decimal_zeros;
738 let mut exp = 0;
739 match precision.cmp(&0) {
740 Ordering::Greater => {
742 if satoshi > 0 {
743 exp = precision as usize;
744 }
745 trailing_decimal_zeros = options.precision.unwrap_or(0);
746 }
747 Ordering::Less => {
748 let precision = precision.unsigned_abs();
749 if let Some(format_precision) = options.precision {
752 if usize::from(precision) > format_precision {
753 let rounding_divisor =
755 10u64.pow(u32::from(precision) - format_precision as u32);
756 let remainder = satoshi % rounding_divisor;
757 satoshi -= remainder;
758 if remainder / (rounding_divisor / 10) >= 5 {
759 satoshi += rounding_divisor;
760 }
761 }
762 }
763 let divisor = 10u64.pow(precision.into());
764 num_before_decimal_point = satoshi / divisor;
765 num_after_decimal_point = satoshi % divisor;
766 if num_after_decimal_point == 0 {
768 norm_nb_decimals = 0;
769 } else {
770 norm_nb_decimals = usize::from(precision);
771 while num_after_decimal_point % 10 == 0 {
772 norm_nb_decimals -= 1;
773 num_after_decimal_point /= 10
774 }
775 }
776 let opt_precision = options.precision.unwrap_or(0);
778 trailing_decimal_zeros = opt_precision.saturating_sub(norm_nb_decimals);
779 }
780 Ordering::Equal => trailing_decimal_zeros = options.precision.unwrap_or(0),
781 }
782 let total_decimals = norm_nb_decimals + trailing_decimal_zeros;
783 let mut num_width = if total_decimals > 0 {
785 1 + total_decimals
787 } else {
788 0
789 };
790 num_width += dec_width(num_before_decimal_point) + exp;
791 if options.sign_plus || negative {
792 num_width += 1;
793 }
794
795 if show_denom {
796 num_width += denom.as_str().len() + 1;
798 }
799
800 let width = options.width.unwrap_or(0);
801 let align = options.align.unwrap_or(fmt::Alignment::Right);
802 let (left_pad, pad_right) = match (num_width < width, options.sign_aware_zero_pad, align) {
803 (false, _, _) => (0, 0),
804 (true, true, _) | (true, false, fmt::Alignment::Right) => (width - num_width, 0),
806 (true, false, fmt::Alignment::Left) => (0, width - num_width),
807 (true, false, fmt::Alignment::Center) =>
809 ((width - num_width) / 2, (width - num_width + 1) / 2),
810 };
811
812 if !options.sign_aware_zero_pad {
813 repeat_char(f, options.fill, left_pad)?;
814 }
815
816 if negative {
817 write!(f, "-")?;
818 } else if options.sign_plus {
819 write!(f, "+")?;
820 }
821
822 if options.sign_aware_zero_pad {
823 repeat_char(f, '0', left_pad)?;
824 }
825
826 write!(f, "{}", num_before_decimal_point)?;
827
828 repeat_char(f, '0', exp)?;
829
830 if total_decimals > 0 {
831 write!(f, ".")?;
832 }
833 if norm_nb_decimals > 0 {
834 write!(f, "{:0width$}", num_after_decimal_point, width = norm_nb_decimals)?;
835 }
836 repeat_char(f, '0', trailing_decimal_zeros)?;
837
838 if show_denom {
839 write!(f, " {}", denom.as_str())?;
840 }
841
842 repeat_char(f, options.fill, pad_right)?;
843 Ok(())
844}
845
846#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
864#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
865pub struct Amount(u64);
866
867impl Amount {
868 pub const ZERO: Amount = Amount(0);
870 pub const ONE_SAT: Amount = Amount(1);
872 pub const ONE_BTC: Amount = Self::from_int_btc(1);
874 pub const MAX_MONEY: Amount = Self::from_int_btc(21_000_000);
876 pub const MIN: Amount = Amount::ZERO;
878 pub const MAX: Amount = Amount(u64::MAX);
880 pub const SIZE: usize = 8; pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) }
885
886 pub fn to_sat(self) -> u64 { self.0 }
888
889 #[cfg(feature = "alloc")]
891 pub fn from_btc(btc: f64) -> Result<Amount, ParseAmountError> {
892 Amount::from_float_in(btc, Denomination::Bitcoin)
893 }
894
895 pub const fn from_int_btc(btc: u64) -> Amount {
903 match btc.checked_mul(100_000_000) {
904 Some(amount) => Amount::from_sat(amount),
905 None => panic!("checked_mul overflowed"),
906 }
907 }
908
909 pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
914 let (negative, satoshi) =
915 parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(false))?;
916 if negative {
917 return Err(ParseAmountError::OutOfRange(OutOfRangeError::negative()));
918 }
919 Ok(Amount::from_sat(satoshi))
920 }
921
922 pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseError> {
927 let (amt, denom) = split_amount_and_denomination(s)?;
928 Amount::from_str_in(amt, denom).map_err(Into::into)
929 }
930
931 #[cfg(feature = "alloc")]
935 pub fn to_float_in(self, denom: Denomination) -> f64 {
936 self.to_string_in(denom).parse::<f64>().unwrap()
937 }
938
939 #[cfg(feature = "alloc")]
951 pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
952
953 #[cfg(feature = "alloc")]
962 pub fn from_float_in(value: f64, denom: Denomination) -> Result<Amount, ParseAmountError> {
963 if value < 0.0 {
964 return Err(OutOfRangeError::negative().into());
965 }
966 Amount::from_str_in(&value.to_string(), denom)
969 }
970
971 pub fn display_in(self, denomination: Denomination) -> Display {
973 Display {
974 sats_abs: self.to_sat(),
975 is_negative: false,
976 style: DisplayStyle::FixedDenomination { denomination, show_denomination: false },
977 }
978 }
979
980 pub fn display_dynamic(self) -> Display {
985 Display {
986 sats_abs: self.to_sat(),
987 is_negative: false,
988 style: DisplayStyle::DynamicDenomination,
989 }
990 }
991
992 #[rustfmt::skip]
996 #[deprecated(since = "TBD", note = "Use `display_in()` instead")]
997 pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
998 fmt_satoshi_in(self.to_sat(), false, f, denom, false, FormatOptions::default())
999 }
1000
1001 #[cfg(feature = "alloc")]
1005 pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
1006
1007 #[cfg(feature = "alloc")]
1010 pub fn to_string_with_denomination(self, denom: Denomination) -> String {
1011 self.display_in(denom).show_denomination().to_string()
1012 }
1013
1014 pub fn checked_add(self, rhs: Amount) -> Option<Amount> {
1020 self.0.checked_add(rhs.0).map(Amount)
1021 }
1022
1023 pub fn checked_sub(self, rhs: Amount) -> Option<Amount> {
1027 self.0.checked_sub(rhs.0).map(Amount)
1028 }
1029
1030 pub fn checked_mul(self, rhs: u64) -> Option<Amount> { self.0.checked_mul(rhs).map(Amount) }
1034
1035 pub fn checked_div(self, rhs: u64) -> Option<Amount> { self.0.checked_div(rhs).map(Amount) }
1041
1042 pub fn checked_rem(self, rhs: u64) -> Option<Amount> { self.0.checked_rem(rhs).map(Amount) }
1046
1047 pub fn unchecked_add(self, rhs: Amount) -> Amount { Self(self.0 + rhs.0) }
1055
1056 pub fn unchecked_sub(self, rhs: Amount) -> Amount { Self(self.0 - rhs.0) }
1064
1065 pub fn to_signed(self) -> Result<SignedAmount, OutOfRangeError> {
1067 if self.to_sat() > SignedAmount::MAX.to_sat() as u64 {
1068 Err(OutOfRangeError::too_big(true))
1069 } else {
1070 Ok(SignedAmount::from_sat(self.to_sat() as i64))
1071 }
1072 }
1073}
1074
1075#[cfg(feature = "arbitrary")]
1076impl<'a> Arbitrary<'a> for Amount {
1077 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1078 let a = u64::arbitrary(u)?;
1079 Ok(Amount(a))
1080 }
1081}
1082
1083impl default::Default for Amount {
1084 fn default() -> Self { Amount::ZERO }
1085}
1086
1087impl fmt::Debug for Amount {
1088 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} SAT", self.to_sat()) }
1089}
1090
1091impl fmt::Display for Amount {
1094 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1095 fmt::Display::fmt(&self.display_in(Denomination::Bitcoin).show_denomination(), f)
1096 }
1097}
1098
1099impl ops::Add for Amount {
1100 type Output = Amount;
1101
1102 fn add(self, rhs: Amount) -> Self::Output {
1103 self.checked_add(rhs).expect("Amount addition error")
1104 }
1105}
1106
1107impl ops::AddAssign for Amount {
1108 fn add_assign(&mut self, other: Amount) { *self = *self + other }
1109}
1110
1111impl ops::Sub for Amount {
1112 type Output = Amount;
1113
1114 fn sub(self, rhs: Amount) -> Self::Output {
1115 self.checked_sub(rhs).expect("Amount subtraction error")
1116 }
1117}
1118
1119impl ops::SubAssign for Amount {
1120 fn sub_assign(&mut self, other: Amount) { *self = *self - other }
1121}
1122
1123impl ops::Rem<u64> for Amount {
1124 type Output = Amount;
1125
1126 fn rem(self, modulus: u64) -> Self {
1127 self.checked_rem(modulus).expect("Amount remainder error")
1128 }
1129}
1130
1131impl ops::RemAssign<u64> for Amount {
1132 fn rem_assign(&mut self, modulus: u64) { *self = *self % modulus }
1133}
1134
1135impl ops::Mul<u64> for Amount {
1136 type Output = Amount;
1137
1138 fn mul(self, rhs: u64) -> Self::Output {
1139 self.checked_mul(rhs).expect("Amount multiplication error")
1140 }
1141}
1142
1143impl ops::MulAssign<u64> for Amount {
1144 fn mul_assign(&mut self, rhs: u64) { *self = *self * rhs }
1145}
1146
1147impl ops::Div<u64> for Amount {
1148 type Output = Amount;
1149
1150 fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).expect("Amount division error") }
1151}
1152
1153impl ops::DivAssign<u64> for Amount {
1154 fn div_assign(&mut self, rhs: u64) { *self = *self / rhs }
1155}
1156
1157impl FromStr for Amount {
1158 type Err = ParseError;
1159
1160 fn from_str(s: &str) -> Result<Self, Self::Err> {
1169 let result = Amount::from_str_with_denomination(s);
1170
1171 match result {
1172 Err(ParseError::MissingDenomination(_)) => {
1173 let d = Amount::from_str_in(s, Denomination::Satoshi);
1174
1175 if d == Ok(Amount::ZERO) {
1176 Ok(Amount::ZERO)
1177 } else {
1178 result
1179 }
1180 }
1181 _ => result,
1182 }
1183 }
1184}
1185
1186impl TryFrom<SignedAmount> for Amount {
1187 type Error = OutOfRangeError;
1188
1189 fn try_from(value: SignedAmount) -> Result<Self, Self::Error> { value.to_unsigned() }
1190}
1191
1192impl core::iter::Sum for Amount {
1193 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
1194 let sats: u64 = iter.map(|amt| amt.0).sum();
1195 Amount::from_sat(sats)
1196 }
1197}
1198
1199#[derive(Debug, Clone)]
1217pub struct Display {
1218 sats_abs: u64,
1220 is_negative: bool,
1222 style: DisplayStyle,
1224}
1225
1226impl Display {
1227 pub fn show_denomination(mut self) -> Self {
1229 match &mut self.style {
1230 DisplayStyle::FixedDenomination { show_denomination, .. } => *show_denomination = true,
1231 DisplayStyle::DynamicDenomination => (),
1233 }
1234 self
1235 }
1236}
1237
1238impl fmt::Display for Display {
1239 #[rustfmt::skip]
1240 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1241 let format_options = FormatOptions::from_formatter(f);
1242 match &self.style {
1243 DisplayStyle::FixedDenomination { show_denomination, denomination } => {
1244 fmt_satoshi_in(self.sats_abs, self.is_negative, f, *denomination, *show_denomination, format_options)
1245 },
1246 DisplayStyle::DynamicDenomination if self.sats_abs >= Amount::ONE_BTC.to_sat() => {
1247 fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Bitcoin, true, format_options)
1248 },
1249 DisplayStyle::DynamicDenomination => {
1250 fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Satoshi, true, format_options)
1251 },
1252 }
1253 }
1254}
1255
1256#[derive(Clone, Debug)]
1257enum DisplayStyle {
1258 FixedDenomination { denomination: Denomination, show_denomination: bool },
1259 DynamicDenomination,
1260}
1261
1262#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1277pub struct SignedAmount(i64);
1278
1279impl SignedAmount {
1280 pub const ZERO: SignedAmount = SignedAmount(0);
1282 pub const ONE_SAT: SignedAmount = SignedAmount(1);
1284 pub const ONE_BTC: SignedAmount = SignedAmount(100_000_000);
1286 pub const MAX_MONEY: SignedAmount = SignedAmount(21_000_000 * 100_000_000);
1288 pub const MIN: SignedAmount = SignedAmount(i64::MIN);
1290 pub const MAX: SignedAmount = SignedAmount(i64::MAX);
1292
1293 pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
1295
1296 pub fn to_sat(self) -> i64 { self.0 }
1298
1299 #[cfg(feature = "alloc")]
1301 pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
1302 SignedAmount::from_float_in(btc, Denomination::Bitcoin)
1303 }
1304
1305 pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAmountError> {
1310 match parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(true))? {
1311 (false, sat) if sat > i64::MAX as u64 =>
1313 Err(ParseAmountError::OutOfRange(OutOfRangeError::too_big(true))),
1314 (false, sat) => Ok(SignedAmount(sat as i64)),
1315 (true, sat) if sat == i64::MIN.unsigned_abs() => Ok(SignedAmount(i64::MIN)),
1316 (true, sat) if sat > i64::MIN.unsigned_abs() =>
1317 Err(ParseAmountError::OutOfRange(OutOfRangeError::too_small())),
1318 (true, sat) => Ok(SignedAmount(-(sat as i64))),
1319 }
1320 }
1321
1322 pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseError> {
1327 let (amt, denom) = split_amount_and_denomination(s)?;
1328 SignedAmount::from_str_in(amt, denom).map_err(Into::into)
1329 }
1330
1331 #[cfg(feature = "alloc")]
1335 pub fn to_float_in(self, denom: Denomination) -> f64 {
1336 self.to_string_in(denom).parse::<f64>().unwrap()
1337 }
1338
1339 #[cfg(feature = "alloc")]
1345 pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
1346
1347 #[cfg(feature = "alloc")]
1356 pub fn from_float_in(
1357 value: f64,
1358 denom: Denomination,
1359 ) -> Result<SignedAmount, ParseAmountError> {
1360 SignedAmount::from_str_in(&value.to_string(), denom)
1363 }
1364
1365 pub fn display_in(self, denomination: Denomination) -> Display {
1367 Display {
1368 sats_abs: self.unsigned_abs().to_sat(),
1369 is_negative: self.is_negative(),
1370 style: DisplayStyle::FixedDenomination { denomination, show_denomination: false },
1371 }
1372 }
1373
1374 pub fn display_dynamic(self) -> Display {
1379 Display {
1380 sats_abs: self.unsigned_abs().to_sat(),
1381 is_negative: self.is_negative(),
1382 style: DisplayStyle::DynamicDenomination,
1383 }
1384 }
1385
1386 #[rustfmt::skip]
1390 #[deprecated(since = "TBD", note = "Use `display_in()` instead")]
1391 pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
1392 fmt_satoshi_in(self.unsigned_abs().to_sat(), self.is_negative(), f, denom, false, FormatOptions::default())
1393 }
1394
1395 #[cfg(feature = "alloc")]
1399 pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
1400
1401 #[cfg(feature = "alloc")]
1404 pub fn to_string_with_denomination(self, denom: Denomination) -> String {
1405 self.display_in(denom).show_denomination().to_string()
1406 }
1407
1408 pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
1412
1413 pub fn unsigned_abs(self) -> Amount { Amount(self.0.unsigned_abs()) }
1415
1416 pub fn signum(self) -> i64 { self.0.signum() }
1422
1423 pub fn is_positive(self) -> bool { self.0.is_positive() }
1428
1429 pub fn is_negative(self) -> bool { self.0.is_negative() }
1434
1435 pub fn checked_abs(self) -> Option<SignedAmount> { self.0.checked_abs().map(SignedAmount) }
1441
1442 pub fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
1446 self.0.checked_add(rhs.0).map(SignedAmount)
1447 }
1448
1449 pub fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
1453 self.0.checked_sub(rhs.0).map(SignedAmount)
1454 }
1455
1456 pub fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
1460 self.0.checked_mul(rhs).map(SignedAmount)
1461 }
1462
1463 pub fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
1470 self.0.checked_div(rhs).map(SignedAmount)
1471 }
1472
1473 pub fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
1477 self.0.checked_rem(rhs).map(SignedAmount)
1478 }
1479
1480 pub fn unchecked_add(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 + rhs.0) }
1488
1489 pub fn unchecked_sub(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 - rhs.0) }
1497
1498 pub fn positive_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
1502 if self.is_negative() || rhs.is_negative() || rhs > self {
1503 None
1504 } else {
1505 self.checked_sub(rhs)
1506 }
1507 }
1508
1509 pub fn to_unsigned(self) -> Result<Amount, OutOfRangeError> {
1511 if self.is_negative() {
1512 Err(OutOfRangeError::negative())
1513 } else {
1514 Ok(Amount::from_sat(self.to_sat() as u64))
1515 }
1516 }
1517}
1518
1519impl default::Default for SignedAmount {
1520 fn default() -> Self { SignedAmount::ZERO }
1521}
1522
1523impl fmt::Debug for SignedAmount {
1524 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1525 write!(f, "SignedAmount({} SAT)", self.to_sat())
1526 }
1527}
1528
1529impl fmt::Display for SignedAmount {
1532 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1533 fmt::Display::fmt(&self.display_in(Denomination::Bitcoin).show_denomination(), f)
1534 }
1535}
1536
1537impl ops::Add for SignedAmount {
1538 type Output = SignedAmount;
1539
1540 fn add(self, rhs: SignedAmount) -> Self::Output {
1541 self.checked_add(rhs).expect("SignedAmount addition error")
1542 }
1543}
1544
1545impl ops::AddAssign for SignedAmount {
1546 fn add_assign(&mut self, other: SignedAmount) { *self = *self + other }
1547}
1548
1549impl ops::Sub for SignedAmount {
1550 type Output = SignedAmount;
1551
1552 fn sub(self, rhs: SignedAmount) -> Self::Output {
1553 self.checked_sub(rhs).expect("SignedAmount subtraction error")
1554 }
1555}
1556
1557impl ops::SubAssign for SignedAmount {
1558 fn sub_assign(&mut self, other: SignedAmount) { *self = *self - other }
1559}
1560
1561impl ops::Rem<i64> for SignedAmount {
1562 type Output = SignedAmount;
1563
1564 fn rem(self, modulus: i64) -> Self {
1565 self.checked_rem(modulus).expect("SignedAmount remainder error")
1566 }
1567}
1568
1569impl ops::RemAssign<i64> for SignedAmount {
1570 fn rem_assign(&mut self, modulus: i64) { *self = *self % modulus }
1571}
1572
1573impl ops::Mul<i64> for SignedAmount {
1574 type Output = SignedAmount;
1575
1576 fn mul(self, rhs: i64) -> Self::Output {
1577 self.checked_mul(rhs).expect("SignedAmount multiplication error")
1578 }
1579}
1580
1581impl ops::MulAssign<i64> for SignedAmount {
1582 fn mul_assign(&mut self, rhs: i64) { *self = *self * rhs }
1583}
1584
1585impl ops::Div<i64> for SignedAmount {
1586 type Output = SignedAmount;
1587
1588 fn div(self, rhs: i64) -> Self::Output {
1589 self.checked_div(rhs).expect("SignedAmount division error")
1590 }
1591}
1592
1593impl ops::DivAssign<i64> for SignedAmount {
1594 fn div_assign(&mut self, rhs: i64) { *self = *self / rhs }
1595}
1596
1597impl ops::Neg for SignedAmount {
1598 type Output = Self;
1599
1600 fn neg(self) -> Self::Output { Self(self.0.neg()) }
1601}
1602
1603impl FromStr for SignedAmount {
1604 type Err = ParseError;
1605
1606 fn from_str(s: &str) -> Result<Self, Self::Err> {
1615 let result = SignedAmount::from_str_with_denomination(s);
1616
1617 match result {
1618 Err(ParseError::MissingDenomination(_)) => {
1619 let d = SignedAmount::from_str_in(s, Denomination::Satoshi);
1620
1621 if d == Ok(SignedAmount::ZERO) {
1622 Ok(SignedAmount::ZERO)
1623 } else {
1624 result
1625 }
1626 }
1627 _ => result,
1628 }
1629 }
1630}
1631
1632impl TryFrom<Amount> for SignedAmount {
1633 type Error = OutOfRangeError;
1634
1635 fn try_from(value: Amount) -> Result<Self, Self::Error> { value.to_signed() }
1636}
1637
1638impl core::iter::Sum for SignedAmount {
1639 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
1640 let sats: i64 = iter.map(|amt| amt.0).sum();
1641 SignedAmount::from_sat(sats)
1642 }
1643}
1644
1645#[cfg(feature = "arbitrary")]
1646impl<'a> Arbitrary<'a> for SignedAmount {
1647 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1648 let s = i64::arbitrary(u)?;
1649 Ok(SignedAmount(s))
1650 }
1651}
1652
1653pub trait CheckedSum<R>: private::SumSeal<R> {
1655 fn checked_sum(self) -> Option<R>;
1658}
1659
1660impl<T> CheckedSum<Amount> for T
1661where
1662 T: Iterator<Item = Amount>,
1663{
1664 fn checked_sum(mut self) -> Option<Amount> {
1665 let first = Some(self.next().unwrap_or_default());
1666
1667 self.fold(first, |acc, item| acc.and_then(|acc| acc.checked_add(item)))
1668 }
1669}
1670
1671impl<T> CheckedSum<SignedAmount> for T
1672where
1673 T: Iterator<Item = SignedAmount>,
1674{
1675 fn checked_sum(mut self) -> Option<SignedAmount> {
1676 let first = Some(self.next().unwrap_or_default());
1677
1678 self.fold(first, |acc, item| acc.and_then(|acc| acc.checked_add(item)))
1679 }
1680}
1681
1682mod private {
1683 use super::{Amount, SignedAmount};
1684
1685 pub trait SumSeal<A> {}
1687
1688 impl<T> SumSeal<Amount> for T where T: Iterator<Item = Amount> {}
1689 impl<T> SumSeal<SignedAmount> for T where T: Iterator<Item = SignedAmount> {}
1690}
1691
1692#[cfg(feature = "serde")]
1693pub mod serde {
1694 #![allow(missing_docs)]
1696
1697 use core::fmt;
1715
1716 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1717
1718 #[cfg(feature = "alloc")]
1719 use super::Denomination;
1720 use super::{Amount, ParseAmountError, SignedAmount};
1721
1722 pub trait SerdeAmount: Copy + Sized {
1725 fn ser_sat<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1726 fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error>;
1727 #[cfg(feature = "alloc")]
1728 fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1729 #[cfg(feature = "alloc")]
1730 fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error>;
1731 }
1732
1733 mod private {
1734 pub struct Token;
1736 }
1737
1738 pub trait SerdeAmountForOpt: Copy + Sized + SerdeAmount {
1740 fn type_prefix(_: private::Token) -> &'static str;
1741 fn ser_sat_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1742 #[cfg(feature = "alloc")]
1743 fn ser_btc_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1744 }
1745
1746 struct DisplayFullError(ParseAmountError);
1747
1748 #[cfg(feature = "std")]
1749 impl fmt::Display for DisplayFullError {
1750 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1751 use std::error::Error;
1752
1753 fmt::Display::fmt(&self.0, f)?;
1754 let mut source_opt = self.0.source();
1755 while let Some(source) = source_opt {
1756 write!(f, ": {}", source)?;
1757 source_opt = source.source();
1758 }
1759 Ok(())
1760 }
1761 }
1762
1763 #[cfg(not(feature = "std"))]
1764 impl fmt::Display for DisplayFullError {
1765 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
1766 }
1767
1768 impl SerdeAmount for Amount {
1769 fn ser_sat<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1770 u64::serialize(&self.to_sat(), s)
1771 }
1772 fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1773 Ok(Amount::from_sat(u64::deserialize(d)?))
1774 }
1775 #[cfg(feature = "alloc")]
1776 fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1777 f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
1778 }
1779 #[cfg(feature = "alloc")]
1780 fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1781 use serde::de::Error;
1782 Amount::from_btc(f64::deserialize(d)?)
1783 .map_err(DisplayFullError)
1784 .map_err(D::Error::custom)
1785 }
1786 }
1787
1788 impl SerdeAmountForOpt for Amount {
1789 fn type_prefix(_: private::Token) -> &'static str { "u" }
1790 fn ser_sat_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1791 s.serialize_some(&self.to_sat())
1792 }
1793 #[cfg(feature = "alloc")]
1794 fn ser_btc_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1795 s.serialize_some(&self.to_btc())
1796 }
1797 }
1798
1799 impl SerdeAmount for SignedAmount {
1800 fn ser_sat<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1801 i64::serialize(&self.to_sat(), s)
1802 }
1803 fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1804 Ok(SignedAmount::from_sat(i64::deserialize(d)?))
1805 }
1806 #[cfg(feature = "alloc")]
1807 fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1808 f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
1809 }
1810 #[cfg(feature = "alloc")]
1811 fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1812 use serde::de::Error;
1813 SignedAmount::from_btc(f64::deserialize(d)?)
1814 .map_err(DisplayFullError)
1815 .map_err(D::Error::custom)
1816 }
1817 }
1818
1819 impl SerdeAmountForOpt for SignedAmount {
1820 fn type_prefix(_: private::Token) -> &'static str { "i" }
1821 fn ser_sat_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1822 s.serialize_some(&self.to_sat())
1823 }
1824 #[cfg(feature = "alloc")]
1825 fn ser_btc_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1826 s.serialize_some(&self.to_btc())
1827 }
1828 }
1829
1830 pub mod as_sat {
1831 use serde::{Deserializer, Serializer};
1835
1836 use super::private;
1837 use crate::amount::serde::SerdeAmount;
1838
1839 pub fn serialize<A: SerdeAmount, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error> {
1840 a.ser_sat(s, private::Token)
1841 }
1842
1843 pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result<A, D::Error> {
1844 A::des_sat(d, private::Token)
1845 }
1846
1847 pub mod opt {
1848 use core::fmt;
1852 use core::marker::PhantomData;
1853
1854 use serde::{de, Deserializer, Serializer};
1855
1856 use super::private;
1857 use crate::amount::serde::SerdeAmountForOpt;
1858
1859 pub fn serialize<A: SerdeAmountForOpt, S: Serializer>(
1860 a: &Option<A>,
1861 s: S,
1862 ) -> Result<S::Ok, S::Error> {
1863 match *a {
1864 Some(a) => a.ser_sat_opt(s, private::Token),
1865 None => s.serialize_none(),
1866 }
1867 }
1868
1869 pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>(
1870 d: D,
1871 ) -> Result<Option<A>, D::Error> {
1872 struct VisitOptAmt<X>(PhantomData<X>);
1873
1874 impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt<X> {
1875 type Value = Option<X>;
1876
1877 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1878 write!(formatter, "An Option<{}64>", X::type_prefix(private::Token))
1879 }
1880
1881 fn visit_none<E>(self) -> Result<Self::Value, E>
1882 where
1883 E: de::Error,
1884 {
1885 Ok(None)
1886 }
1887 fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
1888 where
1889 D: Deserializer<'de>,
1890 {
1891 Ok(Some(X::des_sat(d, private::Token)?))
1892 }
1893 }
1894 d.deserialize_option(VisitOptAmt::<A>(PhantomData))
1895 }
1896 }
1897 }
1898
1899 #[cfg(feature = "alloc")]
1900 pub mod as_btc {
1901 use serde::{Deserializer, Serializer};
1905
1906 use super::private;
1907 use crate::amount::serde::SerdeAmount;
1908
1909 pub fn serialize<A: SerdeAmount, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error> {
1910 a.ser_btc(s, private::Token)
1911 }
1912
1913 pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result<A, D::Error> {
1914 A::des_btc(d, private::Token)
1915 }
1916
1917 pub mod opt {
1918 use core::fmt;
1922 use core::marker::PhantomData;
1923
1924 use serde::{de, Deserializer, Serializer};
1925
1926 use super::private;
1927 use crate::amount::serde::SerdeAmountForOpt;
1928
1929 pub fn serialize<A: SerdeAmountForOpt, S: Serializer>(
1930 a: &Option<A>,
1931 s: S,
1932 ) -> Result<S::Ok, S::Error> {
1933 match *a {
1934 Some(a) => a.ser_btc_opt(s, private::Token),
1935 None => s.serialize_none(),
1936 }
1937 }
1938
1939 pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>(
1940 d: D,
1941 ) -> Result<Option<A>, D::Error> {
1942 struct VisitOptAmt<X>(PhantomData<X>);
1943
1944 impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt<X> {
1945 type Value = Option<X>;
1946
1947 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1948 write!(formatter, "An Option<f64>")
1949 }
1950
1951 fn visit_none<E>(self) -> Result<Self::Value, E>
1952 where
1953 E: de::Error,
1954 {
1955 Ok(None)
1956 }
1957 fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
1958 where
1959 D: Deserializer<'de>,
1960 {
1961 Ok(Some(X::des_btc(d, private::Token)?))
1962 }
1963 }
1964 d.deserialize_option(VisitOptAmt::<A>(PhantomData))
1965 }
1966 }
1967 }
1968}
1969
1970#[cfg(kani)]
1971mod verification {
1972 use std::cmp;
1973
1974 use super::*;
1975
1976 #[kani::unwind(4)]
1989 #[kani::proof]
1990 fn u_amount_homomorphic() {
1991 let n1 = kani::any::<u64>();
1992 let n2 = kani::any::<u64>();
1993 kani::assume(n1.checked_add(n2).is_some()); assert_eq!(Amount::from_sat(n1) + Amount::from_sat(n2), Amount::from_sat(n1 + n2));
1995
1996 let mut amt = Amount::from_sat(n1);
1997 amt += Amount::from_sat(n2);
1998 assert_eq!(amt, Amount::from_sat(n1 + n2));
1999
2000 let max = cmp::max(n1, n2);
2001 let min = cmp::min(n1, n2);
2002 assert_eq!(Amount::from_sat(max) - Amount::from_sat(min), Amount::from_sat(max - min));
2003
2004 let mut amt = Amount::from_sat(max);
2005 amt -= Amount::from_sat(min);
2006 assert_eq!(amt, Amount::from_sat(max - min));
2007
2008 assert_eq!(
2009 Amount::from_sat(n1).to_signed(),
2010 if n1 <= i64::MAX as u64 {
2011 Ok(SignedAmount::from_sat(n1.try_into().unwrap()))
2012 } else {
2013 Err(OutOfRangeError::too_big(true))
2014 },
2015 );
2016 }
2017
2018 #[kani::unwind(4)]
2019 #[kani::proof]
2020 fn u_amount_homomorphic_checked() {
2021 let n1 = kani::any::<u64>();
2022 let n2 = kani::any::<u64>();
2023 assert_eq!(
2024 Amount::from_sat(n1).checked_add(Amount::from_sat(n2)),
2025 n1.checked_add(n2).map(Amount::from_sat),
2026 );
2027 assert_eq!(
2028 Amount::from_sat(n1).checked_sub(Amount::from_sat(n2)),
2029 n1.checked_sub(n2).map(Amount::from_sat),
2030 );
2031 }
2032
2033 #[kani::unwind(4)]
2034 #[kani::proof]
2035 fn s_amount_homomorphic() {
2036 let n1 = kani::any::<i64>();
2037 let n2 = kani::any::<i64>();
2038 kani::assume(n1.checked_add(n2).is_some()); kani::assume(n1.checked_sub(n2).is_some()); assert_eq!(
2041 SignedAmount::from_sat(n1) + SignedAmount::from_sat(n2),
2042 SignedAmount::from_sat(n1 + n2)
2043 );
2044 assert_eq!(
2045 SignedAmount::from_sat(n1) - SignedAmount::from_sat(n2),
2046 SignedAmount::from_sat(n1 - n2)
2047 );
2048
2049 let mut amt = SignedAmount::from_sat(n1);
2050 amt += SignedAmount::from_sat(n2);
2051 assert_eq!(amt, SignedAmount::from_sat(n1 + n2));
2052 let mut amt = SignedAmount::from_sat(n1);
2053 amt -= SignedAmount::from_sat(n2);
2054 assert_eq!(amt, SignedAmount::from_sat(n1 - n2));
2055
2056 assert_eq!(
2057 SignedAmount::from_sat(n1).to_unsigned(),
2058 if n1 >= 0 {
2059 Ok(Amount::from_sat(n1.try_into().unwrap()))
2060 } else {
2061 Err(OutOfRangeError { is_signed: false, is_greater_than_max: false })
2062 },
2063 );
2064 }
2065
2066 #[kani::unwind(4)]
2067 #[kani::proof]
2068 fn s_amount_homomorphic_checked() {
2069 let n1 = kani::any::<i64>();
2070 let n2 = kani::any::<i64>();
2071 assert_eq!(
2072 SignedAmount::from_sat(n1).checked_add(SignedAmount::from_sat(n2)),
2073 n1.checked_add(n2).map(SignedAmount::from_sat),
2074 );
2075 assert_eq!(
2076 SignedAmount::from_sat(n1).checked_sub(SignedAmount::from_sat(n2)),
2077 n1.checked_sub(n2).map(SignedAmount::from_sat),
2078 );
2079
2080 assert_eq!(
2081 SignedAmount::from_sat(n1).positive_sub(SignedAmount::from_sat(n2)),
2082 if n1 >= 0 && n2 >= 0 && n1 >= n2 {
2083 Some(SignedAmount::from_sat(n1 - n2))
2084 } else {
2085 None
2086 },
2087 );
2088 }
2089}
2090
2091#[cfg(test)]
2092mod tests {
2093 #[cfg(feature = "alloc")]
2094 use alloc::format;
2095 #[cfg(feature = "std")]
2096 use std::panic;
2097
2098 use super::*;
2099
2100 #[test]
2101 #[cfg(feature = "alloc")]
2102 fn from_str_zero() {
2103 let denoms = ["BTC", "mBTC", "uBTC", "bits", "sats"];
2104 for denom in denoms {
2105 for v in &["0", "000"] {
2106 let s = format!("{} {}", v, denom);
2107 match s.parse::<Amount>() {
2108 Err(e) => panic!("failed to crate amount from {}: {:?}", s, e),
2109 Ok(amount) => assert_eq!(amount, Amount::from_sat(0)),
2110 }
2111 }
2112
2113 let s = format!("-0 {}", denom);
2114 match s.parse::<Amount>() {
2115 Err(e) => assert_eq!(
2116 e,
2117 ParseError::Amount(ParseAmountError::OutOfRange(OutOfRangeError::negative()))
2118 ),
2119 Ok(_) => panic!("unsigned amount from {}", s),
2120 }
2121 match s.parse::<SignedAmount>() {
2122 Err(e) => panic!("failed to crate amount from {}: {:?}", s, e),
2123 Ok(amount) => assert_eq!(amount, SignedAmount::from_sat(0)),
2124 }
2125 }
2126 }
2127
2128 #[test]
2129 fn from_str_zero_without_denomination() {
2130 let _a = Amount::from_str("0").unwrap();
2131 let _a = Amount::from_str("0.0").unwrap();
2132 let _a = Amount::from_str("00.0").unwrap();
2133
2134 assert!(Amount::from_str("-0").is_err());
2135 assert!(Amount::from_str("-0.0").is_err());
2136 assert!(Amount::from_str("-00.0").is_err());
2137
2138 let _a = SignedAmount::from_str("-0").unwrap();
2139 let _a = SignedAmount::from_str("-0.0").unwrap();
2140 let _a = SignedAmount::from_str("-00.0").unwrap();
2141
2142 let _a = SignedAmount::from_str("0").unwrap();
2143 let _a = SignedAmount::from_str("0.0").unwrap();
2144 let _a = SignedAmount::from_str("00.0").unwrap();
2145 }
2146
2147 #[test]
2148 fn from_int_btc() {
2149 let amt = Amount::from_int_btc(2);
2150 assert_eq!(Amount::from_sat(200_000_000), amt);
2151 }
2152
2153 #[should_panic]
2154 #[test]
2155 fn from_int_btc_panic() { Amount::from_int_btc(u64::MAX); }
2156
2157 #[test]
2158 fn test_signed_amount_try_from_amount() {
2159 let ua_positive = Amount::from_sat(123);
2160 let sa_positive = SignedAmount::try_from(ua_positive).unwrap();
2161 assert_eq!(sa_positive, SignedAmount(123));
2162
2163 let ua_max = Amount::MAX;
2164 let result = SignedAmount::try_from(ua_max);
2165 assert_eq!(result, Err(OutOfRangeError { is_signed: true, is_greater_than_max: true }));
2166 }
2167
2168 #[test]
2169 fn test_amount_try_from_signed_amount() {
2170 let sa_positive = SignedAmount(123);
2171 let ua_positive = Amount::try_from(sa_positive).unwrap();
2172 assert_eq!(ua_positive, Amount::from_sat(123));
2173
2174 let sa_negative = SignedAmount(-123);
2175 let result = Amount::try_from(sa_negative);
2176 assert_eq!(result, Err(OutOfRangeError { is_signed: false, is_greater_than_max: false }));
2177 }
2178
2179 #[test]
2180 fn mul_div() {
2181 let sat = Amount::from_sat;
2182 let ssat = SignedAmount::from_sat;
2183
2184 assert_eq!(sat(14) * 3, sat(42));
2185 assert_eq!(sat(14) / 2, sat(7));
2186 assert_eq!(sat(14) % 3, sat(2));
2187 assert_eq!(ssat(-14) * 3, ssat(-42));
2188 assert_eq!(ssat(-14) / 2, ssat(-7));
2189 assert_eq!(ssat(-14) % 3, ssat(-2));
2190
2191 let mut b = ssat(30);
2192 b /= 3;
2193 assert_eq!(b, ssat(10));
2194 b %= 3;
2195 assert_eq!(b, ssat(1));
2196 }
2197
2198 #[cfg(feature = "std")]
2199 #[test]
2200 fn test_overflows() {
2201 let result = panic::catch_unwind(|| Amount::MAX + Amount::from_sat(1));
2203 assert!(result.is_err());
2204 let result = panic::catch_unwind(|| Amount::from_sat(8446744073709551615) * 3);
2205 assert!(result.is_err());
2206 }
2207
2208 #[test]
2209 fn checked_arithmetic() {
2210 let sat = Amount::from_sat;
2211 let ssat = SignedAmount::from_sat;
2212
2213 assert_eq!(SignedAmount::MAX.checked_add(ssat(1)), None);
2214 assert_eq!(SignedAmount::MIN.checked_sub(ssat(1)), None);
2215 assert_eq!(Amount::MAX.checked_add(sat(1)), None);
2216 assert_eq!(Amount::MIN.checked_sub(sat(1)), None);
2217
2218 assert_eq!(sat(5).checked_div(2), Some(sat(2))); assert_eq!(ssat(-6).checked_div(2), Some(ssat(-3)));
2220 }
2221
2222 #[test]
2223 #[cfg(not(debug_assertions))]
2224 fn unchecked_amount_add() {
2225 let amt = Amount::MAX.unchecked_add(Amount::ONE_SAT);
2226 assert_eq!(amt, Amount::ZERO);
2227 }
2228
2229 #[test]
2230 #[cfg(not(debug_assertions))]
2231 fn unchecked_signed_amount_add() {
2232 let signed_amt = SignedAmount::MAX.unchecked_add(SignedAmount::ONE_SAT);
2233 assert_eq!(signed_amt, SignedAmount::MIN);
2234 }
2235
2236 #[test]
2237 #[cfg(not(debug_assertions))]
2238 fn unchecked_amount_subtract() {
2239 let amt = Amount::ZERO.unchecked_sub(Amount::ONE_SAT);
2240 assert_eq!(amt, Amount::MAX);
2241 }
2242
2243 #[test]
2244 #[cfg(not(debug_assertions))]
2245 fn unchecked_signed_amount_subtract() {
2246 let signed_amt = SignedAmount::MIN.unchecked_sub(SignedAmount::ONE_SAT);
2247 assert_eq!(signed_amt, SignedAmount::MAX);
2248 }
2249
2250 #[cfg(feature = "alloc")]
2251 #[test]
2252 fn floating_point() {
2253 use super::Denomination as D;
2254 let f = Amount::from_float_in;
2255 let sf = SignedAmount::from_float_in;
2256 let sat = Amount::from_sat;
2257 let ssat = SignedAmount::from_sat;
2258
2259 assert_eq!(f(11.22, D::Bitcoin), Ok(sat(1122000000)));
2260 assert_eq!(sf(-11.22, D::MilliBitcoin), Ok(ssat(-1122000)));
2261 assert_eq!(f(11.22, D::Bit), Ok(sat(1122)));
2262 assert_eq!(f(0.0001234, D::Bitcoin), Ok(sat(12340)));
2263 assert_eq!(sf(-0.00012345, D::Bitcoin), Ok(ssat(-12345)));
2264
2265 assert_eq!(f(11.22, D::Satoshi), Err(TooPreciseError { position: 3 }.into()));
2266 assert_eq!(f(42.123456781, D::Bitcoin), Err(TooPreciseError { position: 11 }.into()));
2267 assert_eq!(sf(-184467440738.0, D::Bitcoin), Err(OutOfRangeError::too_small().into()));
2268 assert_eq!(
2269 f(18446744073709551617.0, D::Satoshi),
2270 Err(OutOfRangeError::too_big(false).into())
2271 );
2272
2273 assert!(f(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi).is_ok());
2275
2276 assert_eq!(
2277 f(Amount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
2278 Err(OutOfRangeError::too_big(false).into())
2279 );
2280
2281 assert_eq!(
2282 sf(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
2283 Err(OutOfRangeError::too_big(true).into())
2284 );
2285
2286 let btc = move |f| SignedAmount::from_btc(f).unwrap();
2287 assert_eq!(btc(2.5).to_float_in(D::Bitcoin), 2.5);
2288 assert_eq!(btc(-2.5).to_float_in(D::MilliBitcoin), -2500.0);
2289 assert_eq!(btc(2.5).to_float_in(D::Satoshi), 250000000.0);
2290
2291 let btc = move |f| Amount::from_btc(f).unwrap();
2292 assert_eq!(&btc(0.0012).to_float_in(D::Bitcoin).to_string(), "0.0012")
2293 }
2294
2295 #[test]
2296 #[allow(clippy::inconsistent_digit_grouping)] fn parsing() {
2298 use super::ParseAmountError as E;
2299 let btc = Denomination::Bitcoin;
2300 let sat = Denomination::Satoshi;
2301 let p = Amount::from_str_in;
2302 let sp = SignedAmount::from_str_in;
2303
2304 assert_eq!(
2305 p("x", btc),
2306 Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 0 }))
2307 );
2308 assert_eq!(
2309 p("-", btc),
2310 Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
2311 );
2312 assert_eq!(
2313 sp("-", btc),
2314 Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
2315 );
2316 assert_eq!(
2317 p("-1.0x", btc),
2318 Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 4 }))
2319 );
2320 assert_eq!(
2321 p("0.0 ", btc),
2322 Err(E::from(InvalidCharacterError { invalid_char: ' ', position: 3 }))
2323 );
2324 assert_eq!(
2325 p("0.000.000", btc),
2326 Err(E::from(InvalidCharacterError { invalid_char: '.', position: 5 }))
2327 );
2328 #[cfg(feature = "alloc")]
2329 let more_than_max = format!("1{}", Amount::MAX);
2330 #[cfg(feature = "alloc")]
2331 assert_eq!(p(&more_than_max, btc), Err(OutOfRangeError::too_big(false).into()));
2332 assert_eq!(p("0.000000042", btc), Err(TooPreciseError { position: 10 }.into()));
2333 assert_eq!(p("1.0000000", sat), Ok(Amount::from_sat(1)));
2334 assert_eq!(p("1.1", sat), Err(TooPreciseError { position: 2 }.into()));
2335 assert_eq!(p("1000.1", sat), Err(TooPreciseError { position: 5 }.into()));
2336 assert_eq!(p("1001.0000000", sat), Ok(Amount::from_sat(1001)));
2337 assert_eq!(p("1000.0000001", sat), Err(TooPreciseError { position: 11 }.into()));
2338
2339 assert_eq!(p("1", btc), Ok(Amount::from_sat(1_000_000_00)));
2340 assert_eq!(sp("-.5", btc), Ok(SignedAmount::from_sat(-500_000_00)));
2341 #[cfg(feature = "alloc")]
2342 assert_eq!(sp(&i64::MIN.to_string(), sat), Ok(SignedAmount::from_sat(i64::MIN)));
2343 assert_eq!(p("1.1", btc), Ok(Amount::from_sat(1_100_000_00)));
2344 assert_eq!(p("100", sat), Ok(Amount::from_sat(100)));
2345 assert_eq!(p("55", sat), Ok(Amount::from_sat(55)));
2346 assert_eq!(p("5500000000000000000", sat), Ok(Amount::from_sat(55_000_000_000_000_000_00)));
2347 assert_eq!(p("5500000000000000000.", sat), Ok(Amount::from_sat(55_000_000_000_000_000_00)));
2349 assert_eq!(
2350 p("12345678901.12345678", btc),
2351 Ok(Amount::from_sat(12_345_678_901__123_456_78))
2352 );
2353
2354 #[cfg(feature = "alloc")]
2356 {
2357 let amount = Amount::from_sat(i64::MAX as u64);
2358 assert_eq!(Amount::from_str_in(&amount.to_string_in(sat), sat), Ok(amount));
2359 assert!(
2360 SignedAmount::from_str_in(&(amount + Amount(1)).to_string_in(sat), sat).is_err()
2361 );
2362 assert!(Amount::from_str_in(&(amount + Amount(1)).to_string_in(sat), sat).is_ok());
2363 }
2364
2365 assert_eq!(
2367 p("100000000000000.0000000000000000000000000000000000", Denomination::Bitcoin),
2368 Err(OutOfRangeError::too_big(false).into())
2369 );
2370 assert_eq!(
2372 p("100000000000000.00000000000000000000000000000000000", Denomination::Bitcoin),
2373 Err(E::InputTooLarge(InputTooLargeError { len: 51 }))
2374 );
2375 }
2376
2377 #[test]
2378 #[cfg(feature = "alloc")]
2379 fn to_string() {
2380 use super::Denomination as D;
2381
2382 assert_eq!(Amount::ONE_BTC.to_string_in(D::Bitcoin), "1");
2383 assert_eq!(format!("{:.8}", Amount::ONE_BTC.display_in(D::Bitcoin)), "1.00000000");
2384 assert_eq!(Amount::ONE_BTC.to_string_in(D::Satoshi), "100000000");
2385 assert_eq!(Amount::ONE_SAT.to_string_in(D::Bitcoin), "0.00000001");
2386 assert_eq!(SignedAmount::from_sat(-42).to_string_in(D::Bitcoin), "-0.00000042");
2387
2388 assert_eq!(Amount::ONE_BTC.to_string_with_denomination(D::Bitcoin), "1 BTC");
2389 assert_eq!(
2390 SignedAmount::ONE_BTC.to_string_with_denomination(D::Satoshi),
2391 "100000000 satoshi"
2392 );
2393 assert_eq!(Amount::ONE_SAT.to_string_with_denomination(D::Bitcoin), "0.00000001 BTC");
2394 assert_eq!(
2395 SignedAmount::from_sat(-42).to_string_with_denomination(D::Bitcoin),
2396 "-0.00000042 BTC"
2397 );
2398 }
2399
2400 #[cfg(feature = "alloc")]
2402 #[test]
2403 fn test_repeat_char() {
2404 let mut buf = String::new();
2405 repeat_char(&mut buf, '0', 0).unwrap();
2406 assert_eq!(buf.len(), 0);
2407 repeat_char(&mut buf, '0', 42).unwrap();
2408 assert_eq!(buf.len(), 42);
2409 assert!(buf.chars().all(|c| c == '0'));
2410 }
2411
2412 macro_rules! check_format_non_negative {
2414 ($denom:ident; $($test_name:ident, $val:literal, $format_string:literal, $expected:literal);* $(;)?) => {
2415 $(
2416 #[test]
2417 #[cfg(feature = "alloc")]
2418 fn $test_name() {
2419 assert_eq!(format!($format_string, Amount::from_sat($val).display_in(Denomination::$denom)), $expected);
2420 assert_eq!(format!($format_string, SignedAmount::from_sat($val as i64).display_in(Denomination::$denom)), $expected);
2421 }
2422 )*
2423 }
2424 }
2425
2426 macro_rules! check_format_non_negative_show_denom {
2427 ($denom:ident, $denom_suffix:literal; $($test_name:ident, $val:literal, $format_string:literal, $expected:literal);* $(;)?) => {
2428 $(
2429 #[test]
2430 #[cfg(feature = "alloc")]
2431 fn $test_name() {
2432 assert_eq!(format!($format_string, Amount::from_sat($val).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
2433 assert_eq!(format!($format_string, SignedAmount::from_sat($val as i64).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
2434 }
2435 )*
2436 }
2437 }
2438
2439 check_format_non_negative! {
2440 Satoshi;
2441 sat_check_fmt_non_negative_0, 0, "{}", "0";
2442 sat_check_fmt_non_negative_1, 0, "{:2}", " 0";
2443 sat_check_fmt_non_negative_2, 0, "{:02}", "00";
2444 sat_check_fmt_non_negative_3, 0, "{:.1}", "0.0";
2445 sat_check_fmt_non_negative_4, 0, "{:4.1}", " 0.0";
2446 sat_check_fmt_non_negative_5, 0, "{:04.1}", "00.0";
2447 sat_check_fmt_non_negative_6, 1, "{}", "1";
2448 sat_check_fmt_non_negative_7, 1, "{:2}", " 1";
2449 sat_check_fmt_non_negative_8, 1, "{:02}", "01";
2450 sat_check_fmt_non_negative_9, 1, "{:.1}", "1.0";
2451 sat_check_fmt_non_negative_10, 1, "{:4.1}", " 1.0";
2452 sat_check_fmt_non_negative_11, 1, "{:04.1}", "01.0";
2453 sat_check_fmt_non_negative_12, 10, "{}", "10";
2454 sat_check_fmt_non_negative_13, 10, "{:2}", "10";
2455 sat_check_fmt_non_negative_14, 10, "{:02}", "10";
2456 sat_check_fmt_non_negative_15, 10, "{:3}", " 10";
2457 sat_check_fmt_non_negative_16, 10, "{:03}", "010";
2458 sat_check_fmt_non_negative_17, 10, "{:.1}", "10.0";
2459 sat_check_fmt_non_negative_18, 10, "{:5.1}", " 10.0";
2460 sat_check_fmt_non_negative_19, 10, "{:05.1}", "010.0";
2461 sat_check_fmt_non_negative_20, 1, "{:<2}", "1 ";
2462 sat_check_fmt_non_negative_21, 1, "{:<02}", "01";
2463 sat_check_fmt_non_negative_22, 1, "{:<3.1}", "1.0";
2464 sat_check_fmt_non_negative_23, 1, "{:<4.1}", "1.0 ";
2465 }
2466
2467 check_format_non_negative_show_denom! {
2468 Satoshi, " satoshi";
2469 sat_check_fmt_non_negative_show_denom_0, 0, "{}", "0";
2470 sat_check_fmt_non_negative_show_denom_1, 0, "{:2}", "0";
2471 sat_check_fmt_non_negative_show_denom_2, 0, "{:02}", "0";
2472 sat_check_fmt_non_negative_show_denom_3, 0, "{:9}", "0";
2473 sat_check_fmt_non_negative_show_denom_4, 0, "{:09}", "0";
2474 sat_check_fmt_non_negative_show_denom_5, 0, "{:10}", " 0";
2475 sat_check_fmt_non_negative_show_denom_6, 0, "{:010}", "00";
2476 sat_check_fmt_non_negative_show_denom_7, 0, "{:.1}", "0.0";
2477 sat_check_fmt_non_negative_show_denom_8, 0, "{:11.1}", "0.0";
2478 sat_check_fmt_non_negative_show_denom_9, 0, "{:011.1}", "0.0";
2479 sat_check_fmt_non_negative_show_denom_10, 0, "{:12.1}", " 0.0";
2480 sat_check_fmt_non_negative_show_denom_11, 0, "{:012.1}", "00.0";
2481 sat_check_fmt_non_negative_show_denom_12, 1, "{}", "1";
2482 sat_check_fmt_non_negative_show_denom_13, 1, "{:10}", " 1";
2483 sat_check_fmt_non_negative_show_denom_14, 1, "{:010}", "01";
2484 sat_check_fmt_non_negative_show_denom_15, 1, "{:.1}", "1.0";
2485 sat_check_fmt_non_negative_show_denom_16, 1, "{:12.1}", " 1.0";
2486 sat_check_fmt_non_negative_show_denom_17, 1, "{:012.1}", "01.0";
2487 sat_check_fmt_non_negative_show_denom_18, 10, "{}", "10";
2488 sat_check_fmt_non_negative_show_denom_19, 10, "{:10}", "10";
2489 sat_check_fmt_non_negative_show_denom_20, 10, "{:010}", "10";
2490 sat_check_fmt_non_negative_show_denom_21, 10, "{:11}", " 10";
2491 sat_check_fmt_non_negative_show_denom_22, 10, "{:011}", "010";
2492 }
2493
2494 check_format_non_negative! {
2495 Bitcoin;
2496 btc_check_fmt_non_negative_0, 0, "{}", "0";
2497 btc_check_fmt_non_negative_1, 0, "{:2}", " 0";
2498 btc_check_fmt_non_negative_2, 0, "{:02}", "00";
2499 btc_check_fmt_non_negative_3, 0, "{:.1}", "0.0";
2500 btc_check_fmt_non_negative_4, 0, "{:4.1}", " 0.0";
2501 btc_check_fmt_non_negative_5, 0, "{:04.1}", "00.0";
2502 btc_check_fmt_non_negative_6, 1, "{}", "0.00000001";
2503 btc_check_fmt_non_negative_7, 1, "{:2}", "0.00000001";
2504 btc_check_fmt_non_negative_8, 1, "{:02}", "0.00000001";
2505 btc_check_fmt_non_negative_9, 1, "{:.1}", "0.0";
2506 btc_check_fmt_non_negative_10, 1, "{:11}", " 0.00000001";
2507 btc_check_fmt_non_negative_11, 1, "{:11.1}", " 0.0";
2508 btc_check_fmt_non_negative_12, 1, "{:011.1}", "000000000.0";
2509 btc_check_fmt_non_negative_13, 1, "{:.9}", "0.000000010";
2510 btc_check_fmt_non_negative_14, 1, "{:11.9}", "0.000000010";
2511 btc_check_fmt_non_negative_15, 1, "{:011.9}", "0.000000010";
2512 btc_check_fmt_non_negative_16, 1, "{:12.9}", " 0.000000010";
2513 btc_check_fmt_non_negative_17, 1, "{:012.9}", "00.000000010";
2514 btc_check_fmt_non_negative_18, 100_000_000, "{}", "1";
2515 btc_check_fmt_non_negative_19, 100_000_000, "{:2}", " 1";
2516 btc_check_fmt_non_negative_20, 100_000_000, "{:02}", "01";
2517 btc_check_fmt_non_negative_21, 100_000_000, "{:.1}", "1.0";
2518 btc_check_fmt_non_negative_22, 100_000_000, "{:4.1}", " 1.0";
2519 btc_check_fmt_non_negative_23, 100_000_000, "{:04.1}", "01.0";
2520 btc_check_fmt_non_negative_24, 110_000_000, "{}", "1.1";
2521 btc_check_fmt_non_negative_25, 100_000_001, "{}", "1.00000001";
2522 btc_check_fmt_non_negative_26, 100_000_001, "{:1}", "1.00000001";
2523 btc_check_fmt_non_negative_27, 100_000_001, "{:.1}", "1.0";
2524 btc_check_fmt_non_negative_28, 100_000_001, "{:10}", "1.00000001";
2525 btc_check_fmt_non_negative_29, 100_000_001, "{:11}", " 1.00000001";
2526 btc_check_fmt_non_negative_30, 100_000_001, "{:011}", "01.00000001";
2527 btc_check_fmt_non_negative_31, 100_000_001, "{:.8}", "1.00000001";
2528 btc_check_fmt_non_negative_32, 100_000_001, "{:.9}", "1.000000010";
2529 btc_check_fmt_non_negative_33, 100_000_001, "{:11.9}", "1.000000010";
2530 btc_check_fmt_non_negative_34, 100_000_001, "{:12.9}", " 1.000000010";
2531 btc_check_fmt_non_negative_35, 100_000_001, "{:012.9}", "01.000000010";
2532 btc_check_fmt_non_negative_36, 100_000_001, "{:+011.8}", "+1.00000001";
2533 btc_check_fmt_non_negative_37, 100_000_001, "{:+12.8}", " +1.00000001";
2534 btc_check_fmt_non_negative_38, 100_000_001, "{:+012.8}", "+01.00000001";
2535 btc_check_fmt_non_negative_39, 100_000_001, "{:+12.9}", "+1.000000010";
2536 btc_check_fmt_non_negative_40, 100_000_001, "{:+012.9}", "+1.000000010";
2537 btc_check_fmt_non_negative_41, 100_000_001, "{:+13.9}", " +1.000000010";
2538 btc_check_fmt_non_negative_42, 100_000_001, "{:+013.9}", "+01.000000010";
2539 btc_check_fmt_non_negative_43, 100_000_001, "{:<10}", "1.00000001";
2540 btc_check_fmt_non_negative_44, 100_000_001, "{:<11}", "1.00000001 ";
2541 btc_check_fmt_non_negative_45, 100_000_001, "{:<011}", "01.00000001";
2542 btc_check_fmt_non_negative_46, 100_000_001, "{:<11.9}", "1.000000010";
2543 btc_check_fmt_non_negative_47, 100_000_001, "{:<12.9}", "1.000000010 ";
2544 btc_check_fmt_non_negative_48, 100_000_001, "{:<12}", "1.00000001 ";
2545 btc_check_fmt_non_negative_49, 100_000_001, "{:^11}", "1.00000001 ";
2546 btc_check_fmt_non_negative_50, 100_000_001, "{:^11.9}", "1.000000010";
2547 btc_check_fmt_non_negative_51, 100_000_001, "{:^12.9}", "1.000000010 ";
2548 btc_check_fmt_non_negative_52, 100_000_001, "{:^12}", " 1.00000001 ";
2549 btc_check_fmt_non_negative_53, 100_000_001, "{:^12.9}", "1.000000010 ";
2550 btc_check_fmt_non_negative_54, 100_000_001, "{:^13.9}", " 1.000000010 ";
2551 }
2552
2553 check_format_non_negative_show_denom! {
2554 Bitcoin, " BTC";
2555 btc_check_fmt_non_negative_show_denom_0, 1, "{:14.1}", " 0.0";
2556 btc_check_fmt_non_negative_show_denom_1, 1, "{:14.8}", "0.00000001";
2557 btc_check_fmt_non_negative_show_denom_2, 1, "{:15}", " 0.00000001";
2558 btc_check_fmt_non_negative_show_denom_3, 1, "{:015}", "00.00000001";
2559 btc_check_fmt_non_negative_show_denom_4, 1, "{:.9}", "0.000000010";
2560 btc_check_fmt_non_negative_show_denom_5, 1, "{:15.9}", "0.000000010";
2561 btc_check_fmt_non_negative_show_denom_6, 1, "{:16.9}", " 0.000000010";
2562 btc_check_fmt_non_negative_show_denom_7, 1, "{:016.9}", "00.000000010";
2563 }
2564
2565 check_format_non_negative_show_denom! {
2566 Bitcoin, " BTC ";
2567 btc_check_fmt_non_negative_show_denom_align_0, 1, "{:<15}", "0.00000001";
2568 btc_check_fmt_non_negative_show_denom_align_1, 1, "{:^15}", "0.00000001";
2569 btc_check_fmt_non_negative_show_denom_align_2, 1, "{:^16}", " 0.00000001";
2570 }
2571
2572 #[test]
2573 fn test_unsigned_signed_conversion() {
2574 let sa = SignedAmount::from_sat;
2575 let ua = Amount::from_sat;
2576
2577 assert_eq!(Amount::MAX.to_signed(), Err(OutOfRangeError::too_big(true)));
2578 assert_eq!(ua(i64::MAX as u64).to_signed(), Ok(sa(i64::MAX)));
2579 assert_eq!(ua(i64::MAX as u64 + 1).to_signed(), Err(OutOfRangeError::too_big(true)));
2580
2581 assert_eq!(sa(i64::MAX).to_unsigned(), Ok(ua(i64::MAX as u64)));
2582
2583 assert_eq!(sa(i64::MAX).to_unsigned().unwrap().to_signed(), Ok(sa(i64::MAX)));
2584 }
2585
2586 #[test]
2587 #[allow(clippy::inconsistent_digit_grouping)] fn from_str() {
2589 use ParseDenominationError::*;
2590
2591 use super::ParseAmountError as E;
2592
2593 assert_eq!(
2594 "x BTC".parse::<Amount>(),
2595 Err(InvalidCharacterError { invalid_char: 'x', position: 0 }.into())
2596 );
2597 assert_eq!(
2598 "xBTC".parse::<Amount>(),
2599 Err(Unknown(UnknownDenominationError("xBTC".into())).into()),
2600 );
2601 assert_eq!(
2602 "5 BTC BTC".parse::<Amount>(),
2603 Err(Unknown(UnknownDenominationError("BTC BTC".into())).into()),
2604 );
2605 assert_eq!(
2606 "5BTC BTC".parse::<Amount>(),
2607 Err(E::from(InvalidCharacterError { invalid_char: 'B', position: 1 }).into())
2608 );
2609 assert_eq!(
2610 "5 5 BTC".parse::<Amount>(),
2611 Err(Unknown(UnknownDenominationError("5 BTC".into())).into()),
2612 );
2613
2614 #[track_caller]
2615 fn ok_case(s: &str, expected: Amount) {
2616 assert_eq!(s.parse::<Amount>().unwrap(), expected);
2617 assert_eq!(s.replace(' ', "").parse::<Amount>().unwrap(), expected);
2618 }
2619
2620 #[track_caller]
2621 fn case(s: &str, expected: Result<Amount, impl Into<ParseError>>) {
2622 let expected = expected.map_err(Into::into);
2623 assert_eq!(s.parse::<Amount>(), expected);
2624 assert_eq!(s.replace(' ', "").parse::<Amount>(), expected);
2625 }
2626
2627 #[track_caller]
2628 fn ok_scase(s: &str, expected: SignedAmount) {
2629 assert_eq!(s.parse::<SignedAmount>().unwrap(), expected);
2630 assert_eq!(s.replace(' ', "").parse::<SignedAmount>().unwrap(), expected);
2631 }
2632
2633 #[track_caller]
2634 fn scase(s: &str, expected: Result<SignedAmount, impl Into<ParseError>>) {
2635 let expected = expected.map_err(Into::into);
2636 assert_eq!(s.parse::<SignedAmount>(), expected);
2637 assert_eq!(s.replace(' ', "").parse::<SignedAmount>(), expected);
2638 }
2639
2640 case("5 BCH", Err(Unknown(UnknownDenominationError("BCH".into()))));
2641
2642 case("-1 BTC", Err(OutOfRangeError::negative()));
2643 case("-0.0 BTC", Err(OutOfRangeError::negative()));
2644 case("0.123456789 BTC", Err(TooPreciseError { position: 10 }));
2645 scase("-0.1 satoshi", Err(TooPreciseError { position: 3 }));
2646 case("0.123456 mBTC", Err(TooPreciseError { position: 7 }));
2647 scase("-1.001 bits", Err(TooPreciseError { position: 5 }));
2648 scase("-200000000000 BTC", Err(OutOfRangeError::too_small()));
2649 case("18446744073709551616 sat", Err(OutOfRangeError::too_big(false)));
2650
2651 ok_case(".5 bits", Amount::from_sat(50));
2652 ok_scase("-.5 bits", SignedAmount::from_sat(-50));
2653 ok_case("0.00253583 BTC", Amount::from_sat(253583));
2654 ok_scase("-5 satoshi", SignedAmount::from_sat(-5));
2655 ok_case("0.10000000 BTC", Amount::from_sat(100_000_00));
2656 ok_scase("-100 bits", SignedAmount::from_sat(-10_000));
2657 #[cfg(feature = "alloc")]
2658 ok_scase(&format!("{} SAT", i64::MIN), SignedAmount::from_sat(i64::MIN));
2659 }
2660
2661 #[cfg(feature = "alloc")]
2662 #[test]
2663 #[allow(clippy::inconsistent_digit_grouping)] fn to_from_string_in() {
2665 use super::Denomination as D;
2666 let ua_str = Amount::from_str_in;
2667 let ua_sat = Amount::from_sat;
2668 let sa_str = SignedAmount::from_str_in;
2669 let sa_sat = SignedAmount::from_sat;
2670
2671 assert_eq!("0.5", Amount::from_sat(50).to_string_in(D::Bit));
2672 assert_eq!("-0.5", SignedAmount::from_sat(-50).to_string_in(D::Bit));
2673 assert_eq!("0.00253583", Amount::from_sat(253583).to_string_in(D::Bitcoin));
2674 assert_eq!("-5", SignedAmount::from_sat(-5).to_string_in(D::Satoshi));
2675 assert_eq!("0.1", Amount::from_sat(100_000_00).to_string_in(D::Bitcoin));
2676 assert_eq!("-100", SignedAmount::from_sat(-10_000).to_string_in(D::Bit));
2677
2678 assert_eq!("0.50", format!("{:.2}", Amount::from_sat(50).display_in(D::Bit)));
2679 assert_eq!("-0.50", format!("{:.2}", SignedAmount::from_sat(-50).display_in(D::Bit)));
2680 assert_eq!(
2681 "0.10000000",
2682 format!("{:.8}", Amount::from_sat(100_000_00).display_in(D::Bitcoin))
2683 );
2684 assert_eq!("-100.00", format!("{:.2}", SignedAmount::from_sat(-10_000).display_in(D::Bit)));
2685
2686 assert_eq!(ua_str(&ua_sat(0).to_string_in(D::Satoshi), D::Satoshi), Ok(ua_sat(0)));
2687 assert_eq!(ua_str(&ua_sat(500).to_string_in(D::Bitcoin), D::Bitcoin), Ok(ua_sat(500)));
2688 assert_eq!(
2689 ua_str(&ua_sat(21_000_000).to_string_in(D::Bit), D::Bit),
2690 Ok(ua_sat(21_000_000))
2691 );
2692 assert_eq!(
2693 ua_str(&ua_sat(1).to_string_in(D::MicroBitcoin), D::MicroBitcoin),
2694 Ok(ua_sat(1))
2695 );
2696 assert_eq!(
2697 ua_str(&ua_sat(1_000_000_000_000).to_string_in(D::MilliBitcoin), D::MilliBitcoin),
2698 Ok(ua_sat(1_000_000_000_000))
2699 );
2700 assert!(ua_str(&ua_sat(u64::MAX).to_string_in(D::MilliBitcoin), D::MilliBitcoin).is_ok());
2701
2702 assert_eq!(
2703 sa_str(&sa_sat(-1).to_string_in(D::MicroBitcoin), D::MicroBitcoin),
2704 Ok(sa_sat(-1))
2705 );
2706
2707 assert_eq!(
2708 sa_str(&sa_sat(i64::MAX).to_string_in(D::Satoshi), D::MicroBitcoin),
2709 Err(OutOfRangeError::too_big(true).into())
2710 );
2711 assert_eq!(
2713 sa_str(&sa_sat(i64::MIN).to_string_in(D::Satoshi), D::MicroBitcoin),
2714 Err(OutOfRangeError::too_small().into())
2715 );
2716 }
2717
2718 #[cfg(feature = "alloc")]
2719 #[test]
2720 fn to_string_with_denomination_from_str_roundtrip() {
2721 use ParseDenominationError::*;
2722
2723 use super::Denomination as D;
2724
2725 let amt = Amount::from_sat(42);
2726 let denom = Amount::to_string_with_denomination;
2727 assert_eq!(denom(amt, D::Bitcoin).parse::<Amount>(), Ok(amt));
2728 assert_eq!(denom(amt, D::MilliBitcoin).parse::<Amount>(), Ok(amt));
2729 assert_eq!(denom(amt, D::MicroBitcoin).parse::<Amount>(), Ok(amt));
2730 assert_eq!(denom(amt, D::Bit).parse::<Amount>(), Ok(amt));
2731 assert_eq!(denom(amt, D::Satoshi).parse::<Amount>(), Ok(amt));
2732
2733 assert_eq!(
2734 "42 satoshi BTC".parse::<Amount>(),
2735 Err(Unknown(UnknownDenominationError("satoshi BTC".into())).into()),
2736 );
2737 assert_eq!(
2738 "-42 satoshi BTC".parse::<SignedAmount>(),
2739 Err(Unknown(UnknownDenominationError("satoshi BTC".into())).into()),
2740 );
2741 }
2742
2743 #[cfg(feature = "serde")]
2744 #[test]
2745 fn serde_as_sat() {
2746 #[derive(Serialize, Deserialize, PartialEq, Debug)]
2747 struct T {
2748 #[serde(with = "crate::amount::serde::as_sat")]
2749 pub amt: Amount,
2750 #[serde(with = "crate::amount::serde::as_sat")]
2751 pub samt: SignedAmount,
2752 }
2753
2754 serde_test::assert_tokens(
2755 &T { amt: Amount::from_sat(123456789), samt: SignedAmount::from_sat(-123456789) },
2756 &[
2757 serde_test::Token::Struct { name: "T", len: 2 },
2758 serde_test::Token::Str("amt"),
2759 serde_test::Token::U64(123456789),
2760 serde_test::Token::Str("samt"),
2761 serde_test::Token::I64(-123456789),
2762 serde_test::Token::StructEnd,
2763 ],
2764 );
2765 }
2766
2767 #[cfg(feature = "serde")]
2768 #[cfg(feature = "alloc")]
2769 #[test]
2770 #[allow(clippy::inconsistent_digit_grouping)] fn serde_as_btc() {
2772 use serde_json;
2773
2774 #[derive(Serialize, Deserialize, PartialEq, Debug)]
2775 struct T {
2776 #[serde(with = "crate::amount::serde::as_btc")]
2777 pub amt: Amount,
2778 #[serde(with = "crate::amount::serde::as_btc")]
2779 pub samt: SignedAmount,
2780 }
2781
2782 let orig = T {
2783 amt: Amount::from_sat(21_000_000__000_000_01),
2784 samt: SignedAmount::from_sat(-21_000_000__000_000_01),
2785 };
2786
2787 let json = "{\"amt\": 21000000.00000001, \
2788 \"samt\": -21000000.00000001}";
2789 let t: T = serde_json::from_str(json).unwrap();
2790 assert_eq!(t, orig);
2791
2792 let value: serde_json::Value = serde_json::from_str(json).unwrap();
2793 assert_eq!(t, serde_json::from_value(value).unwrap());
2794
2795 let t: Result<T, serde_json::Error> =
2797 serde_json::from_str("{\"amt\": 1000000.000000001, \"samt\": 1}");
2798 assert!(t
2799 .unwrap_err()
2800 .to_string()
2801 .contains(&ParseAmountError::TooPrecise(TooPreciseError { position: 16 }).to_string()));
2802 let t: Result<T, serde_json::Error> = serde_json::from_str("{\"amt\": -1, \"samt\": 1}");
2803 assert!(t.unwrap_err().to_string().contains(&OutOfRangeError::negative().to_string()));
2804 }
2805
2806 #[cfg(feature = "serde")]
2807 #[cfg(feature = "alloc")]
2808 #[test]
2809 #[allow(clippy::inconsistent_digit_grouping)] fn serde_as_btc_opt() {
2811 use serde_json;
2812
2813 #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
2814 struct T {
2815 #[serde(default, with = "crate::amount::serde::as_btc::opt")]
2816 pub amt: Option<Amount>,
2817 #[serde(default, with = "crate::amount::serde::as_btc::opt")]
2818 pub samt: Option<SignedAmount>,
2819 }
2820
2821 let with = T {
2822 amt: Some(Amount::from_sat(2_500_000_00)),
2823 samt: Some(SignedAmount::from_sat(-2_500_000_00)),
2824 };
2825 let without = T { amt: None, samt: None };
2826
2827 for s in [&with, &without].iter() {
2829 let v = serde_json::to_string(s).unwrap();
2830 let w: T = serde_json::from_str(&v).unwrap();
2831 assert_eq!(w, **s);
2832 }
2833
2834 let t: T = serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap();
2835 assert_eq!(t, with);
2836
2837 let t: T = serde_json::from_str("{}").unwrap();
2838 assert_eq!(t, without);
2839
2840 let value_with: serde_json::Value =
2841 serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap();
2842 assert_eq!(with, serde_json::from_value(value_with).unwrap());
2843
2844 let value_without: serde_json::Value = serde_json::from_str("{}").unwrap();
2845 assert_eq!(without, serde_json::from_value(value_without).unwrap());
2846 }
2847
2848 #[cfg(feature = "serde")]
2849 #[cfg(feature = "alloc")]
2850 #[test]
2851 #[allow(clippy::inconsistent_digit_grouping)] fn serde_as_sat_opt() {
2853 use serde_json;
2854
2855 #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
2856 struct T {
2857 #[serde(default, with = "crate::amount::serde::as_sat::opt")]
2858 pub amt: Option<Amount>,
2859 #[serde(default, with = "crate::amount::serde::as_sat::opt")]
2860 pub samt: Option<SignedAmount>,
2861 }
2862
2863 let with = T {
2864 amt: Some(Amount::from_sat(2_500_000_00)),
2865 samt: Some(SignedAmount::from_sat(-2_500_000_00)),
2866 };
2867 let without = T { amt: None, samt: None };
2868
2869 for s in [&with, &without].iter() {
2871 let v = serde_json::to_string(s).unwrap();
2872 let w: T = serde_json::from_str(&v).unwrap();
2873 assert_eq!(w, **s);
2874 }
2875
2876 let t: T = serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap();
2877 assert_eq!(t, with);
2878
2879 let t: T = serde_json::from_str("{}").unwrap();
2880 assert_eq!(t, without);
2881
2882 let value_with: serde_json::Value =
2883 serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap();
2884 assert_eq!(with, serde_json::from_value(value_with).unwrap());
2885
2886 let value_without: serde_json::Value = serde_json::from_str("{}").unwrap();
2887 assert_eq!(without, serde_json::from_value(value_without).unwrap());
2888 }
2889
2890 #[test]
2891 fn sum_amounts() {
2892 assert_eq!(Amount::from_sat(0), [].into_iter().sum::<Amount>());
2893 assert_eq!(SignedAmount::from_sat(0), [].into_iter().sum::<SignedAmount>());
2894
2895 let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
2896 let sum = amounts.into_iter().sum::<Amount>();
2897 assert_eq!(Amount::from_sat(1400), sum);
2898
2899 let amounts =
2900 [SignedAmount::from_sat(-42), SignedAmount::from_sat(1337), SignedAmount::from_sat(21)];
2901 let sum = amounts.into_iter().sum::<SignedAmount>();
2902 assert_eq!(SignedAmount::from_sat(1316), sum);
2903 }
2904
2905 #[test]
2906 fn checked_sum_amounts() {
2907 assert_eq!(Some(Amount::from_sat(0)), [].into_iter().checked_sum());
2908 assert_eq!(Some(SignedAmount::from_sat(0)), [].into_iter().checked_sum());
2909
2910 let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
2911 let sum = amounts.into_iter().checked_sum();
2912 assert_eq!(Some(Amount::from_sat(1400)), sum);
2913
2914 let amounts = [Amount::from_sat(u64::MAX), Amount::from_sat(1337), Amount::from_sat(21)];
2915 let sum = amounts.into_iter().checked_sum();
2916 assert_eq!(None, sum);
2917
2918 let amounts = [
2919 SignedAmount::from_sat(i64::MIN),
2920 SignedAmount::from_sat(-1),
2921 SignedAmount::from_sat(21),
2922 ];
2923 let sum = amounts.into_iter().checked_sum();
2924 assert_eq!(None, sum);
2925
2926 let amounts = [
2927 SignedAmount::from_sat(i64::MAX),
2928 SignedAmount::from_sat(1),
2929 SignedAmount::from_sat(21),
2930 ];
2931 let sum = amounts.into_iter().checked_sum();
2932 assert_eq!(None, sum);
2933
2934 let amounts =
2935 [SignedAmount::from_sat(42), SignedAmount::from_sat(3301), SignedAmount::from_sat(21)];
2936 let sum = amounts.into_iter().checked_sum();
2937 assert_eq!(Some(SignedAmount::from_sat(3364)), sum);
2938 }
2939
2940 #[test]
2941 fn denomination_string_acceptable_forms() {
2942 let valid = [
2944 "BTC", "btc", "mBTC", "mbtc", "uBTC", "ubtc", "SATOSHI", "satoshi", "SATOSHIS",
2945 "satoshis", "SAT", "sat", "SATS", "sats", "bit", "bits",
2946 ];
2947 for denom in valid.iter() {
2948 assert!(denom.parse::<Denomination>().is_ok());
2949 }
2950 }
2951
2952 #[test]
2953 fn disallow_confusing_forms() {
2954 let confusing = ["CBTC", "Cbtc", "MBTC", "Mbtc", "UBTC", "Ubtc"];
2955 for denom in confusing.iter() {
2956 match denom.parse::<Denomination>() {
2957 Ok(_) => panic!("from_str should error for {}", denom),
2958 Err(ParseDenominationError::PossiblyConfusing(_)) => {}
2959 Err(e) => panic!("unexpected error: {}", e),
2960 }
2961 }
2962 }
2963
2964 #[test]
2965 fn disallow_unknown_denomination() {
2966 let unknown = ["NBTC", "ABC", "abc", "mSat", "msat"];
2968 for denom in unknown.iter() {
2969 match denom.parse::<Denomination>() {
2970 Ok(_) => panic!("from_str should error for {}", denom),
2971 Err(ParseDenominationError::Unknown(_)) => (),
2972 Err(e) => panic!("unexpected error: {}", e),
2973 }
2974 }
2975 }
2976
2977 #[test]
2978 #[cfg(feature = "alloc")]
2979 fn trailing_zeros_for_amount() {
2980 assert_eq!(format!("{}", Amount::from_sat(1000000)), "0.01 BTC");
2981 assert_eq!(format!("{}", Amount::ONE_SAT), "0.00000001 BTC");
2982 assert_eq!(format!("{}", Amount::ONE_BTC), "1 BTC");
2983 assert_eq!(format!("{}", Amount::from_sat(1)), "0.00000001 BTC");
2984 assert_eq!(format!("{}", Amount::from_sat(10)), "0.0000001 BTC");
2985 assert_eq!(format!("{:.2}", Amount::from_sat(10)), "0.00 BTC");
2986 assert_eq!(format!("{:.2}", Amount::from_sat(100)), "0.00 BTC");
2987 assert_eq!(format!("{:.2}", Amount::from_sat(1000)), "0.00 BTC");
2988 assert_eq!(format!("{:.2}", Amount::from_sat(10_000)), "0.00 BTC");
2989 assert_eq!(format!("{:.2}", Amount::from_sat(100_000)), "0.00 BTC");
2990 assert_eq!(format!("{:.2}", Amount::from_sat(1_000_000)), "0.01 BTC");
2991 assert_eq!(format!("{:.2}", Amount::from_sat(10_000_000)), "0.10 BTC");
2992 assert_eq!(format!("{:.2}", Amount::from_sat(100_000_000)), "1.00 BTC");
2993 assert_eq!(format!("{:.2}", Amount::from_sat(500_000)), "0.01 BTC");
2994 assert_eq!(format!("{:.2}", Amount::from_sat(9_500_000)), "0.10 BTC");
2995 assert_eq!(format!("{:.2}", Amount::from_sat(99_500_000)), "1.00 BTC");
2996 assert_eq!(format!("{}", Amount::from_sat(100_000_000)), "1 BTC");
2997 assert_eq!(format!("{}", Amount::from_sat(40_000_000_000)), "400 BTC");
2998 assert_eq!(format!("{:.10}", Amount::from_sat(100_000_000)), "1.0000000000 BTC");
2999 assert_eq!(format!("{}", Amount::from_sat(400_000_000_000_010)), "4000000.0000001 BTC");
3000 assert_eq!(format!("{}", Amount::from_sat(400_000_000_000_000)), "4000000 BTC");
3001 }
3002}