1pub mod error;
9mod result;
10#[cfg(feature = "serde")]
11pub mod serde;
12
13mod signed;
14#[cfg(test)]
15mod tests;
16mod unsigned;
17#[cfg(kani)]
18mod verification;
19
20use core::cmp::Ordering;
21use core::convert::Infallible;
22use core::fmt;
23use core::str::FromStr;
24
25#[cfg(feature = "arbitrary")]
26use arbitrary::{Arbitrary, Unstructured};
27
28use self::error::{
29 BadPositionError, InputTooLargeError, InvalidCharacterError, MissingDenominationError,
30 MissingDigitsError, MissingDigitsKind, ParseAmountErrorInner, ParseErrorInner,
31 PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
32};
33
34#[rustfmt::skip] #[doc(inline)]
36pub use self::{
37 signed::SignedAmount,
38 unsigned::Amount,
39};
40#[cfg(feature = "encoding")]
41#[doc(no_inline)]
42pub use self::error::AmountDecoderError;
43#[doc(no_inline)]
44pub use self::error::{OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError};
45#[doc(inline)]
46#[cfg(feature = "encoding")]
47pub use self::unsigned::{AmountDecoder, AmountEncoder};
48
49#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
86#[non_exhaustive]
87#[allow(clippy::doc_markdown)]
88pub enum Denomination {
89 Bitcoin,
91 CentiBitcoin,
93 MilliBitcoin,
95 MicroBitcoin,
97 Bit,
99 Satoshi,
101 #[doc(hidden)]
104 _DoNotUse(Infallible),
105}
106
107impl Denomination {
108 pub const BTC: Self = Self::Bitcoin;
110
111 pub const SAT: Self = Self::Satoshi;
113
114 fn precision(self) -> i8 {
116 match self {
117 Self::Bitcoin => -8,
118 Self::CentiBitcoin => -6,
119 Self::MilliBitcoin => -5,
120 Self::MicroBitcoin => -2,
121 Self::Bit => -2,
122 Self::Satoshi => 0,
123 Self::_DoNotUse(infallible) => match infallible {},
124 }
125 }
126
127 fn as_str(self) -> &'static str {
129 match self {
130 Self::Bitcoin => "BTC",
131 Self::CentiBitcoin => "cBTC",
132 Self::MilliBitcoin => "mBTC",
133 Self::MicroBitcoin => "uBTC",
134 Self::Bit => "bits",
135 Self::Satoshi => "satoshi",
136 Self::_DoNotUse(infallible) => match infallible {},
137 }
138 }
139
140 fn forms(s: &str) -> Option<Self> {
142 match s {
143 "BTC" | "btc" => Some(Self::Bitcoin),
144 "cBTC" | "cbtc" => Some(Self::CentiBitcoin),
145 "mBTC" | "mbtc" => Some(Self::MilliBitcoin),
146 "uBTC" | "ubtc" | "µBTC" | "µbtc" => Some(Self::MicroBitcoin),
147 "bit" | "bits" | "BIT" | "BITS" => Some(Self::Bit),
148 "SATOSHI" | "satoshi" | "SATOSHIS" | "satoshis" | "SAT" | "sat" | "SATS" | "sats" =>
149 Some(Self::Satoshi),
150 _ => None,
151 }
152 }
153}
154
155const CONFUSING_FORMS: [&str; 6] = ["CBTC", "Cbtc", "MBTC", "Mbtc", "UBTC", "Ubtc"];
158
159impl fmt::Display for Denomination {
160 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_str()) }
161}
162
163impl FromStr for Denomination {
164 type Err = ParseDenominationError;
165
166 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 use self::ParseDenominationError as E;
175
176 if CONFUSING_FORMS.contains(&s) {
177 return Err(E::PossiblyConfusing(PossiblyConfusingDenominationError(s.into())));
178 };
179
180 let form = Self::forms(s);
181
182 form.ok_or_else(|| E::Unknown(UnknownDenominationError(s.into())))
183 }
184}
185
186fn is_too_precise(s: &str, precision: usize) -> Option<usize> {
190 match s.find('.') {
191 Some(pos) if precision >= pos => Some(0),
192 Some(pos) => s[..pos]
193 .char_indices()
194 .rev()
195 .take(precision)
196 .find(|(_, d)| *d != '0')
197 .map(|(i, _)| i)
198 .or_else(|| {
199 s[(pos + 1)..].char_indices().find(|(_, d)| *d != '0').map(|(i, _)| i + pos + 1)
200 }),
201 None if precision >= s.len() => Some(0),
202 None => s.char_indices().rev().take(precision).find(|(_, d)| *d != '0').map(|(i, _)| i),
203 }
204}
205
206const INPUT_STRING_LEN_LIMIT: usize = 50;
207
208#[allow(clippy::too_many_lines)]
213fn parse_signed_to_satoshi(
214 mut s: &str,
215 denom: Denomination,
216) -> Result<(bool, SignedAmount), InnerParseError> {
217 if s.is_empty() {
218 return Err(InnerParseError::MissingDigits(MissingDigitsError {
219 kind: MissingDigitsKind::Empty,
220 }));
221 }
222 if s.len() > INPUT_STRING_LEN_LIMIT {
223 return Err(InnerParseError::InputTooLarge(s.len()));
224 }
225
226 let is_negative = s.starts_with('-');
227 if is_negative {
228 if s.len() == 1 {
229 return Err(InnerParseError::MissingDigits(MissingDigitsError {
230 kind: MissingDigitsKind::OnlyMinusSign,
231 }));
232 }
233 s = &s[1..];
234 }
235
236 let max_decimals = {
237 let precision_diff = -denom.precision();
240 if precision_diff <= 0 {
241 let last_n = precision_diff.unsigned_abs().into();
246 if let Some(position) = is_too_precise(s, last_n) {
247 match s.parse::<i64>() {
248 Ok(0) => return Ok((is_negative, SignedAmount::ZERO)),
249 _ =>
250 return Err(InnerParseError::TooPrecise(TooPreciseError {
251 position: position + usize::from(is_negative),
252 })),
253 }
254 }
255 s = &s[0..s.find('.').unwrap_or(s.len()) - last_n];
256 0
257 } else {
258 precision_diff
259 }
260 };
261
262 let mut decimals = None;
263 let mut underscores = None;
265 let mut value: i64 = 0; for (i, c) in s.char_indices() {
267 match c {
268 '0'..='9' => {
269 match 10_i64.checked_mul(value) {
271 None => return Err(InnerParseError::Overflow { is_negative }),
272 Some(val) => match val.checked_add(i64::from(c as u8 - b'0')) {
273 None => return Err(InnerParseError::Overflow { is_negative }),
274 Some(val) => value = val,
275 },
276 }
277 decimals = match decimals {
279 None => None,
280 Some(d) if d < max_decimals => Some(d + 1),
281 _ =>
282 return Err(InnerParseError::TooPrecise(TooPreciseError {
283 position: i + usize::from(is_negative),
284 })),
285 };
286 underscores = None;
287 }
288 '_' if i == 0 =>
289 return Err(InnerParseError::BadPosition(BadPositionError {
291 char: '_',
292 position: i + usize::from(is_negative),
293 })),
294 '_' => match underscores {
295 None => underscores = Some(1),
296 _ =>
298 return Err(InnerParseError::BadPosition(BadPositionError {
299 char: '_',
300 position: i + usize::from(is_negative),
301 })),
302 },
303 '.' => match decimals {
304 None if max_decimals <= 0 => break,
305 None => {
306 decimals = Some(0);
307 underscores = None;
308 }
309 _ =>
311 return Err(InnerParseError::InvalidCharacter(InvalidCharacterError {
312 invalid_char: '.',
313 position: i + usize::from(is_negative),
314 })),
315 },
316 c =>
317 return Err(InnerParseError::InvalidCharacter(InvalidCharacterError {
318 invalid_char: c,
319 position: i + usize::from(is_negative),
320 })),
321 }
322 }
323
324 let scale_factor = max_decimals - decimals.unwrap_or(0);
326 for _ in 0..scale_factor {
327 value = match 10_i64.checked_mul(value) {
328 Some(v) => v,
329 None => return Err(InnerParseError::Overflow { is_negative }),
330 };
331 }
332
333 let mut ret =
334 SignedAmount::from_sat(value).map_err(|_| InnerParseError::Overflow { is_negative })?;
335 if is_negative {
336 ret = -ret;
337 }
338 Ok((is_negative, ret))
339}
340
341#[derive(Debug)]
342enum InnerParseError {
343 Overflow { is_negative: bool },
344 TooPrecise(TooPreciseError),
345 MissingDigits(MissingDigitsError),
346 InputTooLarge(usize),
347 InvalidCharacter(InvalidCharacterError),
348 BadPosition(BadPositionError),
349}
350
351impl From<Infallible> for InnerParseError {
352 fn from(never: Infallible) -> Self { match never {} }
353}
354
355impl InnerParseError {
356 fn convert(self, is_signed: bool) -> ParseAmountError {
357 match self {
358 Self::Overflow { is_negative } =>
359 OutOfRangeError { is_signed, is_greater_than_max: !is_negative }.into(),
360 Self::TooPrecise(e) => ParseAmountError(ParseAmountErrorInner::TooPrecise(e)),
361 Self::MissingDigits(e) => ParseAmountError(ParseAmountErrorInner::MissingDigits(e)),
362 Self::InputTooLarge(len) =>
363 ParseAmountError(ParseAmountErrorInner::InputTooLarge(InputTooLargeError { len })),
364 Self::InvalidCharacter(e) =>
365 ParseAmountError(ParseAmountErrorInner::InvalidCharacter(e)),
366 Self::BadPosition(e) => ParseAmountError(ParseAmountErrorInner::BadPosition(e)),
367 }
368 }
369}
370
371fn split_amount_and_denomination(s: &str) -> Result<(&str, Denomination), ParseError> {
372 let (i, j) = if let Some(i) = s.find(' ') {
373 (i, i + 1)
374 } else {
375 let i = s
376 .find(|c: char| c.is_alphabetic())
377 .ok_or(ParseError(ParseErrorInner::MissingDenomination(MissingDenominationError)))?;
378 (i, i)
379 };
380 Ok((&s[..i], s[j..].parse()?))
381}
382
383#[derive(Debug, Clone, Copy, Eq, PartialEq)]
385struct FormatOptions {
386 fill: char,
387 align: Option<fmt::Alignment>,
388 width: Option<usize>,
389 precision: Option<usize>,
390 sign_plus: bool,
391 sign_aware_zero_pad: bool,
392}
393
394impl FormatOptions {
395 fn from_formatter(f: &fmt::Formatter) -> Self {
396 Self {
397 fill: f.fill(),
398 align: f.align(),
399 width: f.width(),
400 precision: f.precision(),
401 sign_plus: f.sign_plus(),
402 sign_aware_zero_pad: f.sign_aware_zero_pad(),
403 }
404 }
405}
406
407impl Default for FormatOptions {
408 fn default() -> Self {
409 Self {
410 fill: ' ',
411 align: None,
412 width: None,
413 precision: None,
414 sign_plus: false,
415 sign_aware_zero_pad: false,
416 }
417 }
418}
419
420fn dec_width(mut num: u64) -> usize {
421 let mut width = 1;
422 loop {
423 num /= 10;
424 if num == 0 {
425 break;
426 }
427 width += 1;
428 }
429 width
430}
431
432fn repeat_char(f: &mut dyn fmt::Write, c: char, count: usize) -> fmt::Result {
433 for _ in 0..count {
434 f.write_char(c)?;
435 }
436 Ok(())
437}
438
439fn fmt_satoshi_in(
441 mut satoshi: u64,
442 negative: bool,
443 f: &mut dyn fmt::Write,
444 denom: Denomination,
445 show_denom: bool,
446 options: FormatOptions,
447) -> fmt::Result {
448 let precision = denom.precision();
449 let mut num_after_decimal_point = 0;
452 let mut norm_nb_decimals = 0;
453 let mut num_before_decimal_point = satoshi;
454 let trailing_decimal_zeros;
455 let mut exp = 0;
456 match precision.cmp(&0) {
457 Ordering::Greater => {
459 if satoshi > 0 {
460 exp = precision as usize; }
462 trailing_decimal_zeros = options.precision.unwrap_or(0);
463 }
464 Ordering::Less => {
465 let precision = precision.unsigned_abs();
466 if let Some(format_precision) = options.precision {
469 if usize::from(precision) > format_precision {
470 let rounding_divisor =
472 10u64.pow(u32::from(precision) - format_precision as u32); let remainder = satoshi % rounding_divisor;
474 satoshi -= remainder;
475 if remainder / (rounding_divisor / 10) >= 5 {
476 satoshi += rounding_divisor;
477 }
478 }
479 }
480 let divisor = 10u64.pow(precision.into());
481 num_before_decimal_point = satoshi / divisor;
482 num_after_decimal_point = satoshi % divisor;
483 if num_after_decimal_point == 0 {
485 norm_nb_decimals = 0;
486 } else {
487 norm_nb_decimals = usize::from(precision);
488 while num_after_decimal_point % 10 == 0 {
489 norm_nb_decimals -= 1;
490 num_after_decimal_point /= 10;
491 }
492 }
493 let opt_precision = options.precision.unwrap_or(0);
495 trailing_decimal_zeros = opt_precision.saturating_sub(norm_nb_decimals);
496 }
497 Ordering::Equal => trailing_decimal_zeros = options.precision.unwrap_or(0),
498 }
499 let total_decimals = norm_nb_decimals + trailing_decimal_zeros;
500 let mut num_width = if total_decimals > 0 {
502 1 + total_decimals
504 } else {
505 0
506 };
507 num_width += dec_width(num_before_decimal_point) + exp;
508 if options.sign_plus || negative {
509 num_width += 1;
510 }
511
512 if show_denom {
513 num_width += denom.as_str().len() + 1;
515 }
516
517 let width = options.width.unwrap_or(0);
518 let align = options.align.unwrap_or(fmt::Alignment::Right);
519 let (left_pad, pad_right) = match (num_width < width, options.sign_aware_zero_pad, align) {
520 (false, _, _) => (0, 0),
521 (true, true, _) | (true, false, fmt::Alignment::Right) => (width - num_width, 0),
523 (true, false, fmt::Alignment::Left) => (0, width - num_width),
524 (true, false, fmt::Alignment::Center) =>
526 ((width - num_width) / 2, (width - num_width).div_ceil(2)),
527 };
528
529 if !options.sign_aware_zero_pad {
530 repeat_char(f, options.fill, left_pad)?;
531 }
532
533 if negative {
534 write!(f, "-")?;
535 } else if options.sign_plus {
536 write!(f, "+")?;
537 }
538
539 if options.sign_aware_zero_pad {
540 repeat_char(f, '0', left_pad)?;
541 }
542
543 write!(f, "{}", num_before_decimal_point)?;
544
545 repeat_char(f, '0', exp)?;
546
547 if total_decimals > 0 {
548 write!(f, ".")?;
549 }
550 if norm_nb_decimals > 0 {
551 write!(f, "{:0width$}", num_after_decimal_point, width = norm_nb_decimals)?;
552 }
553 repeat_char(f, '0', trailing_decimal_zeros)?;
554
555 if show_denom {
556 write!(f, " {}", denom.as_str())?;
557 }
558
559 repeat_char(f, options.fill, pad_right)?;
560 Ok(())
561}
562
563#[derive(Debug, Clone)]
581pub struct Display {
582 sats_abs: u64,
584 is_negative: bool,
586 style: DisplayStyle,
588}
589
590impl Display {
591 #[must_use]
593 pub fn show_denomination(mut self) -> Self {
594 match &mut self.style {
595 DisplayStyle::FixedDenomination { show_denomination, .. } => *show_denomination = true,
596 DisplayStyle::DynamicDenomination => (),
598 }
599 self
600 }
601}
602
603impl fmt::Display for Display {
604 #[rustfmt::skip]
605 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
606 let format_options = FormatOptions::from_formatter(f);
607 match &self.style {
608 DisplayStyle::FixedDenomination { show_denomination, denomination } => {
609 fmt_satoshi_in(self.sats_abs, self.is_negative, f, *denomination, *show_denomination, format_options)
610 },
611 DisplayStyle::DynamicDenomination if self.sats_abs >= Amount::ONE_BTC.to_sat() => {
612 fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Bitcoin, true, format_options)
613 },
614 DisplayStyle::DynamicDenomination => {
615 fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Satoshi, true, format_options)
616 },
617 }
618 }
619}
620
621#[derive(Clone, Debug)]
622enum DisplayStyle {
623 FixedDenomination { denomination: Denomination, show_denomination: bool },
624 DynamicDenomination,
625}
626
627#[cfg(feature = "arbitrary")]
628impl<'a> Arbitrary<'a> for Denomination {
629 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
630 let choice = u.int_in_range(0..=5)?;
631 match choice {
632 0 => Ok(Self::Bitcoin),
633 1 => Ok(Self::CentiBitcoin),
634 2 => Ok(Self::MilliBitcoin),
635 3 => Ok(Self::MicroBitcoin),
636 4 => Ok(Self::Bit),
637 _ => Ok(Self::Satoshi),
638 }
639 }
640}