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