1#[cfg(feature = "alloc")]
9use alloc::string::{String, ToString};
10use core::cmp::Ordering;
11use core::convert::Infallible;
12#[cfg(feature = "alloc")]
13use core::fmt::Write as _;
14use core::str::FromStr;
15use core::{default, fmt, ops};
16
17#[cfg(feature = "serde")]
18use ::serde::{Deserialize, Serialize};
19#[cfg(feature = "arbitrary")]
20use arbitrary::{Arbitrary, Unstructured};
21
22use crate::input_string::InputString;
23use crate::internal_macros::write_err;
24#[cfg(feature = "alloc")]
25use crate::{FeeRate, Weight};
26
27#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
45#[non_exhaustive]
46pub enum Denomination {
47 Bitcoin,
49 CentiBitcoin,
51 MilliBitcoin,
53 MicroBitcoin,
55 NanoBitcoin,
57 PicoBitcoin,
59 Bit,
61 Satoshi,
63 MilliSatoshi,
65}
66
67impl Denomination {
68 pub const BTC: Self = Denomination::Bitcoin;
70
71 pub const SAT: Self = Denomination::Satoshi;
73
74 fn precision(self) -> i8 {
76 match self {
77 Denomination::Bitcoin => -8,
78 Denomination::CentiBitcoin => -6,
79 Denomination::MilliBitcoin => -5,
80 Denomination::MicroBitcoin => -2,
81 Denomination::NanoBitcoin => 1,
82 Denomination::PicoBitcoin => 4,
83 Denomination::Bit => -2,
84 Denomination::Satoshi => 0,
85 Denomination::MilliSatoshi => 3,
86 }
87 }
88
89 fn as_str(self) -> &'static str {
91 match self {
92 Denomination::Bitcoin => "BTC",
93 Denomination::CentiBitcoin => "cBTC",
94 Denomination::MilliBitcoin => "mBTC",
95 Denomination::MicroBitcoin => "uBTC",
96 Denomination::NanoBitcoin => "nBTC",
97 Denomination::PicoBitcoin => "pBTC",
98 Denomination::Bit => "bits",
99 Denomination::Satoshi => "satoshi",
100 Denomination::MilliSatoshi => "msat",
101 }
102 }
103
104 fn forms(s: &str) -> Option<Self> {
106 match s {
107 "BTC" | "btc" => Some(Denomination::Bitcoin),
108 "cBTC" | "cbtc" => Some(Denomination::CentiBitcoin),
109 "mBTC" | "mbtc" => Some(Denomination::MilliBitcoin),
110 "uBTC" | "ubtc" => Some(Denomination::MicroBitcoin),
111 "nBTC" | "nbtc" => Some(Denomination::NanoBitcoin),
112 "pBTC" | "pbtc" => Some(Denomination::PicoBitcoin),
113 "bit" | "bits" | "BIT" | "BITS" => Some(Denomination::Bit),
114 "SATOSHI" | "satoshi" | "SATOSHIS" | "satoshis" | "SAT" | "sat" | "SATS" | "sats" =>
115 Some(Denomination::Satoshi),
116 "mSAT" | "msat" | "mSATs" | "msats" => Some(Denomination::MilliSatoshi),
117 _ => None,
118 }
119 }
120}
121
122const CONFUSING_FORMS: [&str; 9] =
125 ["Msat", "Msats", "MSAT", "MSATS", "MSat", "MSats", "MBTC", "Mbtc", "PBTC"];
126
127impl fmt::Display for Denomination {
128 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_str()) }
129}
130
131impl FromStr for Denomination {
132 type Err = ParseDenominationError;
133
134 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 use self::ParseDenominationError::*;
143
144 if CONFUSING_FORMS.contains(&s) {
145 return Err(PossiblyConfusing(PossiblyConfusingDenominationError(s.into())));
146 };
147
148 let form = self::Denomination::forms(s);
149
150 form.ok_or_else(|| Unknown(UnknownDenominationError(s.into())))
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum ParseError {
158 Amount(ParseAmountError),
160
161 Denomination(ParseDenominationError),
163
164 MissingDenomination(MissingDenominationError),
166}
167
168impl From<Infallible> for ParseError {
169 fn from(never: Infallible) -> Self { match never {} }
170}
171
172impl From<ParseAmountError> for ParseError {
173 fn from(e: ParseAmountError) -> Self { Self::Amount(e) }
174}
175
176impl From<ParseDenominationError> for ParseError {
177 fn from(e: ParseDenominationError) -> Self { Self::Denomination(e) }
178}
179
180impl From<OutOfRangeError> for ParseError {
181 fn from(e: OutOfRangeError) -> Self { Self::Amount(e.into()) }
182}
183
184impl From<TooPreciseError> for ParseError {
185 fn from(e: TooPreciseError) -> Self { Self::Amount(e.into()) }
186}
187
188impl From<MissingDigitsError> for ParseError {
189 fn from(e: MissingDigitsError) -> Self { Self::Amount(e.into()) }
190}
191
192impl From<InputTooLargeError> for ParseError {
193 fn from(e: InputTooLargeError) -> Self { Self::Amount(e.into()) }
194}
195
196impl From<InvalidCharacterError> for ParseError {
197 fn from(e: InvalidCharacterError) -> Self { Self::Amount(e.into()) }
198}
199
200impl fmt::Display for ParseError {
201 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202 match self {
203 ParseError::Amount(error) => write_err!(f, "invalid amount"; error),
204 ParseError::Denomination(error) => write_err!(f, "invalid denomination"; error),
205 ParseError::MissingDenomination(_) =>
208 f.write_str("the input doesn't contain a denomination"),
209 }
210 }
211}
212
213#[cfg(feature = "std")]
214impl std::error::Error for ParseError {
215 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
216 match self {
217 ParseError::Amount(error) => Some(error),
218 ParseError::Denomination(error) => Some(error),
219 ParseError::MissingDenomination(_) => None,
222 }
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq)]
228#[non_exhaustive]
229pub enum ParseAmountError {
230 OutOfRange(OutOfRangeError),
232 TooPrecise(TooPreciseError),
234 MissingDigits(MissingDigitsError),
236 InputTooLarge(InputTooLargeError),
238 InvalidCharacter(InvalidCharacterError),
240}
241
242impl From<TooPreciseError> for ParseAmountError {
243 fn from(value: TooPreciseError) -> Self { Self::TooPrecise(value) }
244}
245
246impl From<MissingDigitsError> for ParseAmountError {
247 fn from(value: MissingDigitsError) -> Self { Self::MissingDigits(value) }
248}
249
250impl From<InputTooLargeError> for ParseAmountError {
251 fn from(value: InputTooLargeError) -> Self { Self::InputTooLarge(value) }
252}
253
254impl From<InvalidCharacterError> for ParseAmountError {
255 fn from(value: InvalidCharacterError) -> Self { Self::InvalidCharacter(value) }
256}
257
258impl From<Infallible> for ParseAmountError {
259 fn from(never: Infallible) -> Self { match never {} }
260}
261
262impl fmt::Display for ParseAmountError {
263 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264 use ParseAmountError::*;
265
266 match *self {
267 OutOfRange(ref error) => write_err!(f, "amount out of range"; error),
268 TooPrecise(ref error) => write_err!(f, "amount has a too high precision"; error),
269 MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error),
270 InputTooLarge(ref error) => write_err!(f, "the input is too large"; error),
271 InvalidCharacter(ref error) => write_err!(f, "invalid character in the input"; error),
272 }
273 }
274}
275
276#[cfg(feature = "std")]
277impl std::error::Error for ParseAmountError {
278 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
279 use ParseAmountError::*;
280
281 match *self {
282 TooPrecise(ref error) => Some(error),
283 InputTooLarge(ref error) => Some(error),
284 OutOfRange(ref error) => Some(error),
285 MissingDigits(ref error) => Some(error),
286 InvalidCharacter(ref error) => Some(error),
287 }
288 }
289}
290
291#[derive(Debug, Copy, Clone, Eq, PartialEq)]
293pub struct OutOfRangeError {
294 is_signed: bool,
295 is_greater_than_max: bool,
296}
297
298impl OutOfRangeError {
299 pub fn valid_range(&self) -> (i64, u64) {
303 match self.is_signed {
304 true => (i64::MIN, i64::MAX as u64),
305 false => (0, u64::MAX),
306 }
307 }
308
309 pub fn is_above_max(&self) -> bool { self.is_greater_than_max }
311
312 pub fn is_below_min(&self) -> bool { !self.is_greater_than_max }
314
315 pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
316
317 pub(crate) fn too_small() -> Self {
318 Self {
319 is_signed: true,
321 is_greater_than_max: false,
322 }
323 }
324
325 pub(crate) fn negative() -> Self {
326 Self {
327 is_signed: false,
329 is_greater_than_max: false,
330 }
331 }
332}
333
334impl fmt::Display for OutOfRangeError {
335 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
336 if self.is_greater_than_max {
337 write!(f, "the amount is greater than {}", self.valid_range().1)
338 } else {
339 write!(f, "the amount is less than {}", self.valid_range().0)
340 }
341 }
342}
343
344#[cfg(feature = "std")]
345impl std::error::Error for OutOfRangeError {}
346
347impl From<OutOfRangeError> for ParseAmountError {
348 fn from(value: OutOfRangeError) -> Self { ParseAmountError::OutOfRange(value) }
349}
350
351#[derive(Debug, Clone, Eq, PartialEq)]
353pub struct TooPreciseError {
354 position: usize,
355}
356
357impl fmt::Display for TooPreciseError {
358 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359 match self.position {
360 0 => f.write_str("the amount is less than 1 satoshi but it's not zero"),
361 pos => write!(
362 f,
363 "the digits starting from position {} represent a sub-satoshi amount",
364 pos
365 ),
366 }
367 }
368}
369
370#[cfg(feature = "std")]
371impl std::error::Error for TooPreciseError {}
372
373#[derive(Debug, Clone, Eq, PartialEq)]
375pub struct InputTooLargeError {
376 len: usize,
377}
378
379impl fmt::Display for InputTooLargeError {
380 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381 match self.len - INPUT_STRING_LEN_LIMIT {
382 1 => write!(
383 f,
384 "the input is one character longer than the maximum allowed length ({})",
385 INPUT_STRING_LEN_LIMIT
386 ),
387 n => write!(
388 f,
389 "the input is {} characters longer than the maximum allowed length ({})",
390 n, INPUT_STRING_LEN_LIMIT
391 ),
392 }
393 }
394}
395
396#[cfg(feature = "std")]
397impl std::error::Error for InputTooLargeError {}
398
399#[derive(Debug, Clone, Eq, PartialEq)]
403pub struct MissingDigitsError {
404 kind: MissingDigitsKind,
405}
406
407impl fmt::Display for MissingDigitsError {
408 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
409 match self.kind {
410 MissingDigitsKind::Empty => f.write_str("the input is empty"),
411 MissingDigitsKind::OnlyMinusSign =>
412 f.write_str("there are no digits following the minus (-) sign"),
413 }
414 }
415}
416
417#[cfg(feature = "std")]
418impl std::error::Error for MissingDigitsError {}
419
420#[derive(Debug, Clone, Eq, PartialEq)]
421enum MissingDigitsKind {
422 Empty,
423 OnlyMinusSign,
424}
425
426#[derive(Debug, Clone, PartialEq, Eq)]
428pub struct InvalidCharacterError {
429 invalid_char: char,
430 position: usize,
431}
432
433impl fmt::Display for InvalidCharacterError {
434 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
435 match self.invalid_char {
436 '.' => f.write_str("there is more than one decimal separator (dot) in the input"),
437 '-' => f.write_str("there is more than one minus sign (-) in the input"),
438 c => write!(
439 f,
440 "the character '{}' at position {} is not a valid digit",
441 c, self.position
442 ),
443 }
444 }
445}
446
447#[cfg(feature = "std")]
448impl std::error::Error for InvalidCharacterError {}
449
450#[derive(Debug, Clone, PartialEq, Eq)]
452#[non_exhaustive]
453pub enum ParseDenominationError {
454 Unknown(UnknownDenominationError),
456 PossiblyConfusing(PossiblyConfusingDenominationError),
458}
459
460impl From<Infallible> for ParseDenominationError {
461 fn from(never: Infallible) -> Self { match never {} }
462}
463
464impl fmt::Display for ParseDenominationError {
465 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466 use ParseDenominationError::*;
467
468 match *self {
469 Unknown(ref e) => write_err!(f, "denomination parse error"; e),
470 PossiblyConfusing(ref e) => write_err!(f, "denomination parse error"; e),
471 }
472 }
473}
474
475#[cfg(feature = "std")]
476impl std::error::Error for ParseDenominationError {
477 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
478 use ParseDenominationError::*;
479
480 match *self {
481 Unknown(_) | PossiblyConfusing(_) => None,
482 }
483 }
484}
485
486#[derive(Debug, Clone, PartialEq, Eq)]
488#[non_exhaustive]
489pub struct MissingDenominationError;
490
491#[derive(Debug, Clone, PartialEq, Eq)]
493#[non_exhaustive]
494pub struct UnknownDenominationError(InputString);
495
496impl fmt::Display for UnknownDenominationError {
497 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
498 self.0.unknown_variant("bitcoin denomination", f)
499 }
500}
501
502#[cfg(feature = "std")]
503impl std::error::Error for UnknownDenominationError {
504 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
505}
506
507#[derive(Debug, Clone, PartialEq, Eq)]
509#[non_exhaustive]
510pub struct PossiblyConfusingDenominationError(InputString);
511
512impl fmt::Display for PossiblyConfusingDenominationError {
513 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
514 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"))
515 }
516}
517
518#[cfg(feature = "std")]
519impl std::error::Error for PossiblyConfusingDenominationError {
520 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
521}
522
523fn is_too_precise(s: &str, precision: usize) -> Option<usize> {
527 match s.find('.') {
528 Some(pos) if precision >= pos => Some(0),
529 Some(pos) => s[..pos]
530 .char_indices()
531 .rev()
532 .take(precision)
533 .find(|(_, d)| *d != '0')
534 .map(|(i, _)| i)
535 .or_else(|| {
536 s[(pos + 1)..].char_indices().find(|(_, d)| *d != '0').map(|(i, _)| i + pos + 1)
537 }),
538 None if precision >= s.len() => Some(0),
539 None => s.char_indices().rev().take(precision).find(|(_, d)| *d != '0').map(|(i, _)| i),
540 }
541}
542
543const INPUT_STRING_LEN_LIMIT: usize = 50;
544
545fn parse_signed_to_satoshi(
548 mut s: &str,
549 denom: Denomination,
550) -> Result<(bool, u64), InnerParseError> {
551 if s.is_empty() {
552 return Err(InnerParseError::MissingDigits(MissingDigitsError {
553 kind: MissingDigitsKind::Empty,
554 }));
555 }
556 if s.len() > INPUT_STRING_LEN_LIMIT {
557 return Err(InnerParseError::InputTooLarge(s.len()));
558 }
559
560 let is_negative = s.starts_with('-');
561 if is_negative {
562 if s.len() == 1 {
563 return Err(InnerParseError::MissingDigits(MissingDigitsError {
564 kind: MissingDigitsKind::OnlyMinusSign,
565 }));
566 }
567 s = &s[1..];
568 }
569
570 let max_decimals = {
571 let precision_diff = -denom.precision();
574 if precision_diff <= 0 {
575 let last_n = precision_diff.unsigned_abs().into();
580 if let Some(position) = is_too_precise(s, last_n) {
581 match s.parse::<i64>() {
582 Ok(0) => return Ok((is_negative, 0)),
583 _ =>
584 return Err(InnerParseError::TooPrecise(TooPreciseError {
585 position: position + is_negative as usize,
586 })),
587 }
588 }
589 s = &s[0..s.find('.').unwrap_or(s.len()) - last_n];
590 0
591 } else {
592 precision_diff
593 }
594 };
595
596 let mut decimals = None;
597 let mut value: u64 = 0; for (i, c) in s.char_indices() {
599 match c {
600 '0'..='9' => {
601 match 10_u64.checked_mul(value) {
603 None => return Err(InnerParseError::Overflow { is_negative }),
604 Some(val) => match val.checked_add((c as u8 - b'0') as u64) {
605 None => return Err(InnerParseError::Overflow { is_negative }),
606 Some(val) => value = val,
607 },
608 }
609 decimals = match decimals {
611 None => None,
612 Some(d) if d < max_decimals => Some(d + 1),
613 _ =>
614 return Err(InnerParseError::TooPrecise(TooPreciseError {
615 position: i + is_negative as usize,
616 })),
617 };
618 }
619 '.' => match decimals {
620 None if max_decimals <= 0 => break,
621 None => decimals = Some(0),
622 _ =>
624 return Err(InnerParseError::InvalidCharacter(InvalidCharacterError {
625 invalid_char: '.',
626 position: i + is_negative as usize,
627 })),
628 },
629 c =>
630 return Err(InnerParseError::InvalidCharacter(InvalidCharacterError {
631 invalid_char: c,
632 position: i + is_negative as usize,
633 })),
634 }
635 }
636
637 let scale_factor = max_decimals - decimals.unwrap_or(0);
639 for _ in 0..scale_factor {
640 value = match 10_u64.checked_mul(value) {
641 Some(v) => v,
642 None => return Err(InnerParseError::Overflow { is_negative }),
643 };
644 }
645
646 Ok((is_negative, value))
647}
648
649enum InnerParseError {
650 Overflow { is_negative: bool },
651 TooPrecise(TooPreciseError),
652 MissingDigits(MissingDigitsError),
653 InputTooLarge(usize),
654 InvalidCharacter(InvalidCharacterError),
655}
656
657impl From<Infallible> for InnerParseError {
658 fn from(never: Infallible) -> Self { match never {} }
659}
660
661impl InnerParseError {
662 fn convert(self, is_signed: bool) -> ParseAmountError {
663 match self {
664 Self::Overflow { is_negative } =>
665 OutOfRangeError { is_signed, is_greater_than_max: !is_negative }.into(),
666 Self::TooPrecise(error) => ParseAmountError::TooPrecise(error),
667 Self::MissingDigits(error) => ParseAmountError::MissingDigits(error),
668 Self::InputTooLarge(len) => ParseAmountError::InputTooLarge(InputTooLargeError { len }),
669 Self::InvalidCharacter(error) => ParseAmountError::InvalidCharacter(error),
670 }
671 }
672}
673
674fn split_amount_and_denomination(s: &str) -> Result<(&str, Denomination), ParseError> {
675 let (i, j) = if let Some(i) = s.find(' ') {
676 (i, i + 1)
677 } else {
678 let i = s
679 .find(|c: char| c.is_alphabetic())
680 .ok_or(ParseError::MissingDenomination(MissingDenominationError))?;
681 (i, i)
682 };
683 Ok((&s[..i], s[j..].parse()?))
684}
685
686struct FormatOptions {
688 fill: char,
689 align: Option<fmt::Alignment>,
690 width: Option<usize>,
691 precision: Option<usize>,
692 sign_plus: bool,
693 sign_aware_zero_pad: bool,
694}
695
696impl FormatOptions {
697 fn from_formatter(f: &fmt::Formatter) -> Self {
698 FormatOptions {
699 fill: f.fill(),
700 align: f.align(),
701 width: f.width(),
702 precision: f.precision(),
703 sign_plus: f.sign_plus(),
704 sign_aware_zero_pad: f.sign_aware_zero_pad(),
705 }
706 }
707}
708
709impl Default for FormatOptions {
710 fn default() -> Self {
711 FormatOptions {
712 fill: ' ',
713 align: None,
714 width: None,
715 precision: None,
716 sign_plus: false,
717 sign_aware_zero_pad: false,
718 }
719 }
720}
721
722fn dec_width(mut num: u64) -> usize {
723 let mut width = 1;
724 loop {
725 num /= 10;
726 if num == 0 {
727 break;
728 }
729 width += 1;
730 }
731 width
732}
733
734fn repeat_char(f: &mut dyn fmt::Write, c: char, count: usize) -> fmt::Result {
735 for _ in 0..count {
736 f.write_char(c)?;
737 }
738 Ok(())
739}
740
741fn fmt_satoshi_in(
743 satoshi: u64,
744 negative: bool,
745 f: &mut dyn fmt::Write,
746 denom: Denomination,
747 show_denom: bool,
748 options: FormatOptions,
749) -> fmt::Result {
750 let precision = denom.precision();
751 let mut num_after_decimal_point = 0;
754 let mut norm_nb_decimals = 0;
755 let mut num_before_decimal_point = satoshi;
756 let trailing_decimal_zeros;
757 let mut exp = 0;
758 match precision.cmp(&0) {
759 Ordering::Greater => {
761 if satoshi > 0 {
762 exp = precision as usize;
763 }
764 trailing_decimal_zeros = options.precision.unwrap_or(0);
765 }
766 Ordering::Less => {
767 let precision = precision.unsigned_abs();
768 let divisor = 10u64.pow(precision.into());
769 num_before_decimal_point = satoshi / divisor;
770 num_after_decimal_point = satoshi % divisor;
771 if num_after_decimal_point == 0 {
773 norm_nb_decimals = 0;
774 } else {
775 norm_nb_decimals = usize::from(precision);
776 while num_after_decimal_point % 10 == 0 {
777 norm_nb_decimals -= 1;
778 num_after_decimal_point /= 10
779 }
780 }
781 let opt_precision = options.precision.unwrap_or(0);
783 trailing_decimal_zeros = opt_precision.saturating_sub(norm_nb_decimals);
784 }
785 Ordering::Equal => trailing_decimal_zeros = options.precision.unwrap_or(0),
786 }
787 let total_decimals = norm_nb_decimals + trailing_decimal_zeros;
788 let mut num_width = if total_decimals > 0 {
790 1 + total_decimals
792 } else {
793 0
794 };
795 num_width += dec_width(num_before_decimal_point) + exp;
796 if options.sign_plus || negative {
797 num_width += 1;
798 }
799
800 if show_denom {
801 num_width += denom.as_str().len() + 1;
803 }
804
805 let width = options.width.unwrap_or(0);
806 let align = options.align.unwrap_or(fmt::Alignment::Right);
807 let (left_pad, pad_right) = match (num_width < width, options.sign_aware_zero_pad, align) {
808 (false, _, _) => (0, 0),
809 (true, true, _) | (true, false, fmt::Alignment::Right) => (width - num_width, 0),
811 (true, false, fmt::Alignment::Left) => (0, width - num_width),
812 (true, false, fmt::Alignment::Center) =>
814 ((width - num_width) / 2, (width - num_width + 1) / 2),
815 };
816
817 if !options.sign_aware_zero_pad {
818 repeat_char(f, options.fill, left_pad)?;
819 }
820
821 if negative {
822 write!(f, "-")?;
823 } else if options.sign_plus {
824 write!(f, "+")?;
825 }
826
827 if options.sign_aware_zero_pad {
828 repeat_char(f, '0', left_pad)?;
829 }
830
831 write!(f, "{}", num_before_decimal_point)?;
832
833 repeat_char(f, '0', exp)?;
834
835 if total_decimals > 0 {
836 write!(f, ".")?;
837 }
838 if norm_nb_decimals > 0 {
839 write!(f, "{:0width$}", num_after_decimal_point, width = norm_nb_decimals)?;
840 }
841 repeat_char(f, '0', trailing_decimal_zeros)?;
842
843 if show_denom {
844 write!(f, " {}", denom.as_str())?;
845 }
846
847 repeat_char(f, options.fill, pad_right)?;
848 Ok(())
849}
850
851#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
869#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
870pub struct Amount(u64);
871
872impl Amount {
873 pub const ZERO: Amount = Amount(0);
875 pub const ONE_SAT: Amount = Amount(1);
877 pub const ONE_BTC: Amount = Self::from_int_btc(1);
879 pub const MAX_MONEY: Amount = Self::from_int_btc(21_000_000);
881 pub const MIN: Amount = Amount::ZERO;
883 pub const MAX: Amount = Amount(u64::MAX);
885 pub const SIZE: usize = 8; pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) }
890
891 pub fn to_sat(self) -> u64 { self.0 }
893
894 #[cfg(feature = "alloc")]
896 pub fn from_btc(btc: f64) -> Result<Amount, ParseAmountError> {
897 Amount::from_float_in(btc, Denomination::Bitcoin)
898 }
899
900 pub const fn from_int_btc(btc: u64) -> Amount {
908 match btc.checked_mul(100_000_000) {
909 Some(amount) => Amount::from_sat(amount),
910 None => {
911 #[allow(unconditional_panic)]
913 #[allow(clippy::let_unit_value)]
914 #[allow(clippy::out_of_bounds_indexing)]
915 let _int_overflow_converting_btc_to_sats = [(); 0][1];
916 Amount(0)
917 }
918 }
919 }
920
921 pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
926 let (negative, satoshi) =
927 parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(false))?;
928 if negative {
929 return Err(ParseAmountError::OutOfRange(OutOfRangeError::negative()));
930 }
931 Ok(Amount::from_sat(satoshi))
932 }
933
934 pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseError> {
939 let (amt, denom) = split_amount_and_denomination(s)?;
940 Amount::from_str_in(amt, denom).map_err(Into::into)
941 }
942
943 #[cfg(feature = "alloc")]
947 pub fn to_float_in(self, denom: Denomination) -> f64 {
948 f64::from_str(&self.to_string_in(denom)).unwrap()
949 }
950
951 #[cfg(feature = "alloc")]
962 pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
963
964 #[cfg(feature = "alloc")]
970 pub fn from_float_in(value: f64, denom: Denomination) -> Result<Amount, ParseAmountError> {
971 if value < 0.0 {
972 return Err(OutOfRangeError::negative().into());
973 }
974 Amount::from_str_in(&value.to_string(), denom)
977 }
978
979 pub fn display_in(self, denomination: Denomination) -> Display {
981 Display {
982 sats_abs: self.to_sat(),
983 is_negative: false,
984 style: DisplayStyle::FixedDenomination { denomination, show_denomination: false },
985 }
986 }
987
988 pub fn display_dynamic(self) -> Display {
993 Display {
994 sats_abs: self.to_sat(),
995 is_negative: false,
996 style: DisplayStyle::DynamicDenomination,
997 }
998 }
999
1000 #[rustfmt::skip]
1004 pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
1005 fmt_satoshi_in(self.to_sat(), false, f, denom, false, FormatOptions::default())
1006 }
1007
1008 #[cfg(feature = "alloc")]
1012 pub fn to_string_in(self, denom: Denomination) -> String {
1013 let mut buf = String::new();
1014 self.fmt_value_in(&mut buf, denom).unwrap();
1015 buf
1016 }
1017
1018 #[cfg(feature = "alloc")]
1021 pub fn to_string_with_denomination(self, denom: Denomination) -> String {
1022 let mut buf = String::new();
1023 self.fmt_value_in(&mut buf, denom).unwrap();
1024 write!(buf, " {}", denom).unwrap();
1025 buf
1026 }
1027
1028 pub fn checked_add(self, rhs: Amount) -> Option<Amount> {
1034 self.0.checked_add(rhs.0).map(Amount)
1035 }
1036
1037 pub fn checked_sub(self, rhs: Amount) -> Option<Amount> {
1041 self.0.checked_sub(rhs.0).map(Amount)
1042 }
1043
1044 pub fn checked_mul(self, rhs: u64) -> Option<Amount> { self.0.checked_mul(rhs).map(Amount) }
1048
1049 pub fn checked_div(self, rhs: u64) -> Option<Amount> { self.0.checked_div(rhs).map(Amount) }
1055
1056 #[cfg(feature = "alloc")]
1064 pub fn div_by_weight_ceil(self, rhs: Weight) -> Option<FeeRate> {
1065 let sats = self.0.checked_mul(1000)?;
1066 let wu = rhs.to_wu();
1067
1068 let fee_rate = sats.checked_add(wu.checked_sub(1)?)?.checked_div(wu)?;
1069 Some(FeeRate::from_sat_per_kwu(fee_rate))
1070 }
1071
1072 #[cfg(feature = "alloc")]
1079 pub fn div_by_weight_floor(self, rhs: Weight) -> Option<FeeRate> {
1080 let fee_rate = self.0.checked_mul(1_000)?.checked_div(rhs.to_wu())?;
1081 Some(FeeRate::from_sat_per_kwu(fee_rate))
1082 }
1083
1084 pub fn checked_rem(self, rhs: u64) -> Option<Amount> { self.0.checked_rem(rhs).map(Amount) }
1088
1089 pub fn unchecked_add(self, rhs: Amount) -> Amount { Self(self.0 + rhs.0) }
1093
1094 pub fn unchecked_sub(self, rhs: Amount) -> Amount { Self(self.0 - rhs.0) }
1098
1099 pub fn to_signed(self) -> Result<SignedAmount, OutOfRangeError> {
1101 if self.to_sat() > SignedAmount::MAX.to_sat() as u64 {
1102 Err(OutOfRangeError::too_big(true))
1103 } else {
1104 Ok(SignedAmount::from_sat(self.to_sat() as i64))
1105 }
1106 }
1107}
1108
1109impl default::Default for Amount {
1110 fn default() -> Self { Amount::ZERO }
1111}
1112
1113impl fmt::Debug for Amount {
1114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} SAT", self.to_sat()) }
1115}
1116
1117impl fmt::Display for Amount {
1120 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1121 let satoshis = self.to_sat();
1122 let denomination = Denomination::Bitcoin;
1123 let mut format_options = FormatOptions::from_formatter(f);
1124
1125 if f.precision().is_none() && satoshis.rem_euclid(Amount::ONE_BTC.to_sat()) != 0 {
1126 format_options.precision = Some(8);
1127 }
1128
1129 fmt_satoshi_in(satoshis, false, f, denomination, true, format_options)
1130 }
1131}
1132
1133impl ops::Add for Amount {
1134 type Output = Amount;
1135
1136 fn add(self, rhs: Amount) -> Self::Output {
1137 self.checked_add(rhs).expect("Amount addition error")
1138 }
1139}
1140
1141impl ops::AddAssign for Amount {
1142 fn add_assign(&mut self, other: Amount) { *self = *self + other }
1143}
1144
1145impl ops::Sub for Amount {
1146 type Output = Amount;
1147
1148 fn sub(self, rhs: Amount) -> Self::Output {
1149 self.checked_sub(rhs).expect("Amount subtraction error")
1150 }
1151}
1152
1153impl ops::SubAssign for Amount {
1154 fn sub_assign(&mut self, other: Amount) { *self = *self - other }
1155}
1156
1157impl ops::Rem<u64> for Amount {
1158 type Output = Amount;
1159
1160 fn rem(self, modulus: u64) -> Self {
1161 self.checked_rem(modulus).expect("Amount remainder error")
1162 }
1163}
1164
1165impl ops::RemAssign<u64> for Amount {
1166 fn rem_assign(&mut self, modulus: u64) { *self = *self % modulus }
1167}
1168
1169impl ops::Mul<u64> for Amount {
1170 type Output = Amount;
1171
1172 fn mul(self, rhs: u64) -> Self::Output {
1173 self.checked_mul(rhs).expect("Amount multiplication error")
1174 }
1175}
1176
1177impl ops::MulAssign<u64> for Amount {
1178 fn mul_assign(&mut self, rhs: u64) { *self = *self * rhs }
1179}
1180
1181impl ops::Div<u64> for Amount {
1182 type Output = Amount;
1183
1184 fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).expect("Amount division error") }
1185}
1186
1187impl ops::DivAssign<u64> for Amount {
1188 fn div_assign(&mut self, rhs: u64) { *self = *self / rhs }
1189}
1190
1191impl FromStr for Amount {
1192 type Err = ParseError;
1193
1194 fn from_str(s: &str) -> Result<Self, Self::Err> { Amount::from_str_with_denomination(s) }
1195}
1196
1197impl TryFrom<SignedAmount> for Amount {
1198 type Error = OutOfRangeError;
1199
1200 fn try_from(value: SignedAmount) -> Result<Self, Self::Error> { value.to_unsigned() }
1201}
1202
1203impl core::iter::Sum for Amount {
1204 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
1205 let sats: u64 = iter.map(|amt| amt.0).sum();
1206 Amount::from_sat(sats)
1207 }
1208}
1209
1210#[cfg(feature = "encoding")]
1211impl encoding::Encode for Amount {
1212 type Encoder<'e> = AmountEncoder<'e>;
1213
1214 #[inline]
1215 fn encoder(&self) -> Self::Encoder<'_> {
1216 AmountEncoder::new(encoding::ArrayEncoder::without_length_prefix(
1217 self.to_sat().to_le_bytes(),
1218 ))
1219 }
1220}
1221
1222#[cfg(feature = "encoding")]
1223impl encoding::Decode for Amount {
1224 type Decoder = AmountDecoder;
1225}
1226
1227#[cfg(feature = "encoding")]
1228encoding::encoder_newtype_exact! {
1229 #[derive(Debug, Clone)]
1231 pub struct AmountEncoder<'e>(encoding::ArrayEncoder<8>);
1232}
1233
1234#[cfg(feature = "encoding")]
1235crate::decoder_newtype! {
1236 #[derive(Debug, Clone)]
1238 pub struct AmountDecoder(encoding::ArrayDecoder<8>);
1239
1240 pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
1242
1243 fn end(result: Result<[u8; 8], encoding::UnexpectedEofError>) -> Result<Amount, AmountDecoderError> {
1244 let value = result.map_err(AmountDecoderError)?;
1245 let a = u64::from_le_bytes(value);
1246 Ok(Amount::from_sat(a))
1247 }
1248}
1249
1250#[cfg(feature = "encoding")]
1252#[derive(Debug, Clone, PartialEq, Eq)]
1253pub struct AmountDecoderError(pub(super) encoding::UnexpectedEofError);
1254
1255#[cfg(feature = "encoding")]
1256impl From<Infallible> for AmountDecoderError {
1257 fn from(never: Infallible) -> Self { match never {} }
1258}
1259
1260#[cfg(feature = "encoding")]
1261impl fmt::Display for AmountDecoderError {
1262 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1263 write_err!(f, "amount decoder error"; self.0)
1264 }
1265}
1266
1267#[cfg(all(feature = "std", feature = "encoding"))]
1268impl std::error::Error for AmountDecoderError {
1269 #[inline]
1270 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
1271}
1272
1273#[derive(Debug, Clone)]
1287pub struct Display {
1288 sats_abs: u64,
1290 is_negative: bool,
1292 style: DisplayStyle,
1294}
1295
1296impl Display {
1297 pub fn show_denomination(mut self) -> Self {
1299 match &mut self.style {
1300 DisplayStyle::FixedDenomination { show_denomination, .. } => *show_denomination = true,
1301 DisplayStyle::DynamicDenomination => (),
1303 }
1304 self
1305 }
1306}
1307
1308impl fmt::Display for Display {
1309 #[rustfmt::skip]
1310 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1311 let format_options = FormatOptions::from_formatter(f);
1312 match &self.style {
1313 DisplayStyle::FixedDenomination { show_denomination, denomination } => {
1314 fmt_satoshi_in(self.sats_abs, self.is_negative, f, *denomination, *show_denomination, format_options)
1315 },
1316 DisplayStyle::DynamicDenomination if self.sats_abs >= Amount::ONE_BTC.to_sat() => {
1317 fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Bitcoin, true, format_options)
1318 },
1319 DisplayStyle::DynamicDenomination => {
1320 fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Satoshi, true, format_options)
1321 },
1322 }
1323 }
1324}
1325
1326#[derive(Clone, Debug)]
1327enum DisplayStyle {
1328 FixedDenomination { denomination: Denomination, show_denomination: bool },
1329 DynamicDenomination,
1330}
1331
1332#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1347pub struct SignedAmount(i64);
1348
1349impl SignedAmount {
1350 pub const ZERO: SignedAmount = SignedAmount(0);
1352 pub const ONE_SAT: SignedAmount = SignedAmount(1);
1354 pub const ONE_BTC: SignedAmount = SignedAmount(100_000_000);
1356 pub const MAX_MONEY: SignedAmount = SignedAmount(21_000_000 * 100_000_000);
1358 pub const MIN: SignedAmount = SignedAmount(i64::MIN);
1360 pub const MAX: SignedAmount = SignedAmount(i64::MAX);
1362
1363 pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
1365
1366 pub fn to_sat(self) -> i64 { self.0 }
1368
1369 #[cfg(feature = "alloc")]
1371 pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
1372 SignedAmount::from_float_in(btc, Denomination::Bitcoin)
1373 }
1374
1375 pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAmountError> {
1380 match parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(true))? {
1381 (false, sat) if sat > i64::MAX as u64 =>
1383 Err(ParseAmountError::OutOfRange(OutOfRangeError::too_big(true))),
1384 (false, sat) => Ok(SignedAmount(sat as i64)),
1385 (true, sat) if sat == i64::MIN.unsigned_abs() => Ok(SignedAmount(i64::MIN)),
1386 (true, sat) if sat > i64::MIN.unsigned_abs() =>
1387 Err(ParseAmountError::OutOfRange(OutOfRangeError::too_small())),
1388 (true, sat) => Ok(SignedAmount(-(sat as i64))),
1389 }
1390 }
1391
1392 pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseError> {
1397 let (amt, denom) = split_amount_and_denomination(s)?;
1398 SignedAmount::from_str_in(amt, denom).map_err(Into::into)
1399 }
1400
1401 #[cfg(feature = "alloc")]
1405 pub fn to_float_in(self, denom: Denomination) -> f64 {
1406 f64::from_str(&self.to_string_in(denom)).unwrap()
1407 }
1408
1409 #[cfg(feature = "alloc")]
1415 pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
1416
1417 #[cfg(feature = "alloc")]
1423 pub fn from_float_in(
1424 value: f64,
1425 denom: Denomination,
1426 ) -> Result<SignedAmount, ParseAmountError> {
1427 SignedAmount::from_str_in(&value.to_string(), denom)
1430 }
1431
1432 pub fn display_in(self, denomination: Denomination) -> Display {
1434 Display {
1435 sats_abs: self.unsigned_abs().to_sat(),
1436 is_negative: self.is_negative(),
1437 style: DisplayStyle::FixedDenomination { denomination, show_denomination: false },
1438 }
1439 }
1440
1441 pub fn display_dynamic(self) -> Display {
1446 Display {
1447 sats_abs: self.unsigned_abs().to_sat(),
1448 is_negative: self.is_negative(),
1449 style: DisplayStyle::DynamicDenomination,
1450 }
1451 }
1452
1453 #[rustfmt::skip]
1457 pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
1458 fmt_satoshi_in(self.unsigned_abs().to_sat(), self.is_negative(), f, denom, false, FormatOptions::default())
1459 }
1460
1461 #[cfg(feature = "alloc")]
1465 pub fn to_string_in(self, denom: Denomination) -> String {
1466 let mut buf = String::new();
1467 self.fmt_value_in(&mut buf, denom).unwrap();
1468 buf
1469 }
1470
1471 #[cfg(feature = "alloc")]
1474 pub fn to_string_with_denomination(self, denom: Denomination) -> String {
1475 let mut buf = String::new();
1476 self.fmt_value_in(&mut buf, denom).unwrap();
1477 write!(buf, " {}", denom).unwrap();
1478 buf
1479 }
1480
1481 pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
1485
1486 pub fn unsigned_abs(self) -> Amount { Amount(self.0.unsigned_abs()) }
1488
1489 pub fn signum(self) -> i64 { self.0.signum() }
1495
1496 pub fn is_positive(self) -> bool { self.0.is_positive() }
1499
1500 pub fn is_negative(self) -> bool { self.0.is_negative() }
1503
1504 pub fn checked_abs(self) -> Option<SignedAmount> { self.0.checked_abs().map(SignedAmount) }
1507
1508 pub fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
1511 self.0.checked_add(rhs.0).map(SignedAmount)
1512 }
1513
1514 pub fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
1517 self.0.checked_sub(rhs.0).map(SignedAmount)
1518 }
1519
1520 pub fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
1523 self.0.checked_mul(rhs).map(SignedAmount)
1524 }
1525
1526 pub fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
1531 self.0.checked_div(rhs).map(SignedAmount)
1532 }
1533
1534 pub fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
1537 self.0.checked_rem(rhs).map(SignedAmount)
1538 }
1539
1540 pub fn unchecked_add(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 + rhs.0) }
1544
1545 pub fn unchecked_sub(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 - rhs.0) }
1549
1550 pub fn positive_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
1553 if self.is_negative() || rhs.is_negative() || rhs > self {
1554 None
1555 } else {
1556 self.checked_sub(rhs)
1557 }
1558 }
1559
1560 pub fn to_unsigned(self) -> Result<Amount, OutOfRangeError> {
1562 if self.is_negative() {
1563 Err(OutOfRangeError::negative())
1564 } else {
1565 Ok(Amount::from_sat(self.to_sat() as u64))
1566 }
1567 }
1568}
1569
1570impl default::Default for SignedAmount {
1571 fn default() -> Self { SignedAmount::ZERO }
1572}
1573
1574impl fmt::Debug for SignedAmount {
1575 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1576 write!(f, "SignedAmount({} SAT)", self.to_sat())
1577 }
1578}
1579
1580impl fmt::Display for SignedAmount {
1583 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1584 self.fmt_value_in(f, Denomination::Bitcoin)?;
1585 write!(f, " {}", Denomination::Bitcoin)
1586 }
1587}
1588
1589impl ops::Add for SignedAmount {
1590 type Output = SignedAmount;
1591
1592 fn add(self, rhs: SignedAmount) -> Self::Output {
1593 self.checked_add(rhs).expect("SignedAmount addition error")
1594 }
1595}
1596
1597impl ops::AddAssign for SignedAmount {
1598 fn add_assign(&mut self, other: SignedAmount) { *self = *self + other }
1599}
1600
1601impl ops::Sub for SignedAmount {
1602 type Output = SignedAmount;
1603
1604 fn sub(self, rhs: SignedAmount) -> Self::Output {
1605 self.checked_sub(rhs).expect("SignedAmount subtraction error")
1606 }
1607}
1608
1609impl ops::SubAssign for SignedAmount {
1610 fn sub_assign(&mut self, other: SignedAmount) { *self = *self - other }
1611}
1612
1613impl ops::Rem<i64> for SignedAmount {
1614 type Output = SignedAmount;
1615
1616 fn rem(self, modulus: i64) -> Self {
1617 self.checked_rem(modulus).expect("SignedAmount remainder error")
1618 }
1619}
1620
1621impl ops::RemAssign<i64> for SignedAmount {
1622 fn rem_assign(&mut self, modulus: i64) { *self = *self % modulus }
1623}
1624
1625impl ops::Mul<i64> for SignedAmount {
1626 type Output = SignedAmount;
1627
1628 fn mul(self, rhs: i64) -> Self::Output {
1629 self.checked_mul(rhs).expect("SignedAmount multiplication error")
1630 }
1631}
1632
1633impl ops::MulAssign<i64> for SignedAmount {
1634 fn mul_assign(&mut self, rhs: i64) { *self = *self * rhs }
1635}
1636
1637impl ops::Div<i64> for SignedAmount {
1638 type Output = SignedAmount;
1639
1640 fn div(self, rhs: i64) -> Self::Output {
1641 self.checked_div(rhs).expect("SignedAmount division error")
1642 }
1643}
1644
1645impl ops::DivAssign<i64> for SignedAmount {
1646 fn div_assign(&mut self, rhs: i64) { *self = *self / rhs }
1647}
1648
1649impl ops::Neg for SignedAmount {
1650 type Output = Self;
1651
1652 fn neg(self) -> Self::Output { Self(self.0.neg()) }
1653}
1654
1655impl FromStr for SignedAmount {
1656 type Err = ParseError;
1657
1658 fn from_str(s: &str) -> Result<Self, Self::Err> { SignedAmount::from_str_with_denomination(s) }
1659}
1660
1661impl TryFrom<Amount> for SignedAmount {
1662 type Error = OutOfRangeError;
1663
1664 fn try_from(value: Amount) -> Result<Self, Self::Error> { value.to_signed() }
1665}
1666
1667impl core::iter::Sum for SignedAmount {
1668 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
1669 let sats: i64 = iter.map(|amt| amt.0).sum();
1670 SignedAmount::from_sat(sats)
1671 }
1672}
1673
1674pub trait CheckedSum<R>: private::SumSeal<R> {
1676 fn checked_sum(self) -> Option<R>;
1679}
1680
1681impl<T> CheckedSum<Amount> for T
1682where
1683 T: Iterator<Item = Amount>,
1684{
1685 fn checked_sum(mut self) -> Option<Amount> {
1686 let first = Some(self.next().unwrap_or_default());
1687
1688 self.fold(first, |acc, item| acc.and_then(|acc| acc.checked_add(item)))
1689 }
1690}
1691
1692impl<T> CheckedSum<SignedAmount> for T
1693where
1694 T: Iterator<Item = SignedAmount>,
1695{
1696 fn checked_sum(mut self) -> Option<SignedAmount> {
1697 let first = Some(self.next().unwrap_or_default());
1698
1699 self.fold(first, |acc, item| acc.and_then(|acc| acc.checked_add(item)))
1700 }
1701}
1702
1703mod private {
1704 use super::{Amount, SignedAmount};
1705
1706 pub trait SumSeal<A> {}
1708
1709 impl<T> SumSeal<Amount> for T where T: Iterator<Item = Amount> {}
1710 impl<T> SumSeal<SignedAmount> for T where T: Iterator<Item = SignedAmount> {}
1711}
1712
1713#[cfg(feature = "serde")]
1714pub mod serde {
1715 #![allow(missing_docs)]
1717
1718 use core::fmt;
1736
1737 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1738
1739 #[cfg(feature = "alloc")]
1740 use super::Denomination;
1741 use super::{Amount, ParseAmountError, SignedAmount};
1742
1743 pub trait SerdeAmount: Copy + Sized {
1746 fn ser_sat<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1747 fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error>;
1748 #[cfg(feature = "alloc")]
1749 fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1750 #[cfg(feature = "alloc")]
1751 fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error>;
1752 }
1753
1754 mod private {
1755 pub struct Token;
1757 }
1758
1759 pub trait SerdeAmountForOpt: Copy + Sized + SerdeAmount {
1761 fn type_prefix(_: private::Token) -> &'static str;
1762 fn ser_sat_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1763 #[cfg(feature = "alloc")]
1764 fn ser_btc_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error>;
1765 }
1766
1767 struct DisplayFullError(ParseAmountError);
1768
1769 #[cfg(feature = "std")]
1770 impl fmt::Display for DisplayFullError {
1771 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1772 use std::error::Error;
1773
1774 fmt::Display::fmt(&self.0, f)?;
1775 let mut source_opt = self.0.source();
1776 while let Some(source) = source_opt {
1777 write!(f, ": {}", source)?;
1778 source_opt = source.source();
1779 }
1780 Ok(())
1781 }
1782 }
1783
1784 #[cfg(not(feature = "std"))]
1785 impl fmt::Display for DisplayFullError {
1786 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
1787 }
1788
1789 impl SerdeAmount for Amount {
1790 fn ser_sat<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1791 u64::serialize(&self.to_sat(), s)
1792 }
1793 fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1794 Ok(Amount::from_sat(u64::deserialize(d)?))
1795 }
1796 #[cfg(feature = "alloc")]
1797 fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1798 f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
1799 }
1800 #[cfg(feature = "alloc")]
1801 fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1802 use serde::de::Error;
1803 Amount::from_btc(f64::deserialize(d)?)
1804 .map_err(DisplayFullError)
1805 .map_err(D::Error::custom)
1806 }
1807 }
1808
1809 impl SerdeAmountForOpt for Amount {
1810 fn type_prefix(_: private::Token) -> &'static str { "u" }
1811 fn ser_sat_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1812 s.serialize_some(&self.to_sat())
1813 }
1814 #[cfg(feature = "alloc")]
1815 fn ser_btc_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1816 s.serialize_some(&self.to_btc())
1817 }
1818 }
1819
1820 impl SerdeAmount for SignedAmount {
1821 fn ser_sat<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1822 i64::serialize(&self.to_sat(), s)
1823 }
1824 fn des_sat<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1825 Ok(SignedAmount::from_sat(i64::deserialize(d)?))
1826 }
1827 #[cfg(feature = "alloc")]
1828 fn ser_btc<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1829 f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
1830 }
1831 #[cfg(feature = "alloc")]
1832 fn des_btc<'d, D: Deserializer<'d>>(d: D, _: private::Token) -> Result<Self, D::Error> {
1833 use serde::de::Error;
1834 SignedAmount::from_btc(f64::deserialize(d)?)
1835 .map_err(DisplayFullError)
1836 .map_err(D::Error::custom)
1837 }
1838 }
1839
1840 impl SerdeAmountForOpt for SignedAmount {
1841 fn type_prefix(_: private::Token) -> &'static str { "i" }
1842 fn ser_sat_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1843 s.serialize_some(&self.to_sat())
1844 }
1845 #[cfg(feature = "alloc")]
1846 fn ser_btc_opt<S: Serializer>(self, s: S, _: private::Token) -> Result<S::Ok, S::Error> {
1847 s.serialize_some(&self.to_btc())
1848 }
1849 }
1850
1851 pub mod as_sat {
1852 use serde::{Deserializer, Serializer};
1856
1857 use super::private;
1858 use crate::amount::serde::SerdeAmount;
1859
1860 pub fn serialize<A: SerdeAmount, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error> {
1861 a.ser_sat(s, private::Token)
1862 }
1863
1864 pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result<A, D::Error> {
1865 A::des_sat(d, private::Token)
1866 }
1867
1868 pub mod opt {
1869 use core::fmt;
1873 use core::marker::PhantomData;
1874
1875 use serde::{de, Deserializer, Serializer};
1876
1877 use super::private;
1878 use crate::amount::serde::SerdeAmountForOpt;
1879
1880 pub fn serialize<A: SerdeAmountForOpt, S: Serializer>(
1881 a: &Option<A>,
1882 s: S,
1883 ) -> Result<S::Ok, S::Error> {
1884 match *a {
1885 Some(a) => a.ser_sat_opt(s, private::Token),
1886 None => s.serialize_none(),
1887 }
1888 }
1889
1890 pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>(
1891 d: D,
1892 ) -> Result<Option<A>, D::Error> {
1893 struct VisitOptAmt<X>(PhantomData<X>);
1894
1895 impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt<X> {
1896 type Value = Option<X>;
1897
1898 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1899 write!(formatter, "An Option<{}64>", X::type_prefix(private::Token))
1900 }
1901
1902 fn visit_none<E>(self) -> Result<Self::Value, E>
1903 where
1904 E: de::Error,
1905 {
1906 Ok(None)
1907 }
1908 fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
1909 where
1910 D: Deserializer<'de>,
1911 {
1912 Ok(Some(X::des_sat(d, private::Token)?))
1913 }
1914 }
1915 d.deserialize_option(VisitOptAmt::<A>(PhantomData))
1916 }
1917 }
1918 }
1919
1920 #[cfg(feature = "alloc")]
1921 pub mod as_btc {
1922 use serde::{Deserializer, Serializer};
1926
1927 use super::private;
1928 use crate::amount::serde::SerdeAmount;
1929
1930 pub fn serialize<A: SerdeAmount, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error> {
1931 a.ser_btc(s, private::Token)
1932 }
1933
1934 pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result<A, D::Error> {
1935 A::des_btc(d, private::Token)
1936 }
1937
1938 pub mod opt {
1939 use core::fmt;
1943 use core::marker::PhantomData;
1944
1945 use serde::{de, Deserializer, Serializer};
1946
1947 use super::private;
1948 use crate::amount::serde::SerdeAmountForOpt;
1949
1950 pub fn serialize<A: SerdeAmountForOpt, S: Serializer>(
1951 a: &Option<A>,
1952 s: S,
1953 ) -> Result<S::Ok, S::Error> {
1954 match *a {
1955 Some(a) => a.ser_btc_opt(s, private::Token),
1956 None => s.serialize_none(),
1957 }
1958 }
1959
1960 pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>(
1961 d: D,
1962 ) -> Result<Option<A>, D::Error> {
1963 struct VisitOptAmt<X>(PhantomData<X>);
1964
1965 impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt<X> {
1966 type Value = Option<X>;
1967
1968 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1969 write!(formatter, "An Option<f64>")
1970 }
1971
1972 fn visit_none<E>(self) -> Result<Self::Value, E>
1973 where
1974 E: de::Error,
1975 {
1976 Ok(None)
1977 }
1978 fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
1979 where
1980 D: Deserializer<'de>,
1981 {
1982 Ok(Some(X::des_btc(d, private::Token)?))
1983 }
1984 }
1985 d.deserialize_option(VisitOptAmt::<A>(PhantomData))
1986 }
1987 }
1988 }
1989}
1990
1991#[cfg(feature = "arbitrary")]
1992impl<'a> Arbitrary<'a> for Amount {
1993 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1994 let a = u64::arbitrary(u)?;
1995 Ok(Amount(a))
1996 }
1997}
1998
1999#[cfg(feature = "arbitrary")]
2000impl<'a> Arbitrary<'a> for Denomination {
2001 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2002 let choice = u.int_in_range(0..=5)?;
2003 match choice {
2004 0 => Ok(Denomination::Bitcoin),
2005 1 => Ok(Denomination::CentiBitcoin),
2006 2 => Ok(Denomination::MilliBitcoin),
2007 3 => Ok(Denomination::MicroBitcoin),
2008 4 => Ok(Denomination::Bit),
2009 _ => Ok(Denomination::Satoshi),
2010 }
2011 }
2012}
2013
2014#[cfg(feature = "arbitrary")]
2015impl<'a> Arbitrary<'a> for SignedAmount {
2016 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2017 let s = i64::arbitrary(u)?;
2018 Ok(SignedAmount(s))
2019 }
2020}
2021
2022#[cfg(kani)]
2023mod verification {
2024 use std::cmp;
2025 use std::convert::TryInto;
2026
2027 use super::*;
2028
2029 #[kani::unwind(4)]
2042 #[kani::proof]
2043 fn u_amount_add_homomorphic() {
2044 let n1 = kani::any::<u64>();
2045 let n2 = kani::any::<u64>();
2046 kani::assume(n1.checked_add(n2).is_some()); assert_eq!(Amount::from_sat(n1) + Amount::from_sat(n2), Amount::from_sat(n1 + n2));
2048
2049 let mut amt = Amount::from_sat(n1);
2050 amt += Amount::from_sat(n2);
2051 assert_eq!(amt, Amount::from_sat(n1 + n2));
2052
2053 let max = cmp::max(n1, n2);
2054 let min = cmp::min(n1, n2);
2055 assert_eq!(Amount::from_sat(max) - Amount::from_sat(min), Amount::from_sat(max - min));
2056
2057 let mut amt = Amount::from_sat(max);
2058 amt -= Amount::from_sat(min);
2059 assert_eq!(amt, Amount::from_sat(max - min));
2060
2061 assert_eq!(
2062 Amount::from_sat(n1).to_signed(),
2063 if n1 <= i64::MAX as u64 {
2064 Ok(SignedAmount::from_sat(n1.try_into().unwrap()))
2065 } else {
2066 Err(OutOfRangeError::too_big(true))
2067 },
2068 );
2069 }
2070
2071 #[kani::unwind(4)]
2072 #[kani::proof]
2073 fn u_amount_add_homomorphic_checked() {
2074 let n1 = kani::any::<u64>();
2075 let n2 = kani::any::<u64>();
2076 assert_eq!(
2077 Amount::from_sat(n1).checked_add(Amount::from_sat(n2)),
2078 n1.checked_add(n2).map(Amount::from_sat),
2079 );
2080 assert_eq!(
2081 Amount::from_sat(n1).checked_sub(Amount::from_sat(n2)),
2082 n1.checked_sub(n2).map(Amount::from_sat),
2083 );
2084 }
2085
2086 #[kani::unwind(4)]
2087 #[kani::proof]
2088 fn s_amount_add_homomorphic() {
2089 let n1 = kani::any::<i64>();
2090 let n2 = kani::any::<i64>();
2091 kani::assume(n1.checked_add(n2).is_some()); kani::assume(n1.checked_sub(n2).is_some()); assert_eq!(
2094 SignedAmount::from_sat(n1) + SignedAmount::from_sat(n2),
2095 SignedAmount::from_sat(n1 + n2)
2096 );
2097 assert_eq!(
2098 SignedAmount::from_sat(n1) - SignedAmount::from_sat(n2),
2099 SignedAmount::from_sat(n1 - n2)
2100 );
2101
2102 let mut amt = SignedAmount::from_sat(n1);
2103 amt += SignedAmount::from_sat(n2);
2104 assert_eq!(amt, SignedAmount::from_sat(n1 + n2));
2105 let mut amt = SignedAmount::from_sat(n1);
2106 amt -= SignedAmount::from_sat(n2);
2107 assert_eq!(amt, SignedAmount::from_sat(n1 - n2));
2108
2109 assert_eq!(
2110 SignedAmount::from_sat(n1).to_unsigned(),
2111 if n1 >= 0 {
2112 Ok(Amount::from_sat(n1.try_into().unwrap()))
2113 } else {
2114 Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
2115 },
2116 );
2117 }
2118
2119 #[kani::unwind(4)]
2120 #[kani::proof]
2121 fn s_amount_add_homomorphic_checked() {
2122 let n1 = kani::any::<i64>();
2123 let n2 = kani::any::<i64>();
2124 assert_eq!(
2125 SignedAmount::from_sat(n1).checked_add(SignedAmount::from_sat(n2)),
2126 n1.checked_add(n2).map(SignedAmount::from_sat),
2127 );
2128 assert_eq!(
2129 SignedAmount::from_sat(n1).checked_sub(SignedAmount::from_sat(n2)),
2130 n1.checked_sub(n2).map(SignedAmount::from_sat),
2131 );
2132
2133 assert_eq!(
2134 SignedAmount::from_sat(n1).positive_sub(SignedAmount::from_sat(n2)),
2135 if n1 >= 0 && n2 >= 0 && n1 >= n2 {
2136 Some(SignedAmount::from_sat(n1 - n2))
2137 } else {
2138 None
2139 },
2140 );
2141 }
2142}
2143
2144#[cfg(test)]
2145mod tests {
2146 #[cfg(feature = "alloc")]
2147 use alloc::format;
2148 #[cfg(feature = "std")]
2149 use std::panic;
2150
2151 use super::*;
2152
2153 #[test]
2154 #[cfg(feature = "alloc")]
2155 fn from_str_zero() {
2156 let denoms = ["BTC", "mBTC", "uBTC", "nBTC", "pBTC", "bits", "sats", "msats"];
2157 for denom in denoms {
2158 for v in &["0", "000"] {
2159 let s = format!("{} {}", v, denom);
2160 match Amount::from_str(&s) {
2161 Err(e) => panic!("Failed to crate amount from {}: {:?}", s, e),
2162 Ok(amount) => assert_eq!(amount, Amount::from_sat(0)),
2163 }
2164 }
2165
2166 let s = format!("-0 {}", denom);
2167 match Amount::from_str(&s) {
2168 Err(e) => assert_eq!(
2169 e,
2170 ParseError::Amount(ParseAmountError::OutOfRange(OutOfRangeError::negative()))
2171 ),
2172 Ok(_) => panic!("Unsigned amount from {}", s),
2173 }
2174 match SignedAmount::from_str(&s) {
2175 Err(e) => panic!("Failed to crate amount from {}: {:?}", s, e),
2176 Ok(amount) => assert_eq!(amount, SignedAmount::from_sat(0)),
2177 }
2178 }
2179 }
2180
2181 #[test]
2182 fn from_int_btc() {
2183 let amt = Amount::from_int_btc(2);
2184 assert_eq!(Amount::from_sat(200_000_000), amt);
2185 }
2186
2187 #[should_panic]
2188 #[test]
2189 fn from_int_btc_panic() { Amount::from_int_btc(u64::MAX); }
2190
2191 #[test]
2192 fn test_signed_amount_try_from_amount() {
2193 let ua_positive = Amount::from_sat(123);
2194 let sa_positive = SignedAmount::try_from(ua_positive).unwrap();
2195 assert_eq!(sa_positive, SignedAmount(123));
2196
2197 let ua_max = Amount::MAX;
2198 let result = SignedAmount::try_from(ua_max);
2199 assert_eq!(result, Err(OutOfRangeError { is_signed: true, is_greater_than_max: true }));
2200 }
2201
2202 #[test]
2203 fn test_amount_try_from_signed_amount() {
2204 let sa_positive = SignedAmount(123);
2205 let ua_positive = Amount::try_from(sa_positive).unwrap();
2206 assert_eq!(ua_positive, Amount::from_sat(123));
2207
2208 let sa_negative = SignedAmount(-123);
2209 let result = Amount::try_from(sa_negative);
2210 assert_eq!(result, Err(OutOfRangeError { is_signed: false, is_greater_than_max: false }));
2211 }
2212
2213 #[test]
2214 fn mul_div() {
2215 let sat = Amount::from_sat;
2216 let ssat = SignedAmount::from_sat;
2217
2218 assert_eq!(sat(14) * 3, sat(42));
2219 assert_eq!(sat(14) / 2, sat(7));
2220 assert_eq!(sat(14) % 3, sat(2));
2221 assert_eq!(ssat(-14) * 3, ssat(-42));
2222 assert_eq!(ssat(-14) / 2, ssat(-7));
2223 assert_eq!(ssat(-14) % 3, ssat(-2));
2224
2225 let mut b = ssat(30);
2226 b /= 3;
2227 assert_eq!(b, ssat(10));
2228 b %= 3;
2229 assert_eq!(b, ssat(1));
2230 }
2231
2232 #[cfg(feature = "std")]
2233 #[test]
2234 fn test_overflows() {
2235 let result = panic::catch_unwind(|| Amount::MAX + Amount::from_sat(1));
2237 assert!(result.is_err());
2238 let result = panic::catch_unwind(|| Amount::from_sat(8446744073709551615) * 3);
2239 assert!(result.is_err());
2240 }
2241
2242 #[test]
2243 fn checked_arithmetic() {
2244 let sat = Amount::from_sat;
2245 let ssat = SignedAmount::from_sat;
2246
2247 assert_eq!(SignedAmount::MAX.checked_add(ssat(1)), None);
2248 assert_eq!(SignedAmount::MIN.checked_sub(ssat(1)), None);
2249 assert_eq!(Amount::MAX.checked_add(sat(1)), None);
2250 assert_eq!(Amount::MIN.checked_sub(sat(1)), None);
2251
2252 assert_eq!(sat(5).checked_div(2), Some(sat(2))); assert_eq!(ssat(-6).checked_div(2), Some(ssat(-3)));
2254 }
2255
2256 #[cfg(feature = "alloc")]
2257 #[test]
2258 fn amount_checked_div_by_weight_ceil() {
2259 let weight = Weight::from_kwu(1).unwrap();
2260 let fee_rate = Amount::from_sat(1).div_by_weight_ceil(weight).unwrap();
2261 assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
2263
2264 let weight = Weight::from_wu(381);
2265 let fee_rate = Amount::from_sat(329).div_by_weight_ceil(weight).unwrap();
2266 assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864));
2269
2270 let fee_rate = Amount::MAX.div_by_weight_ceil(weight);
2271 assert!(fee_rate.is_none());
2272
2273 let fee_rate = Amount::ONE_SAT.div_by_weight_ceil(Weight::ZERO);
2274 assert!(fee_rate.is_none());
2275 }
2276
2277 #[cfg(feature = "alloc")]
2278 #[test]
2279 fn amount_checked_div_by_weight_floor() {
2280 let weight = Weight::from_kwu(1).unwrap();
2281 let fee_rate = Amount::from_sat(1).div_by_weight_floor(weight).unwrap();
2282 assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
2284
2285 let weight = Weight::from_wu(381);
2286 let fee_rate = Amount::from_sat(329).div_by_weight_floor(weight).unwrap();
2287 assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
2290
2291 let fee_rate = Amount::MAX.div_by_weight_floor(weight);
2292 assert!(fee_rate.is_none());
2293
2294 let fee_rate = Amount::ONE_SAT.div_by_weight_floor(Weight::ZERO);
2295 assert!(fee_rate.is_none());
2296 }
2297
2298 #[test]
2299 #[cfg(not(debug_assertions))]
2300 fn unchecked_amount_add() {
2301 let amt = Amount::MAX.unchecked_add(Amount::ONE_SAT);
2302 assert_eq!(amt, Amount::ZERO);
2303 }
2304
2305 #[test]
2306 #[cfg(not(debug_assertions))]
2307 fn unchecked_signed_amount_add() {
2308 let signed_amt = SignedAmount::MAX.unchecked_add(SignedAmount::ONE_SAT);
2309 assert_eq!(signed_amt, SignedAmount::MIN);
2310 }
2311
2312 #[test]
2313 #[cfg(not(debug_assertions))]
2314 fn unchecked_amount_subtract() {
2315 let amt = Amount::ZERO.unchecked_sub(Amount::ONE_SAT);
2316 assert_eq!(amt, Amount::MAX);
2317 }
2318
2319 #[test]
2320 #[cfg(not(debug_assertions))]
2321 fn unchecked_signed_amount_subtract() {
2322 let signed_amt = SignedAmount::MIN.unchecked_sub(SignedAmount::ONE_SAT);
2323 assert_eq!(signed_amt, SignedAmount::MAX);
2324 }
2325
2326 #[cfg(feature = "alloc")]
2327 #[test]
2328 fn floating_point() {
2329 use super::Denomination as D;
2330 let f = Amount::from_float_in;
2331 let sf = SignedAmount::from_float_in;
2332 let sat = Amount::from_sat;
2333 let ssat = SignedAmount::from_sat;
2334
2335 assert_eq!(f(11.22, D::Bitcoin), Ok(sat(1122000000)));
2336 assert_eq!(sf(-11.22, D::MilliBitcoin), Ok(ssat(-1122000)));
2337 assert_eq!(f(11.22, D::Bit), Ok(sat(1122)));
2338 assert_eq!(sf(-1000.0, D::MilliSatoshi), Ok(ssat(-1)));
2339 assert_eq!(f(0.0001234, D::Bitcoin), Ok(sat(12340)));
2340 assert_eq!(sf(-0.00012345, D::Bitcoin), Ok(ssat(-12345)));
2341
2342 assert_eq!(f(-100.0, D::MilliSatoshi), Err(OutOfRangeError::negative().into()));
2343 assert_eq!(f(11.22, D::Satoshi), Err(TooPreciseError { position: 3 }.into()));
2344 assert_eq!(sf(-100.0, D::MilliSatoshi), Err(TooPreciseError { position: 1 }.into()));
2345 assert_eq!(f(42.123456781, D::Bitcoin), Err(TooPreciseError { position: 11 }.into()));
2346 assert_eq!(sf(-184467440738.0, D::Bitcoin), Err(OutOfRangeError::too_small().into()));
2347 assert_eq!(
2348 f(18446744073709551617.0, D::Satoshi),
2349 Err(OutOfRangeError::too_big(false).into())
2350 );
2351
2352 assert!(f(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi).is_ok());
2354
2355 assert_eq!(
2356 f(Amount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
2357 Err(OutOfRangeError::too_big(false).into())
2358 );
2359
2360 assert_eq!(
2361 sf(SignedAmount::MAX.to_float_in(D::Satoshi) + 1.0, D::Satoshi),
2362 Err(OutOfRangeError::too_big(true).into())
2363 );
2364
2365 let btc = move |f| SignedAmount::from_btc(f).unwrap();
2366 assert_eq!(btc(2.5).to_float_in(D::Bitcoin), 2.5);
2367 assert_eq!(btc(-2.5).to_float_in(D::MilliBitcoin), -2500.0);
2368 assert_eq!(btc(2.5).to_float_in(D::Satoshi), 250000000.0);
2369 assert_eq!(btc(-2.5).to_float_in(D::MilliSatoshi), -250000000000.0);
2370
2371 let btc = move |f| Amount::from_btc(f).unwrap();
2372 assert_eq!(&btc(0.0012).to_float_in(D::Bitcoin).to_string(), "0.0012")
2373 }
2374
2375 #[test]
2376 #[allow(clippy::inconsistent_digit_grouping)] fn parsing() {
2378 use super::ParseAmountError as E;
2379 let btc = Denomination::Bitcoin;
2380 let sat = Denomination::Satoshi;
2381 let msat = Denomination::MilliSatoshi;
2382 let p = Amount::from_str_in;
2383 let sp = SignedAmount::from_str_in;
2384
2385 assert_eq!(
2386 p("x", btc),
2387 Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 0 }))
2388 );
2389 assert_eq!(
2390 p("-", btc),
2391 Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
2392 );
2393 assert_eq!(
2394 sp("-", btc),
2395 Err(E::from(MissingDigitsError { kind: MissingDigitsKind::OnlyMinusSign }))
2396 );
2397 assert_eq!(
2398 p("-1.0x", btc),
2399 Err(E::from(InvalidCharacterError { invalid_char: 'x', position: 4 }))
2400 );
2401 assert_eq!(
2402 p("0.0 ", btc),
2403 Err(E::from(InvalidCharacterError { invalid_char: ' ', position: 3 }))
2404 );
2405 assert_eq!(
2406 p("0.000.000", btc),
2407 Err(E::from(InvalidCharacterError { invalid_char: '.', position: 5 }))
2408 );
2409 #[cfg(feature = "alloc")]
2410 let more_than_max = format!("1{}", Amount::MAX);
2411 #[cfg(feature = "alloc")]
2412 assert_eq!(p(&more_than_max, btc), Err(OutOfRangeError::too_big(false).into()));
2413 assert_eq!(p("0.000000042", btc), Err(TooPreciseError { position: 10 }.into()));
2414 assert_eq!(p("999.0000000", msat), Err(TooPreciseError { position: 0 }.into()));
2415 assert_eq!(p("1.0000000", msat), Err(TooPreciseError { position: 0 }.into()));
2416 assert_eq!(p("1.1", msat), Err(TooPreciseError { position: 0 }.into()));
2417 assert_eq!(p("1000.1", msat), Err(TooPreciseError { position: 5 }.into()));
2418 assert_eq!(p("1001.0000000", msat), Err(TooPreciseError { position: 3 }.into()));
2419 assert_eq!(p("1000.0000001", msat), Err(TooPreciseError { position: 11 }.into()));
2420 assert_eq!(p("1000.1000000", msat), Err(TooPreciseError { position: 5 }.into()));
2421 assert_eq!(p("1100.0000000", msat), Err(TooPreciseError { position: 1 }.into()));
2422 assert_eq!(p("10001.0000000", msat), Err(TooPreciseError { position: 4 }.into()));
2423
2424 assert_eq!(p("1", btc), Ok(Amount::from_sat(1_000_000_00)));
2425 assert_eq!(sp("-.5", btc), Ok(SignedAmount::from_sat(-500_000_00)));
2426 #[cfg(feature = "alloc")]
2427 assert_eq!(sp(&i64::MIN.to_string(), sat), Ok(SignedAmount::from_sat(i64::MIN)));
2428 assert_eq!(p("1.1", btc), Ok(Amount::from_sat(1_100_000_00)));
2429 assert_eq!(p("100", sat), Ok(Amount::from_sat(100)));
2430 assert_eq!(p("55", sat), Ok(Amount::from_sat(55)));
2431 assert_eq!(p("5500000000000000000", sat), Ok(Amount::from_sat(55_000_000_000_000_000_00)));
2432 assert_eq!(p("5500000000000000000.", sat), Ok(Amount::from_sat(55_000_000_000_000_000_00)));
2434 assert_eq!(
2435 p("12345678901.12345678", btc),
2436 Ok(Amount::from_sat(12_345_678_901__123_456_78))
2437 );
2438 assert_eq!(p("1000.0", msat), Ok(Amount::from_sat(1)));
2439 assert_eq!(p("1000.000000000000000000000000000", msat), Ok(Amount::from_sat(1)));
2440
2441 #[cfg(feature = "alloc")]
2443 {
2444 let amount = Amount::from_sat(i64::MAX as u64);
2445 assert_eq!(Amount::from_str_in(&amount.to_string_in(sat), sat), Ok(amount));
2446 assert!(
2447 SignedAmount::from_str_in(&(amount + Amount(1)).to_string_in(sat), sat).is_err()
2448 );
2449 assert!(Amount::from_str_in(&(amount + Amount(1)).to_string_in(sat), sat).is_ok());
2450 }
2451
2452 assert_eq!(
2453 p("12.000", Denomination::MilliSatoshi),
2454 Err(TooPreciseError { position: 0 }.into())
2455 );
2456 assert_eq!(
2458 p("100000000000000.0000000000000000000000000000000000", Denomination::Bitcoin),
2459 Err(OutOfRangeError::too_big(false).into())
2460 );
2461 assert_eq!(
2463 p("100000000000000.00000000000000000000000000000000000", Denomination::Bitcoin),
2464 Err(E::InputTooLarge(InputTooLargeError { len: 51 }))
2465 );
2466 }
2467
2468 #[test]
2469 #[cfg(feature = "alloc")]
2470 fn to_string() {
2471 use super::Denomination as D;
2472
2473 assert_eq!(Amount::ONE_BTC.to_string_in(D::Bitcoin), "1");
2474 assert_eq!(format!("{:.8}", Amount::ONE_BTC.display_in(D::Bitcoin)), "1.00000000");
2475 assert_eq!(Amount::ONE_BTC.to_string_in(D::Satoshi), "100000000");
2476 assert_eq!(Amount::ONE_SAT.to_string_in(D::Bitcoin), "0.00000001");
2477 assert_eq!(SignedAmount::from_sat(-42).to_string_in(D::Bitcoin), "-0.00000042");
2478
2479 assert_eq!(Amount::ONE_BTC.to_string_with_denomination(D::Bitcoin), "1 BTC");
2480 assert_eq!(Amount::ONE_SAT.to_string_with_denomination(D::MilliSatoshi), "1000 msat");
2481 assert_eq!(
2482 SignedAmount::ONE_BTC.to_string_with_denomination(D::Satoshi),
2483 "100000000 satoshi"
2484 );
2485 assert_eq!(Amount::ONE_SAT.to_string_with_denomination(D::Bitcoin), "0.00000001 BTC");
2486 assert_eq!(
2487 SignedAmount::from_sat(-42).to_string_with_denomination(D::Bitcoin),
2488 "-0.00000042 BTC"
2489 );
2490 }
2491
2492 #[cfg(feature = "alloc")]
2494 #[test]
2495 fn test_repeat_char() {
2496 let mut buf = String::new();
2497 repeat_char(&mut buf, '0', 0).unwrap();
2498 assert_eq!(buf.len(), 0);
2499 repeat_char(&mut buf, '0', 42).unwrap();
2500 assert_eq!(buf.len(), 42);
2501 assert!(buf.chars().all(|c| c == '0'));
2502 }
2503
2504 macro_rules! check_format_non_negative {
2506 ($denom:ident; $($test_name:ident, $val:literal, $format_string:literal, $expected:literal);* $(;)?) => {
2507 $(
2508 #[test]
2509 #[cfg(feature = "alloc")]
2510 fn $test_name() {
2511 assert_eq!(format!($format_string, Amount::from_sat($val).display_in(Denomination::$denom)), $expected);
2512 assert_eq!(format!($format_string, SignedAmount::from_sat($val as i64).display_in(Denomination::$denom)), $expected);
2513 }
2514 )*
2515 }
2516 }
2517
2518 macro_rules! check_format_non_negative_show_denom {
2519 ($denom:ident, $denom_suffix:literal; $($test_name:ident, $val:literal, $format_string:literal, $expected:literal);* $(;)?) => {
2520 $(
2521 #[test]
2522 #[cfg(feature = "alloc")]
2523 fn $test_name() {
2524 assert_eq!(format!($format_string, Amount::from_sat($val).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
2525 assert_eq!(format!($format_string, SignedAmount::from_sat($val as i64).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
2526 }
2527 )*
2528 }
2529 }
2530
2531 check_format_non_negative! {
2532 Satoshi;
2533 sat_check_fmt_non_negative_0, 0, "{}", "0";
2534 sat_check_fmt_non_negative_1, 0, "{:2}", " 0";
2535 sat_check_fmt_non_negative_2, 0, "{:02}", "00";
2536 sat_check_fmt_non_negative_3, 0, "{:.1}", "0.0";
2537 sat_check_fmt_non_negative_4, 0, "{:4.1}", " 0.0";
2538 sat_check_fmt_non_negative_5, 0, "{:04.1}", "00.0";
2539 sat_check_fmt_non_negative_6, 1, "{}", "1";
2540 sat_check_fmt_non_negative_7, 1, "{:2}", " 1";
2541 sat_check_fmt_non_negative_8, 1, "{:02}", "01";
2542 sat_check_fmt_non_negative_9, 1, "{:.1}", "1.0";
2543 sat_check_fmt_non_negative_10, 1, "{:4.1}", " 1.0";
2544 sat_check_fmt_non_negative_11, 1, "{:04.1}", "01.0";
2545 sat_check_fmt_non_negative_12, 10, "{}", "10";
2546 sat_check_fmt_non_negative_13, 10, "{:2}", "10";
2547 sat_check_fmt_non_negative_14, 10, "{:02}", "10";
2548 sat_check_fmt_non_negative_15, 10, "{:3}", " 10";
2549 sat_check_fmt_non_negative_16, 10, "{:03}", "010";
2550 sat_check_fmt_non_negative_17, 10, "{:.1}", "10.0";
2551 sat_check_fmt_non_negative_18, 10, "{:5.1}", " 10.0";
2552 sat_check_fmt_non_negative_19, 10, "{:05.1}", "010.0";
2553 sat_check_fmt_non_negative_20, 1, "{:<2}", "1 ";
2554 sat_check_fmt_non_negative_21, 1, "{:<02}", "01";
2555 sat_check_fmt_non_negative_22, 1, "{:<3.1}", "1.0";
2556 sat_check_fmt_non_negative_23, 1, "{:<4.1}", "1.0 ";
2557 }
2558
2559 check_format_non_negative_show_denom! {
2560 Satoshi, " satoshi";
2561 sat_check_fmt_non_negative_show_denom_0, 0, "{}", "0";
2562 sat_check_fmt_non_negative_show_denom_1, 0, "{:2}", "0";
2563 sat_check_fmt_non_negative_show_denom_2, 0, "{:02}", "0";
2564 sat_check_fmt_non_negative_show_denom_3, 0, "{:9}", "0";
2565 sat_check_fmt_non_negative_show_denom_4, 0, "{:09}", "0";
2566 sat_check_fmt_non_negative_show_denom_5, 0, "{:10}", " 0";
2567 sat_check_fmt_non_negative_show_denom_6, 0, "{:010}", "00";
2568 sat_check_fmt_non_negative_show_denom_7, 0, "{:.1}", "0.0";
2569 sat_check_fmt_non_negative_show_denom_8, 0, "{:11.1}", "0.0";
2570 sat_check_fmt_non_negative_show_denom_9, 0, "{:011.1}", "0.0";
2571 sat_check_fmt_non_negative_show_denom_10, 0, "{:12.1}", " 0.0";
2572 sat_check_fmt_non_negative_show_denom_11, 0, "{:012.1}", "00.0";
2573 sat_check_fmt_non_negative_show_denom_12, 1, "{}", "1";
2574 sat_check_fmt_non_negative_show_denom_13, 1, "{:10}", " 1";
2575 sat_check_fmt_non_negative_show_denom_14, 1, "{:010}", "01";
2576 sat_check_fmt_non_negative_show_denom_15, 1, "{:.1}", "1.0";
2577 sat_check_fmt_non_negative_show_denom_16, 1, "{:12.1}", " 1.0";
2578 sat_check_fmt_non_negative_show_denom_17, 1, "{:012.1}", "01.0";
2579 sat_check_fmt_non_negative_show_denom_18, 10, "{}", "10";
2580 sat_check_fmt_non_negative_show_denom_19, 10, "{:10}", "10";
2581 sat_check_fmt_non_negative_show_denom_20, 10, "{:010}", "10";
2582 sat_check_fmt_non_negative_show_denom_21, 10, "{:11}", " 10";
2583 sat_check_fmt_non_negative_show_denom_22, 10, "{:011}", "010";
2584 }
2585
2586 check_format_non_negative! {
2587 Bitcoin;
2588 btc_check_fmt_non_negative_0, 0, "{}", "0";
2589 btc_check_fmt_non_negative_1, 0, "{:2}", " 0";
2590 btc_check_fmt_non_negative_2, 0, "{:02}", "00";
2591 btc_check_fmt_non_negative_3, 0, "{:.1}", "0.0";
2592 btc_check_fmt_non_negative_4, 0, "{:4.1}", " 0.0";
2593 btc_check_fmt_non_negative_5, 0, "{:04.1}", "00.0";
2594 btc_check_fmt_non_negative_6, 1, "{}", "0.00000001";
2595 btc_check_fmt_non_negative_7, 1, "{:2}", "0.00000001";
2596 btc_check_fmt_non_negative_8, 1, "{:02}", "0.00000001";
2597 btc_check_fmt_non_negative_9, 1, "{:.1}", "0.00000001";
2598 btc_check_fmt_non_negative_10, 1, "{:11}", " 0.00000001";
2599 btc_check_fmt_non_negative_11, 1, "{:11.1}", " 0.00000001";
2600 btc_check_fmt_non_negative_12, 1, "{:011.1}", "00.00000001";
2601 btc_check_fmt_non_negative_13, 1, "{:.9}", "0.000000010";
2602 btc_check_fmt_non_negative_14, 1, "{:11.9}", "0.000000010";
2603 btc_check_fmt_non_negative_15, 1, "{:011.9}", "0.000000010";
2604 btc_check_fmt_non_negative_16, 1, "{:12.9}", " 0.000000010";
2605 btc_check_fmt_non_negative_17, 1, "{:012.9}", "00.000000010";
2606 btc_check_fmt_non_negative_18, 100_000_000, "{}", "1";
2607 btc_check_fmt_non_negative_19, 100_000_000, "{:2}", " 1";
2608 btc_check_fmt_non_negative_20, 100_000_000, "{:02}", "01";
2609 btc_check_fmt_non_negative_21, 100_000_000, "{:.1}", "1.0";
2610 btc_check_fmt_non_negative_22, 100_000_000, "{:4.1}", " 1.0";
2611 btc_check_fmt_non_negative_23, 100_000_000, "{:04.1}", "01.0";
2612 btc_check_fmt_non_negative_24, 110_000_000, "{}", "1.1";
2613 btc_check_fmt_non_negative_25, 100_000_001, "{}", "1.00000001";
2614 btc_check_fmt_non_negative_26, 100_000_001, "{:1}", "1.00000001";
2615 btc_check_fmt_non_negative_27, 100_000_001, "{:.1}", "1.00000001";
2616 btc_check_fmt_non_negative_28, 100_000_001, "{:10}", "1.00000001";
2617 btc_check_fmt_non_negative_29, 100_000_001, "{:11}", " 1.00000001";
2618 btc_check_fmt_non_negative_30, 100_000_001, "{:011}", "01.00000001";
2619 btc_check_fmt_non_negative_31, 100_000_001, "{:.8}", "1.00000001";
2620 btc_check_fmt_non_negative_32, 100_000_001, "{:.9}", "1.000000010";
2621 btc_check_fmt_non_negative_33, 100_000_001, "{:11.9}", "1.000000010";
2622 btc_check_fmt_non_negative_34, 100_000_001, "{:12.9}", " 1.000000010";
2623 btc_check_fmt_non_negative_35, 100_000_001, "{:012.9}", "01.000000010";
2624 btc_check_fmt_non_negative_36, 100_000_001, "{:+011.8}", "+1.00000001";
2625 btc_check_fmt_non_negative_37, 100_000_001, "{:+12.8}", " +1.00000001";
2626 btc_check_fmt_non_negative_38, 100_000_001, "{:+012.8}", "+01.00000001";
2627 btc_check_fmt_non_negative_39, 100_000_001, "{:+12.9}", "+1.000000010";
2628 btc_check_fmt_non_negative_40, 100_000_001, "{:+012.9}", "+1.000000010";
2629 btc_check_fmt_non_negative_41, 100_000_001, "{:+13.9}", " +1.000000010";
2630 btc_check_fmt_non_negative_42, 100_000_001, "{:+013.9}", "+01.000000010";
2631 btc_check_fmt_non_negative_43, 100_000_001, "{:<10}", "1.00000001";
2632 btc_check_fmt_non_negative_44, 100_000_001, "{:<11}", "1.00000001 ";
2633 btc_check_fmt_non_negative_45, 100_000_001, "{:<011}", "01.00000001";
2634 btc_check_fmt_non_negative_46, 100_000_001, "{:<11.9}", "1.000000010";
2635 btc_check_fmt_non_negative_47, 100_000_001, "{:<12.9}", "1.000000010 ";
2636 btc_check_fmt_non_negative_48, 100_000_001, "{:<12}", "1.00000001 ";
2637 btc_check_fmt_non_negative_49, 100_000_001, "{:^11}", "1.00000001 ";
2638 btc_check_fmt_non_negative_50, 100_000_001, "{:^11.9}", "1.000000010";
2639 btc_check_fmt_non_negative_51, 100_000_001, "{:^12.9}", "1.000000010 ";
2640 btc_check_fmt_non_negative_52, 100_000_001, "{:^12}", " 1.00000001 ";
2641 btc_check_fmt_non_negative_53, 100_000_001, "{:^12.9}", "1.000000010 ";
2642 btc_check_fmt_non_negative_54, 100_000_001, "{:^13.9}", " 1.000000010 ";
2643 }
2644
2645 check_format_non_negative_show_denom! {
2646 Bitcoin, " BTC";
2647 btc_check_fmt_non_negative_show_denom_0, 1, "{:14.1}", "0.00000001";
2648 btc_check_fmt_non_negative_show_denom_1, 1, "{:14.8}", "0.00000001";
2649 btc_check_fmt_non_negative_show_denom_2, 1, "{:15}", " 0.00000001";
2650 btc_check_fmt_non_negative_show_denom_3, 1, "{:015}", "00.00000001";
2651 btc_check_fmt_non_negative_show_denom_4, 1, "{:.9}", "0.000000010";
2652 btc_check_fmt_non_negative_show_denom_5, 1, "{:15.9}", "0.000000010";
2653 btc_check_fmt_non_negative_show_denom_6, 1, "{:16.9}", " 0.000000010";
2654 btc_check_fmt_non_negative_show_denom_7, 1, "{:016.9}", "00.000000010";
2655 }
2656
2657 check_format_non_negative_show_denom! {
2658 Bitcoin, " BTC ";
2659 btc_check_fmt_non_negative_show_denom_align_0, 1, "{:<15}", "0.00000001";
2660 btc_check_fmt_non_negative_show_denom_align_1, 1, "{:^15}", "0.00000001";
2661 btc_check_fmt_non_negative_show_denom_align_2, 1, "{:^16}", " 0.00000001";
2662 }
2663
2664 check_format_non_negative! {
2665 MilliSatoshi;
2666 msat_check_fmt_non_negative_0, 0, "{}", "0";
2667 msat_check_fmt_non_negative_1, 1, "{}", "1000";
2668 msat_check_fmt_non_negative_2, 1, "{:5}", " 1000";
2669 msat_check_fmt_non_negative_3, 1, "{:05}", "01000";
2670 msat_check_fmt_non_negative_4, 1, "{:.1}", "1000.0";
2671 msat_check_fmt_non_negative_5, 1, "{:6.1}", "1000.0";
2672 msat_check_fmt_non_negative_6, 1, "{:06.1}", "1000.0";
2673 msat_check_fmt_non_negative_7, 1, "{:7.1}", " 1000.0";
2674 msat_check_fmt_non_negative_8, 1, "{:07.1}", "01000.0";
2675 }
2676
2677 #[test]
2678 fn test_unsigned_signed_conversion() {
2679 let sa = SignedAmount::from_sat;
2680 let ua = Amount::from_sat;
2681
2682 assert_eq!(Amount::MAX.to_signed(), Err(OutOfRangeError::too_big(true)));
2683 assert_eq!(ua(i64::MAX as u64).to_signed(), Ok(sa(i64::MAX)));
2684 assert_eq!(ua(i64::MAX as u64 + 1).to_signed(), Err(OutOfRangeError::too_big(true)));
2685
2686 assert_eq!(sa(i64::MAX).to_unsigned(), Ok(ua(i64::MAX as u64)));
2687
2688 assert_eq!(sa(i64::MAX).to_unsigned().unwrap().to_signed(), Ok(sa(i64::MAX)));
2689 }
2690
2691 #[test]
2692 #[allow(clippy::inconsistent_digit_grouping)] fn from_str() {
2694 use ParseDenominationError::*;
2695
2696 use super::ParseAmountError as E;
2697
2698 assert_eq!(
2699 Amount::from_str("x BTC"),
2700 Err(InvalidCharacterError { invalid_char: 'x', position: 0 }.into())
2701 );
2702 assert_eq!(
2703 Amount::from_str("xBTC"),
2704 Err(Unknown(UnknownDenominationError("xBTC".into())).into()),
2705 );
2706 assert_eq!(
2707 Amount::from_str("5 BTC BTC"),
2708 Err(Unknown(UnknownDenominationError("BTC BTC".into())).into()),
2709 );
2710 assert_eq!(
2711 Amount::from_str("5BTC BTC"),
2712 Err(E::from(InvalidCharacterError { invalid_char: 'B', position: 1 }).into())
2713 );
2714 assert_eq!(
2715 Amount::from_str("5 5 BTC"),
2716 Err(Unknown(UnknownDenominationError("5 BTC".into())).into()),
2717 );
2718
2719 #[track_caller]
2720 fn ok_case(s: &str, expected: Amount) {
2721 assert_eq!(Amount::from_str(s).unwrap(), expected);
2722 assert_eq!(Amount::from_str(&s.replace(' ', "")).unwrap(), expected);
2723 }
2724
2725 #[track_caller]
2726 fn case(s: &str, expected: Result<Amount, impl Into<ParseError>>) {
2727 let expected = expected.map_err(Into::into);
2728 assert_eq!(Amount::from_str(s), expected);
2729 assert_eq!(Amount::from_str(&s.replace(' ', "")), expected);
2730 }
2731
2732 #[track_caller]
2733 fn ok_scase(s: &str, expected: SignedAmount) {
2734 assert_eq!(SignedAmount::from_str(s).unwrap(), expected);
2735 assert_eq!(SignedAmount::from_str(&s.replace(' ', "")).unwrap(), expected);
2736 }
2737
2738 #[track_caller]
2739 fn scase(s: &str, expected: Result<SignedAmount, impl Into<ParseError>>) {
2740 let expected = expected.map_err(Into::into);
2741 assert_eq!(SignedAmount::from_str(s), expected);
2742 assert_eq!(SignedAmount::from_str(&s.replace(' ', "")), expected);
2743 }
2744
2745 case("5 BCH", Err(Unknown(UnknownDenominationError("BCH".into()))));
2746
2747 case("-1 BTC", Err(OutOfRangeError::negative()));
2748 case("-0.0 BTC", Err(OutOfRangeError::negative()));
2749 case("0.123456789 BTC", Err(TooPreciseError { position: 10 }));
2750 scase("-0.1 satoshi", Err(TooPreciseError { position: 3 }));
2751 case("0.123456 mBTC", Err(TooPreciseError { position: 7 }));
2752 scase("-1.001 bits", Err(TooPreciseError { position: 5 }));
2753 scase("-200000000000 BTC", Err(OutOfRangeError::too_small()));
2754 case("18446744073709551616 sat", Err(OutOfRangeError::too_big(false)));
2755
2756 ok_case(".5 bits", Amount::from_sat(50));
2757 ok_scase("-.5 bits", SignedAmount::from_sat(-50));
2758 ok_case("0.00253583 BTC", Amount::from_sat(253583));
2759 ok_scase("-5 satoshi", SignedAmount::from_sat(-5));
2760 ok_case("0.10000000 BTC", Amount::from_sat(100_000_00));
2761 ok_scase("-100 bits", SignedAmount::from_sat(-10_000));
2762 #[cfg(feature = "alloc")]
2763 ok_scase(&format!("{} SAT", i64::MIN), SignedAmount::from_sat(i64::MIN));
2764 }
2765
2766 #[cfg(feature = "alloc")]
2767 #[test]
2768 #[allow(clippy::inconsistent_digit_grouping)] fn to_from_string_in() {
2770 use super::Denomination as D;
2771 let ua_str = Amount::from_str_in;
2772 let ua_sat = Amount::from_sat;
2773 let sa_str = SignedAmount::from_str_in;
2774 let sa_sat = SignedAmount::from_sat;
2775
2776 assert_eq!("0.5", Amount::from_sat(50).to_string_in(D::Bit));
2777 assert_eq!("-0.5", SignedAmount::from_sat(-50).to_string_in(D::Bit));
2778 assert_eq!("0.00253583", Amount::from_sat(253583).to_string_in(D::Bitcoin));
2779 assert_eq!("-5", SignedAmount::from_sat(-5).to_string_in(D::Satoshi));
2780 assert_eq!("0.1", Amount::from_sat(100_000_00).to_string_in(D::Bitcoin));
2781 assert_eq!("-100", SignedAmount::from_sat(-10_000).to_string_in(D::Bit));
2782 assert_eq!("2535830", Amount::from_sat(253583).to_string_in(D::NanoBitcoin));
2783 assert_eq!("-100000", SignedAmount::from_sat(-10_000).to_string_in(D::NanoBitcoin));
2784 assert_eq!("2535830000", Amount::from_sat(253583).to_string_in(D::PicoBitcoin));
2785 assert_eq!("-100000000", SignedAmount::from_sat(-10_000).to_string_in(D::PicoBitcoin));
2786
2787 assert_eq!("0.50", format!("{:.2}", Amount::from_sat(50).display_in(D::Bit)));
2788 assert_eq!("-0.50", format!("{:.2}", SignedAmount::from_sat(-50).display_in(D::Bit)));
2789 assert_eq!(
2790 "0.10000000",
2791 format!("{:.8}", Amount::from_sat(100_000_00).display_in(D::Bitcoin))
2792 );
2793 assert_eq!("-100.00", format!("{:.2}", SignedAmount::from_sat(-10_000).display_in(D::Bit)));
2794
2795 assert_eq!(ua_str(&ua_sat(0).to_string_in(D::Satoshi), D::Satoshi), Ok(ua_sat(0)));
2796 assert_eq!(ua_str(&ua_sat(500).to_string_in(D::Bitcoin), D::Bitcoin), Ok(ua_sat(500)));
2797 assert_eq!(
2798 ua_str(&ua_sat(21_000_000).to_string_in(D::Bit), D::Bit),
2799 Ok(ua_sat(21_000_000))
2800 );
2801 assert_eq!(
2802 ua_str(&ua_sat(1).to_string_in(D::MicroBitcoin), D::MicroBitcoin),
2803 Ok(ua_sat(1))
2804 );
2805 assert_eq!(
2806 ua_str(&ua_sat(1_000_000_000_000).to_string_in(D::MilliBitcoin), D::MilliBitcoin),
2807 Ok(ua_sat(1_000_000_000_000))
2808 );
2809 assert!(ua_str(&ua_sat(u64::MAX).to_string_in(D::MilliBitcoin), D::MilliBitcoin).is_ok());
2810
2811 assert_eq!(
2812 sa_str(&sa_sat(-1).to_string_in(D::MicroBitcoin), D::MicroBitcoin),
2813 Ok(sa_sat(-1))
2814 );
2815
2816 assert_eq!(
2817 sa_str(&sa_sat(i64::MAX).to_string_in(D::Satoshi), D::MicroBitcoin),
2818 Err(OutOfRangeError::too_big(true).into())
2819 );
2820 assert_eq!(
2822 sa_str(&sa_sat(i64::MIN).to_string_in(D::Satoshi), D::MicroBitcoin),
2823 Err(OutOfRangeError::too_small().into())
2824 );
2825
2826 assert_eq!(
2827 sa_str(&sa_sat(-1).to_string_in(D::NanoBitcoin), D::NanoBitcoin),
2828 Ok(sa_sat(-1))
2829 );
2830 assert_eq!(
2831 sa_str(&sa_sat(i64::MAX).to_string_in(D::Satoshi), D::NanoBitcoin),
2832 Err(TooPreciseError { position: 18 }.into())
2833 );
2834 assert_eq!(
2835 sa_str(&sa_sat(i64::MIN).to_string_in(D::Satoshi), D::NanoBitcoin),
2836 Err(TooPreciseError { position: 19 }.into())
2837 );
2838
2839 assert_eq!(
2840 sa_str(&sa_sat(-1).to_string_in(D::PicoBitcoin), D::PicoBitcoin),
2841 Ok(sa_sat(-1))
2842 );
2843 assert_eq!(
2844 sa_str(&sa_sat(i64::MAX).to_string_in(D::Satoshi), D::PicoBitcoin),
2845 Err(TooPreciseError { position: 18 }.into())
2846 );
2847 assert_eq!(
2848 sa_str(&sa_sat(i64::MIN).to_string_in(D::Satoshi), D::PicoBitcoin),
2849 Err(TooPreciseError { position: 19 }.into())
2850 );
2851 }
2852
2853 #[cfg(feature = "alloc")]
2854 #[test]
2855 fn to_string_with_denomination_from_str_roundtrip() {
2856 use ParseDenominationError::*;
2857
2858 use super::Denomination as D;
2859
2860 let amt = Amount::from_sat(42);
2861 let denom = Amount::to_string_with_denomination;
2862 assert_eq!(Amount::from_str(&denom(amt, D::Bitcoin)), Ok(amt));
2863 assert_eq!(Amount::from_str(&denom(amt, D::MilliBitcoin)), Ok(amt));
2864 assert_eq!(Amount::from_str(&denom(amt, D::MicroBitcoin)), Ok(amt));
2865 assert_eq!(Amount::from_str(&denom(amt, D::Bit)), Ok(amt));
2866 assert_eq!(Amount::from_str(&denom(amt, D::Satoshi)), Ok(amt));
2867 assert_eq!(Amount::from_str(&denom(amt, D::NanoBitcoin)), Ok(amt));
2868 assert_eq!(Amount::from_str(&denom(amt, D::MilliSatoshi)), Ok(amt));
2869 assert_eq!(Amount::from_str(&denom(amt, D::PicoBitcoin)), Ok(amt));
2870
2871 assert_eq!(
2872 Amount::from_str("42 satoshi BTC"),
2873 Err(Unknown(UnknownDenominationError("satoshi BTC".into())).into()),
2874 );
2875 assert_eq!(
2876 SignedAmount::from_str("-42 satoshi BTC"),
2877 Err(Unknown(UnknownDenominationError("satoshi BTC".into())).into()),
2878 );
2879 }
2880
2881 #[cfg(feature = "serde")]
2882 #[test]
2883 fn serde_as_sat() {
2884 #[derive(Serialize, Deserialize, PartialEq, Debug)]
2885 struct T {
2886 #[serde(with = "crate::amount::serde::as_sat")]
2887 pub amt: Amount,
2888 #[serde(with = "crate::amount::serde::as_sat")]
2889 pub samt: SignedAmount,
2890 }
2891
2892 serde_test::assert_tokens(
2893 &T { amt: Amount::from_sat(123456789), samt: SignedAmount::from_sat(-123456789) },
2894 &[
2895 serde_test::Token::Struct { name: "T", len: 2 },
2896 serde_test::Token::Str("amt"),
2897 serde_test::Token::U64(123456789),
2898 serde_test::Token::Str("samt"),
2899 serde_test::Token::I64(-123456789),
2900 serde_test::Token::StructEnd,
2901 ],
2902 );
2903 }
2904
2905 #[cfg(feature = "serde")]
2906 #[cfg(feature = "alloc")]
2907 #[test]
2908 #[allow(clippy::inconsistent_digit_grouping)] fn serde_as_btc() {
2910 use serde_json;
2911
2912 #[derive(Serialize, Deserialize, PartialEq, Debug)]
2913 struct T {
2914 #[serde(with = "crate::amount::serde::as_btc")]
2915 pub amt: Amount,
2916 #[serde(with = "crate::amount::serde::as_btc")]
2917 pub samt: SignedAmount,
2918 }
2919
2920 let orig = T {
2921 amt: Amount::from_sat(21_000_000__000_000_01),
2922 samt: SignedAmount::from_sat(-21_000_000__000_000_01),
2923 };
2924
2925 let json = "{\"amt\": 21000000.00000001, \
2926 \"samt\": -21000000.00000001}";
2927 let t: T = serde_json::from_str(json).unwrap();
2928 assert_eq!(t, orig);
2929
2930 let value: serde_json::Value = serde_json::from_str(json).unwrap();
2931 assert_eq!(t, serde_json::from_value(value).unwrap());
2932
2933 let t: Result<T, serde_json::Error> =
2935 serde_json::from_str("{\"amt\": 1000000.000000001, \"samt\": 1}");
2936 assert!(t
2937 .unwrap_err()
2938 .to_string()
2939 .contains(&ParseAmountError::TooPrecise(TooPreciseError { position: 16 }).to_string()));
2940 let t: Result<T, serde_json::Error> = serde_json::from_str("{\"amt\": -1, \"samt\": 1}");
2941 assert!(t.unwrap_err().to_string().contains(&OutOfRangeError::negative().to_string()));
2942 }
2943
2944 #[cfg(feature = "serde")]
2945 #[cfg(feature = "alloc")]
2946 #[test]
2947 #[allow(clippy::inconsistent_digit_grouping)] fn serde_as_btc_opt() {
2949 use serde_json;
2950
2951 #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
2952 struct T {
2953 #[serde(default, with = "crate::amount::serde::as_btc::opt")]
2954 pub amt: Option<Amount>,
2955 #[serde(default, with = "crate::amount::serde::as_btc::opt")]
2956 pub samt: Option<SignedAmount>,
2957 }
2958
2959 let with = T {
2960 amt: Some(Amount::from_sat(2_500_000_00)),
2961 samt: Some(SignedAmount::from_sat(-2_500_000_00)),
2962 };
2963 let without = T { amt: None, samt: None };
2964
2965 for s in [&with, &without].iter() {
2967 let v = serde_json::to_string(s).unwrap();
2968 let w: T = serde_json::from_str(&v).unwrap();
2969 assert_eq!(w, **s);
2970 }
2971
2972 let t: T = serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap();
2973 assert_eq!(t, with);
2974
2975 let t: T = serde_json::from_str("{}").unwrap();
2976 assert_eq!(t, without);
2977
2978 let value_with: serde_json::Value =
2979 serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap();
2980 assert_eq!(with, serde_json::from_value(value_with).unwrap());
2981
2982 let value_without: serde_json::Value = serde_json::from_str("{}").unwrap();
2983 assert_eq!(without, serde_json::from_value(value_without).unwrap());
2984 }
2985
2986 #[cfg(feature = "serde")]
2987 #[cfg(feature = "alloc")]
2988 #[test]
2989 #[allow(clippy::inconsistent_digit_grouping)] fn serde_as_sat_opt() {
2991 use serde_json;
2992
2993 #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
2994 struct T {
2995 #[serde(default, with = "crate::amount::serde::as_sat::opt")]
2996 pub amt: Option<Amount>,
2997 #[serde(default, with = "crate::amount::serde::as_sat::opt")]
2998 pub samt: Option<SignedAmount>,
2999 }
3000
3001 let with = T {
3002 amt: Some(Amount::from_sat(2_500_000_00)),
3003 samt: Some(SignedAmount::from_sat(-2_500_000_00)),
3004 };
3005 let without = T { amt: None, samt: None };
3006
3007 for s in [&with, &without].iter() {
3009 let v = serde_json::to_string(s).unwrap();
3010 let w: T = serde_json::from_str(&v).unwrap();
3011 assert_eq!(w, **s);
3012 }
3013
3014 let t: T = serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap();
3015 assert_eq!(t, with);
3016
3017 let t: T = serde_json::from_str("{}").unwrap();
3018 assert_eq!(t, without);
3019
3020 let value_with: serde_json::Value =
3021 serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap();
3022 assert_eq!(with, serde_json::from_value(value_with).unwrap());
3023
3024 let value_without: serde_json::Value = serde_json::from_str("{}").unwrap();
3025 assert_eq!(without, serde_json::from_value(value_without).unwrap());
3026 }
3027
3028 #[test]
3029 fn sum_amounts() {
3030 assert_eq!(Amount::from_sat(0), [].into_iter().sum::<Amount>());
3031 assert_eq!(SignedAmount::from_sat(0), [].into_iter().sum::<SignedAmount>());
3032
3033 let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
3034 let sum = amounts.into_iter().sum::<Amount>();
3035 assert_eq!(Amount::from_sat(1400), sum);
3036
3037 let amounts =
3038 [SignedAmount::from_sat(-42), SignedAmount::from_sat(1337), SignedAmount::from_sat(21)];
3039 let sum = amounts.into_iter().sum::<SignedAmount>();
3040 assert_eq!(SignedAmount::from_sat(1316), sum);
3041 }
3042
3043 #[test]
3044 fn checked_sum_amounts() {
3045 assert_eq!(Some(Amount::from_sat(0)), [].into_iter().checked_sum());
3046 assert_eq!(Some(SignedAmount::from_sat(0)), [].into_iter().checked_sum());
3047
3048 let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
3049 let sum = amounts.into_iter().checked_sum();
3050 assert_eq!(Some(Amount::from_sat(1400)), sum);
3051
3052 let amounts = [Amount::from_sat(u64::MAX), Amount::from_sat(1337), Amount::from_sat(21)];
3053 let sum = amounts.into_iter().checked_sum();
3054 assert_eq!(None, sum);
3055
3056 let amounts = [
3057 SignedAmount::from_sat(i64::MIN),
3058 SignedAmount::from_sat(-1),
3059 SignedAmount::from_sat(21),
3060 ];
3061 let sum = amounts.into_iter().checked_sum();
3062 assert_eq!(None, sum);
3063
3064 let amounts = [
3065 SignedAmount::from_sat(i64::MAX),
3066 SignedAmount::from_sat(1),
3067 SignedAmount::from_sat(21),
3068 ];
3069 let sum = amounts.into_iter().checked_sum();
3070 assert_eq!(None, sum);
3071
3072 let amounts =
3073 [SignedAmount::from_sat(42), SignedAmount::from_sat(3301), SignedAmount::from_sat(21)];
3074 let sum = amounts.into_iter().checked_sum();
3075 assert_eq!(Some(SignedAmount::from_sat(3364)), sum);
3076 }
3077
3078 #[test]
3079 fn denomination_string_acceptable_forms() {
3080 let valid = [
3082 "BTC", "btc", "mBTC", "mbtc", "uBTC", "ubtc", "SATOSHI", "satoshi", "SATOSHIS",
3083 "satoshis", "SAT", "sat", "SATS", "sats", "bit", "bits", "nBTC", "pBTC",
3084 ];
3085 for denom in valid.iter() {
3086 assert!(Denomination::from_str(denom).is_ok());
3087 }
3088 }
3089
3090 #[test]
3091 fn disallow_confusing_forms() {
3092 let confusing = ["Msat", "Msats", "MSAT", "MSATS", "MSat", "MSats", "MBTC", "Mbtc", "PBTC"];
3093 for denom in confusing.iter() {
3094 match Denomination::from_str(denom) {
3095 Ok(_) => panic!("from_str should error for {}", denom),
3096 Err(ParseDenominationError::PossiblyConfusing(_)) => {}
3097 Err(e) => panic!("unexpected error: {}", e),
3098 }
3099 }
3100 }
3101
3102 #[test]
3103 fn disallow_unknown_denomination() {
3104 let unknown = ["NBTC", "UBTC", "ABC", "abc", "cBtC", "Sat", "Sats"];
3106 for denom in unknown.iter() {
3107 match Denomination::from_str(denom) {
3108 Ok(_) => panic!("from_str should error for {}", denom),
3109 Err(ParseDenominationError::Unknown(_)) => (),
3110 Err(e) => panic!("unexpected error: {}", e),
3111 }
3112 }
3113 }
3114
3115 #[test]
3116 #[cfg(feature = "alloc")]
3117 fn trailing_zeros_for_amount() {
3118 assert_eq!(format!("{}", Amount::ONE_SAT), "0.00000001 BTC");
3119 assert_eq!(format!("{}", Amount::ONE_BTC), "1 BTC");
3120 assert_eq!(format!("{}", Amount::from_sat(1)), "0.00000001 BTC");
3121 assert_eq!(format!("{}", Amount::from_sat(10)), "0.00000010 BTC");
3122 assert_eq!(format!("{:.2}", Amount::from_sat(10)), "0.0000001 BTC");
3123 assert_eq!(format!("{:.2}", Amount::from_sat(100)), "0.000001 BTC");
3124 assert_eq!(format!("{:.2}", Amount::from_sat(1000)), "0.00001 BTC");
3125 assert_eq!(format!("{:.2}", Amount::from_sat(10_000)), "0.0001 BTC");
3126 assert_eq!(format!("{:.2}", Amount::from_sat(100_000)), "0.001 BTC");
3127 assert_eq!(format!("{:.2}", Amount::from_sat(1_000_000)), "0.01 BTC");
3128 assert_eq!(format!("{:.2}", Amount::from_sat(10_000_000)), "0.10 BTC");
3129 assert_eq!(format!("{:.2}", Amount::from_sat(100_000_000)), "1.00 BTC");
3130 assert_eq!(format!("{}", Amount::from_sat(100_000_000)), "1 BTC");
3131 assert_eq!(format!("{}", Amount::from_sat(40_000_000_000)), "400 BTC");
3132 assert_eq!(format!("{:.10}", Amount::from_sat(100_000_000)), "1.0000000000 BTC");
3133 assert_eq!(format!("{}", Amount::from_sat(400_000_000_000_010)), "4000000.00000010 BTC");
3134 assert_eq!(format!("{}", Amount::from_sat(400_000_000_000_000)), "4000000 BTC");
3135 }
3136}