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