// To the extent possible under law, the author(s) have dedicated all // copyright and related and neighboring rights to this software to // the public domain worldwide. This software is distributed without // any warranty. // // You should have received a copy of the CC0 Public Domain Dedication // along with this software. // If not, see . // //! Bitcoin amounts. //! //! This module mainly introduces the [Amount] and [SignedAmount] types. //! We refer to the documentation on the types for more information. //! use prelude::*; use core::{ops, default, str::FromStr, cmp::Ordering}; use core::fmt::{self, Write}; /// A set of denominations in which amounts can be expressed. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum Denomination { /// BTC Bitcoin, /// mBTC MilliBitcoin, /// uBTC MicroBitcoin, /// nBTC NanoBitcoin, /// pBTC PicoBitcoin, /// bits Bit, /// satoshi Satoshi, /// msat MilliSatoshi, } impl Denomination { /// The number of decimal places more than a satoshi. fn precision(self) -> i32 { match self { Denomination::Bitcoin => -8, Denomination::MilliBitcoin => -5, Denomination::MicroBitcoin => -2, Denomination::NanoBitcoin => 1, Denomination::PicoBitcoin => 4, Denomination::Bit => -2, Denomination::Satoshi => 0, Denomination::MilliSatoshi => 3, } } } impl fmt::Display for Denomination { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { Denomination::Bitcoin => "BTC", Denomination::MilliBitcoin => "mBTC", Denomination::MicroBitcoin => "uBTC", Denomination::NanoBitcoin => "nBTC", Denomination::PicoBitcoin => "pBTC", Denomination::Bit => "bits", Denomination::Satoshi => "satoshi", Denomination::MilliSatoshi => "msat", }) } } impl FromStr for Denomination { type Err = ParseAmountError; /// Convert from a str to Denomination. /// /// Any combination of upper and/or lower case, excluding uppercase of SI(m, u, n, p) is considered valid. /// - Singular: BTC, mBTC, uBTC, nBTC, pBTC /// - Plural or singular: sat, satoshi, bit, msat /// /// Due to ambiguity between mega and milli, pico and peta we prohibit usage of leading capital 'M', 'P'. fn from_str(s: &str) -> Result { use self::ParseAmountError::*; use self::Denomination as D; let starts_with_uppercase = || s.starts_with(|ch: char| ch.is_uppercase()); match denomination_from_str(s) { None => Err(UnknownDenomination(s.to_owned())), Some(D::MilliBitcoin) | Some(D::PicoBitcoin) | Some(D::MilliSatoshi) if starts_with_uppercase() => { Err(PossiblyConfusingDenomination(s.to_owned())) } Some(D::NanoBitcoin) | Some(D::MicroBitcoin) if starts_with_uppercase() => { Err(UnknownDenomination(s.to_owned())) } Some(d) => Ok(d), } } } fn denomination_from_str(mut s: &str) -> Option { if s.eq_ignore_ascii_case("BTC") { return Some(Denomination::Bitcoin); } if s.eq_ignore_ascii_case("mBTC") { return Some(Denomination::MilliBitcoin); } if s.eq_ignore_ascii_case("uBTC") { return Some(Denomination::MicroBitcoin); } if s.eq_ignore_ascii_case("nBTC") { return Some(Denomination::NanoBitcoin); } if s.eq_ignore_ascii_case("pBTC") { return Some(Denomination::PicoBitcoin); } if s.ends_with('s') || s.ends_with('S') { s = &s[..(s.len() - 1)]; } if s.eq_ignore_ascii_case("bit") { return Some(Denomination::Bit); } if s.eq_ignore_ascii_case("satoshi") { return Some(Denomination::Satoshi); } if s.eq_ignore_ascii_case("sat") { return Some(Denomination::Satoshi); } if s.eq_ignore_ascii_case("msat") { return Some(Denomination::MilliSatoshi); } None } /// An error during amount parsing. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ParseAmountError { /// Amount is negative. Negative, /// Amount is too big to fit inside the type. TooBig, /// Amount has higher precision than supported by the type. TooPrecise, /// Invalid number format. InvalidFormat, /// Input string was too large. InputTooLarge, /// Invalid character in input. InvalidCharacter(char), /// The denomination was unknown. UnknownDenomination(String), /// The denomination has multiple possible interpretations. PossiblyConfusingDenomination(String) } impl fmt::Display for ParseAmountError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ParseAmountError::Negative => f.write_str("amount is negative"), ParseAmountError::TooBig => f.write_str("amount is too big"), ParseAmountError::TooPrecise => f.write_str("amount has a too high precision"), ParseAmountError::InvalidFormat => f.write_str("invalid number format"), ParseAmountError::InputTooLarge => f.write_str("input string was too large"), ParseAmountError::InvalidCharacter(c) => write!(f, "invalid character in input: {}", c), ParseAmountError::UnknownDenomination(ref d) => write!(f, "unknown denomination: {}", d), ParseAmountError::PossiblyConfusingDenomination(ref d) => { let (letter, upper, lower) = match d.chars().next() { Some('M') => ('M', "Mega", "milli"), Some('P') => ('P',"Peta", "pico"), // This panic could be avoided by adding enum ConfusingDenomination { Mega, Peta } but is it worth it? _ => panic!("invalid error information"), }; write!(f, "the '{}' at the beginning of {} should technically mean '{}' but that denomination is uncommon and maybe '{}' was intended", letter, d, upper, lower) } } } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl ::std::error::Error for ParseAmountError {} fn is_too_precise(s: &str, precision: usize) -> bool { s.contains('.') || precision >= s.len() || s.chars().rev().take(precision).any(|d| d != '0') } /// Parse decimal string in the given denomination into a satoshi value and a /// bool indicator for a negative amount. fn parse_signed_to_satoshi( mut s: &str, denom: Denomination, ) -> Result<(bool, u64), ParseAmountError> { if s.is_empty() { return Err(ParseAmountError::InvalidFormat); } if s.len() > 50 { return Err(ParseAmountError::InputTooLarge); } let is_negative = s.starts_with('-'); if is_negative { if s.len() == 1 { return Err(ParseAmountError::InvalidFormat); } s = &s[1..]; } let max_decimals = { // The difference in precision between native (satoshi) // and desired denomination. let precision_diff = -denom.precision(); if precision_diff < 0 { // If precision diff is negative, this means we are parsing // into a less precise amount. That is not allowed unless // there are no decimals and the last digits are zeroes as // many as the difference in precision. let last_n = precision_diff.abs() as usize; if is_too_precise(s, last_n) { return Err(ParseAmountError::TooPrecise); } s = &s[0..s.len() - last_n]; 0 } else { precision_diff } }; let mut decimals = None; let mut value: u64 = 0; // as satoshis for c in s.chars() { match c { '0'..='9' => { // Do `value = 10 * value + digit`, catching overflows. match 10_u64.checked_mul(value) { None => return Err(ParseAmountError::TooBig), Some(val) => match val.checked_add((c as u8 - b'0') as u64) { None => return Err(ParseAmountError::TooBig), Some(val) => value = val, }, } // Increment the decimal digit counter if past decimal. decimals = match decimals { None => None, Some(d) if d < max_decimals => Some(d + 1), _ => return Err(ParseAmountError::TooPrecise), }; } '.' => match decimals { None => decimals = Some(0), // Double decimal dot. _ => return Err(ParseAmountError::InvalidFormat), }, c => return Err(ParseAmountError::InvalidCharacter(c)), } } // Decimally shift left by `max_decimals - decimals`. let scale_factor = max_decimals - decimals.unwrap_or(0); for _ in 0..scale_factor { value = match 10_u64.checked_mul(value) { Some(v) => v, None => return Err(ParseAmountError::TooBig), }; } Ok((is_negative, value)) } /// Format the given satoshi amount in the given denomination. /// /// Does not include the denomination. fn fmt_satoshi_in( satoshi: u64, negative: bool, f: &mut dyn fmt::Write, denom: Denomination, ) -> fmt::Result { if negative { f.write_str("-")?; } let precision = denom.precision(); match precision.cmp(&0) { Ordering::Greater => { // add zeroes in the end let width = precision as usize; write!(f, "{}{:0width$}", satoshi, 0, width = width)?; } Ordering::Less => { // need to inject a comma in the number let nb_decimals = precision.abs() as usize; let real = format!("{:0width$}", satoshi, width = nb_decimals); if real.len() == nb_decimals { write!(f, "0.{}", &real[real.len() - nb_decimals..])?; } else { write!( f, "{}.{}", &real[0..(real.len() - nb_decimals)], &real[real.len() - nb_decimals..] )?; } } Ordering::Equal => write!(f, "{}", satoshi)?, } Ok(()) } /// Amount /// /// The [Amount] type can be used to express Bitcoin amounts that supports /// arithmetic and conversion to various denominations. /// /// /// Warning! /// /// This type implements several arithmetic operations from [core::ops]. /// To prevent errors due to overflow or underflow when using these operations, /// it is advised to instead use the checked arithmetic methods whose names /// start with `checked_`. The operations from [core::ops] that [Amount] /// implements will panic when overflow or underflow occurs. Also note that /// since the internal representation of amounts is unsigned, subtracting below /// zero is considered an underflow and will cause a panic if you're not using /// the checked arithmetic methods. /// #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Amount(u64); impl Amount { /// The zero amount. pub const ZERO: Amount = Amount(0); /// Exactly one satoshi. pub const ONE_SAT: Amount = Amount(1); /// Exactly one bitcoin. pub const ONE_BTC: Amount = Amount(100_000_000); /// The maximum value allowed as an amount. Useful for sanity checking. pub const MAX_MONEY: Amount = Amount(21_000_000 * 100_000_000); /// Create an [Amount] with satoshi precision and the given number of satoshis. pub fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) } /// Get the number of satoshis in this [Amount]. pub fn as_sat(self) -> u64 { self.0 } /// The maximum value of an [Amount]. pub fn max_value() -> Amount { Amount(u64::max_value()) } /// The minimum value of an [Amount]. pub fn min_value() -> Amount { Amount(u64::min_value()) } /// Convert from a value expressing bitcoins to an [Amount]. pub fn from_btc(btc: f64) -> Result { Amount::from_float_in(btc, Denomination::Bitcoin) } /// Parse a decimal string as a value in the given denomination. /// /// Note: This only parses the value string. If you want to parse a value /// with denomination, use [FromStr]. pub fn from_str_in(s: &str, denom: Denomination) -> Result { let (negative, satoshi) = parse_signed_to_satoshi(s, denom)?; if negative { return Err(ParseAmountError::Negative); } if satoshi > i64::max_value() as u64 { return Err(ParseAmountError::TooBig); } Ok(Amount::from_sat(satoshi)) } /// Parses amounts with denomination suffix like they are produced with /// [Self::to_string_with_denomination] or with [fmt::Display]. /// If you want to parse only the amount without the denomination, /// use [Self::from_str_in]. pub fn from_str_with_denomination(s: &str) -> Result { let mut split = s.splitn(3, ' '); let amt_str = split.next().unwrap(); let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?; if split.next().is_some() { return Err(ParseAmountError::InvalidFormat); } Amount::from_str_in(amt_str, denom_str.parse()?) } /// Express this [Amount] as a floating-point value in the given denomination. /// /// Please be aware of the risk of using floating-point numbers. pub fn to_float_in(self, denom: Denomination) -> f64 { f64::from_str(&self.to_string_in(denom)).unwrap() } /// Express this [Amount] as a floating-point value in Bitcoin. /// /// Equivalent to `to_float_in(Denomination::Bitcoin)`. /// /// Please be aware of the risk of using floating-point numbers. pub fn as_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) } /// Convert this [Amount] in floating-point notation with a given /// denomination. /// Can return error if the amount is too big, too precise or negative. /// /// Please be aware of the risk of using floating-point numbers. pub fn from_float_in(value: f64, denom: Denomination) -> Result { if value < 0.0 { return Err(ParseAmountError::Negative); } // This is inefficient, but the safest way to deal with this. The parsing logic is safe. // Any performance-critical application should not be dealing with floats. Amount::from_str_in(&value.to_string(), denom) } /// Format the value of this [Amount] in the given denomination. /// /// Does not include the denomination. pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result { fmt_satoshi_in(self.as_sat(), false, f, denom) } /// Get a string number of this [Amount] in the given denomination. /// /// Does not include the denomination. pub fn to_string_in(self, denom: Denomination) -> String { let mut buf = String::new(); self.fmt_value_in(&mut buf, denom).unwrap(); buf } /// Get a formatted string of this [Amount] in the given denomination, /// suffixed with the abbreviation for the denomination. pub fn to_string_with_denomination(self, denom: Denomination) -> String { let mut buf = String::new(); self.fmt_value_in(&mut buf, denom).unwrap(); write!(buf, " {}", denom).unwrap(); buf } // Some arithmetic that doesn't fit in `core::ops` traits. /// Checked addition. /// Returns [None] if overflow occurred. pub fn checked_add(self, rhs: Amount) -> Option { self.0.checked_add(rhs.0).map(Amount) } /// Checked subtraction. /// Returns [None] if overflow occurred. pub fn checked_sub(self, rhs: Amount) -> Option { self.0.checked_sub(rhs.0).map(Amount) } /// Checked multiplication. /// Returns [None] if overflow occurred. pub fn checked_mul(self, rhs: u64) -> Option { self.0.checked_mul(rhs).map(Amount) } /// Checked integer division. /// Be aware that integer division loses the remainder if no exact division /// can be made. /// Returns [None] if overflow occurred. pub fn checked_div(self, rhs: u64) -> Option { self.0.checked_div(rhs).map(Amount) } /// Checked remainder. /// Returns [None] if overflow occurred. pub fn checked_rem(self, rhs: u64) -> Option { self.0.checked_rem(rhs).map(Amount) } /// Convert to a signed amount. pub fn to_signed(self) -> Result { if self.as_sat() > SignedAmount::max_value().as_sat() as u64 { Err(ParseAmountError::TooBig) } else { Ok(SignedAmount::from_sat(self.as_sat() as i64)) } } } impl default::Default for Amount { fn default() -> Self { Amount::ZERO } } impl fmt::Debug for Amount { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Amount({:.8} BTC)", self.as_btc()) } } // No one should depend on a binding contract for Display for this type. // Just using Bitcoin denominated string. impl fmt::Display for Amount { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_value_in(f, Denomination::Bitcoin)?; write!(f, " {}", Denomination::Bitcoin) } } impl ops::Add for Amount { type Output = Amount; fn add(self, rhs: Amount) -> Self::Output { self.checked_add(rhs).expect("Amount addition error") } } impl ops::AddAssign for Amount { fn add_assign(&mut self, other: Amount) { *self = *self + other } } impl ops::Sub for Amount { type Output = Amount; fn sub(self, rhs: Amount) -> Self::Output { self.checked_sub(rhs).expect("Amount subtraction error") } } impl ops::SubAssign for Amount { fn sub_assign(&mut self, other: Amount) { *self = *self - other } } impl ops::Rem for Amount { type Output = Amount; fn rem(self, modulus: u64) -> Self { self.checked_rem(modulus).expect("Amount remainder error") } } impl ops::RemAssign for Amount { fn rem_assign(&mut self, modulus: u64) { *self = *self % modulus } } impl ops::Mul for Amount { type Output = Amount; fn mul(self, rhs: u64) -> Self::Output { self.checked_mul(rhs).expect("Amount multiplication error") } } impl ops::MulAssign for Amount { fn mul_assign(&mut self, rhs: u64) { *self = *self * rhs } } impl ops::Div for Amount { type Output = Amount; fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).expect("Amount division error") } } impl ops::DivAssign for Amount { fn div_assign(&mut self, rhs: u64) { *self = *self / rhs } } impl FromStr for Amount { type Err = ParseAmountError; fn from_str(s: &str) -> Result { Amount::from_str_with_denomination(s) } } impl ::core::iter::Sum for Amount { fn sum>(iter: I) -> Self { let sats: u64 = iter.map(|amt| amt.0).sum(); Amount::from_sat(sats) } } /// SignedAmount /// /// The [SignedAmount] type can be used to express Bitcoin amounts that supports /// arithmetic and conversion to various denominations. /// /// /// Warning! /// /// This type implements several arithmetic operations from [core::ops]. /// To prevent errors due to overflow or underflow when using these operations, /// it is advised to instead use the checked arithmetic methods whose names /// start with `checked_`. The operations from [core::ops] that [Amount] /// implements will panic when overflow or underflow occurs. /// #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SignedAmount(i64); impl SignedAmount { /// The zero amount. pub const ZERO: SignedAmount = SignedAmount(0); /// Exactly one satoshi. pub const ONE_SAT: SignedAmount = SignedAmount(1); /// Exactly one bitcoin. pub const ONE_BTC: SignedAmount = SignedAmount(100_000_000); /// The maximum value allowed as an amount. Useful for sanity checking. pub const MAX_MONEY: SignedAmount = SignedAmount(21_000_000 * 100_000_000); /// Create an [SignedAmount] with satoshi precision and the given number of satoshis. pub fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) } /// Get the number of satoshis in this [SignedAmount]. pub fn as_sat(self) -> i64 { self.0 } /// The maximum value of an [SignedAmount]. pub fn max_value() -> SignedAmount { SignedAmount(i64::max_value()) } /// The minimum value of an [SignedAmount]. pub fn min_value() -> SignedAmount { SignedAmount(i64::min_value()) } /// Convert from a value expressing bitcoins to an [SignedAmount]. pub fn from_btc(btc: f64) -> Result { SignedAmount::from_float_in(btc, Denomination::Bitcoin) } /// Parse a decimal string as a value in the given denomination. /// /// Note: This only parses the value string. If you want to parse a value /// with denomination, use [FromStr]. pub fn from_str_in(s: &str, denom: Denomination) -> Result { let (negative, satoshi) = parse_signed_to_satoshi(s, denom)?; if satoshi > i64::max_value() as u64 { return Err(ParseAmountError::TooBig); } Ok(match negative { true => SignedAmount(-(satoshi as i64)), false => SignedAmount(satoshi as i64), }) } /// Parses amounts with denomination suffix like they are produced with /// [Self::to_string_with_denomination] or with [fmt::Display]. /// If you want to parse only the amount without the denomination, /// use [Self::from_str_in]. pub fn from_str_with_denomination(s: &str) -> Result { let mut split = s.splitn(3, ' '); let amt_str = split.next().unwrap(); let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?; if split.next().is_some() { return Err(ParseAmountError::InvalidFormat); } SignedAmount::from_str_in(amt_str, denom_str.parse()?) } /// Express this [SignedAmount] as a floating-point value in the given denomination. /// /// Please be aware of the risk of using floating-point numbers. pub fn to_float_in(self, denom: Denomination) -> f64 { f64::from_str(&self.to_string_in(denom)).unwrap() } /// Express this [SignedAmount] as a floating-point value in Bitcoin. /// /// Equivalent to `to_float_in(Denomination::Bitcoin)`. /// /// Please be aware of the risk of using floating-point numbers. pub fn as_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) } /// Convert this [SignedAmount] in floating-point notation with a given /// denomination. /// Can return error if the amount is too big, too precise or negative. /// /// Please be aware of the risk of using floating-point numbers. pub fn from_float_in( value: f64, denom: Denomination, ) -> Result { // This is inefficient, but the safest way to deal with this. The parsing logic is safe. // Any performance-critical application should not be dealing with floats. SignedAmount::from_str_in(&value.to_string(), denom) } /// Format the value of this [SignedAmount] in the given denomination. /// /// Does not include the denomination. pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result { let sats = self.as_sat().checked_abs().map(|a: i64| a as u64).unwrap_or_else(|| { // We could also hard code this into `9223372036854775808` u64::max_value() - self.as_sat() as u64 +1 }); fmt_satoshi_in(sats, self.is_negative(), f, denom) } /// Get a string number of this [SignedAmount] in the given denomination. /// /// Does not include the denomination. pub fn to_string_in(self, denom: Denomination) -> String { let mut buf = String::new(); self.fmt_value_in(&mut buf, denom).unwrap(); buf } /// Get a formatted string of this [SignedAmount] in the given denomination, /// suffixed with the abbreviation for the denomination. pub fn to_string_with_denomination(self, denom: Denomination) -> String { let mut buf = String::new(); self.fmt_value_in(&mut buf, denom).unwrap(); write!(buf, " {}", denom).unwrap(); buf } // Some arithmetic that doesn't fit in `core::ops` traits. /// Get the absolute value of this [SignedAmount]. pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) } /// Returns a number representing sign of this [SignedAmount]. /// /// - `0` if the amount is zero /// - `1` if the amount is positive /// - `-1` if the amount is negative pub fn signum(self) -> i64 { self.0.signum() } /// Returns `true` if this [SignedAmount] is positive and `false` if /// this [SignedAmount] is zero or negative. pub fn is_positive(self) -> bool { self.0.is_positive() } /// Returns `true` if this [SignedAmount] is negative and `false` if /// this [SignedAmount] is zero or positive. pub fn is_negative(self) -> bool { self.0.is_negative() } /// Get the absolute value of this [SignedAmount]. /// Returns [None] if overflow occurred. (`self == min_value()`) pub fn checked_abs(self) -> Option { self.0.checked_abs().map(SignedAmount) } /// Checked addition. /// Returns [None] if overflow occurred. pub fn checked_add(self, rhs: SignedAmount) -> Option { self.0.checked_add(rhs.0).map(SignedAmount) } /// Checked subtraction. /// Returns [None] if overflow occurred. pub fn checked_sub(self, rhs: SignedAmount) -> Option { self.0.checked_sub(rhs.0).map(SignedAmount) } /// Checked multiplication. /// Returns [None] if overflow occurred. pub fn checked_mul(self, rhs: i64) -> Option { self.0.checked_mul(rhs).map(SignedAmount) } /// Checked integer division. /// Be aware that integer division loses the remainder if no exact division /// can be made. /// Returns [None] if overflow occurred. pub fn checked_div(self, rhs: i64) -> Option { self.0.checked_div(rhs).map(SignedAmount) } /// Checked remainder. /// Returns [None] if overflow occurred. pub fn checked_rem(self, rhs: i64) -> Option { self.0.checked_rem(rhs).map(SignedAmount) } /// Subtraction that doesn't allow negative [SignedAmount]s. /// Returns [None] if either [self], `rhs` or the result is strictly negative. pub fn positive_sub(self, rhs: SignedAmount) -> Option { if self.is_negative() || rhs.is_negative() || rhs > self { None } else { self.checked_sub(rhs) } } /// Convert to an unsigned amount. pub fn to_unsigned(self) -> Result { if self.is_negative() { Err(ParseAmountError::Negative) } else { Ok(Amount::from_sat(self.as_sat() as u64)) } } } impl default::Default for SignedAmount { fn default() -> Self { SignedAmount::ZERO } } impl fmt::Debug for SignedAmount { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SignedAmount({:.8} BTC)", self.as_btc()) } } // No one should depend on a binding contract for Display for this type. // Just using Bitcoin denominated string. impl fmt::Display for SignedAmount { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_value_in(f, Denomination::Bitcoin)?; write!(f, " {}", Denomination::Bitcoin) } } impl ops::Add for SignedAmount { type Output = SignedAmount; fn add(self, rhs: SignedAmount) -> Self::Output { self.checked_add(rhs).expect("SignedAmount addition error") } } impl ops::AddAssign for SignedAmount { fn add_assign(&mut self, other: SignedAmount) { *self = *self + other } } impl ops::Sub for SignedAmount { type Output = SignedAmount; fn sub(self, rhs: SignedAmount) -> Self::Output { self.checked_sub(rhs).expect("SignedAmount subtraction error") } } impl ops::SubAssign for SignedAmount { fn sub_assign(&mut self, other: SignedAmount) { *self = *self - other } } impl ops::Rem for SignedAmount { type Output = SignedAmount; fn rem(self, modulus: i64) -> Self { self.checked_rem(modulus).expect("SignedAmount remainder error") } } impl ops::RemAssign for SignedAmount { fn rem_assign(&mut self, modulus: i64) { *self = *self % modulus } } impl ops::Mul for SignedAmount { type Output = SignedAmount; fn mul(self, rhs: i64) -> Self::Output { self.checked_mul(rhs).expect("SignedAmount multiplication error") } } impl ops::MulAssign for SignedAmount { fn mul_assign(&mut self, rhs: i64) { *self = *self * rhs } } impl ops::Div for SignedAmount { type Output = SignedAmount; fn div(self, rhs: i64) -> Self::Output { self.checked_div(rhs).expect("SignedAmount division error") } } impl ops::DivAssign for SignedAmount { fn div_assign(&mut self, rhs: i64) { *self = *self / rhs } } impl FromStr for SignedAmount { type Err = ParseAmountError; fn from_str(s: &str) -> Result { SignedAmount::from_str_with_denomination(s) } } impl ::core::iter::Sum for SignedAmount { fn sum>(iter: I) -> Self { let sats: i64 = iter.map(|amt| amt.0).sum(); SignedAmount::from_sat(sats) } } /// Calculate the sum over the iterator using checked arithmetic. pub trait CheckedSum: private::SumSeal { /// Calculate the sum over the iterator using checked arithmetic. If an over or underflow would /// happen it returns `None`. fn checked_sum(self) -> Option; } impl CheckedSum for T where T: Iterator { fn checked_sum(mut self) -> Option { let first = Some(self.next().unwrap_or_default()); self.fold( first, |acc, item| acc.and_then(|acc| acc.checked_add(item)) ) } } impl CheckedSum for T where T: Iterator { fn checked_sum(mut self) -> Option { let first = Some(self.next().unwrap_or_default()); self.fold( first, |acc, item| acc.and_then(|acc| acc.checked_add(item)) ) } } mod private { use ::{Amount, SignedAmount}; /// Used to seal the `CheckedSum` trait pub trait SumSeal {} impl SumSeal for T where T: Iterator {} impl SumSeal for T where T: Iterator {} } #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde { // methods are implementation of a standardized serde-specific signature #![allow(missing_docs)] //! This module adds serde serialization and deserialization support for Amounts. //! Since there is not a default way to serialize and deserialize Amounts, multiple //! ways are supported and it's up to the user to decide which serialiation to use. //! The provided modules can be used as follows: //! //! ```rust,ignore //! use serde::{Serialize, Deserialize}; //! use bitcoin::Amount; //! //! #[derive(Serialize, Deserialize)] //! pub struct HasAmount { //! #[serde(with = "bitcoin::util::amount::serde::as_btc")] //! pub amount: Amount, //! } //! ``` use serde::{Deserialize, Deserializer, Serialize, Serializer}; use util::amount::{Amount, Denomination, SignedAmount}; /// This trait is used only to avoid code duplication and naming collisions /// of the different serde serialization crates. /// /// TODO: Add the private::Sealed bound in next breaking release pub trait SerdeAmount: Copy + Sized { fn ser_sat(self, s: S) -> Result; fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result; fn ser_btc(self, s: S) -> Result; fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result; } mod private { /// add this as a trait bound to traits which consumers of this library /// should not be able to implement. pub trait Sealed {} impl Sealed for super::Amount {} impl Sealed for super::SignedAmount {} } /// This trait is only for internal Amount type serialization/deserialization pub trait SerdeAmountForOpt: Copy + Sized + SerdeAmount + private::Sealed { fn type_prefix() -> &'static str; fn ser_sat_opt(self, s: S) -> Result; fn ser_btc_opt(self, s: S) -> Result; } impl SerdeAmount for Amount { fn ser_sat(self, s: S) -> Result { u64::serialize(&self.as_sat(), s) } fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result { Ok(Amount::from_sat(u64::deserialize(d)?)) } fn ser_btc(self, s: S) -> Result { f64::serialize(&self.to_float_in(Denomination::Bitcoin), s) } fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result { use serde::de::Error; Ok(Amount::from_btc(f64::deserialize(d)?).map_err(D::Error::custom)?) } } impl SerdeAmountForOpt for Amount { fn type_prefix() -> &'static str { "u" } fn ser_sat_opt(self, s: S) -> Result { s.serialize_some(&self.as_sat()) } fn ser_btc_opt(self, s: S) -> Result { s.serialize_some(&self.as_btc()) } } impl SerdeAmount for SignedAmount { fn ser_sat(self, s: S) -> Result { i64::serialize(&self.as_sat(), s) } fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result { Ok(SignedAmount::from_sat(i64::deserialize(d)?)) } fn ser_btc(self, s: S) -> Result { f64::serialize(&self.to_float_in(Denomination::Bitcoin), s) } fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result { use serde::de::Error; Ok(SignedAmount::from_btc(f64::deserialize(d)?).map_err(D::Error::custom)?) } } impl SerdeAmountForOpt for SignedAmount { fn type_prefix() -> &'static str { "i" } fn ser_sat_opt(self, s: S) -> Result { s.serialize_some(&self.as_sat()) } fn ser_btc_opt(self, s: S) -> Result { s.serialize_some(&self.as_btc()) } } pub mod as_sat { //! Serialize and deserialize [`Amount`](crate::Amount) as real numbers denominated in satoshi. //! Use with `#[serde(with = "amount::serde::as_sat")]`. use serde::{Deserializer, Serializer}; use util::amount::serde::SerdeAmount; pub fn serialize(a: &A, s: S) -> Result { a.ser_sat(s) } pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result { A::des_sat(d) } pub mod opt { //! Serialize and deserialize [`Option`](crate::Amount) as real numbers denominated in satoshi. //! Use with `#[serde(default, with = "amount::serde::as_sat::opt")]`. use serde::{Deserializer, Serializer, de}; use util::amount::serde::SerdeAmountForOpt; use core::fmt; use core::marker::PhantomData; pub fn serialize( a: &Option, s: S, ) -> Result { match *a { Some(a) => a.ser_sat_opt(s), None => s.serialize_none(), } } pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>( d: D, ) -> Result, D::Error> { struct VisitOptAmt(PhantomData); impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "An Option<{}64>", X::type_prefix()) } fn visit_none(self) -> Result where E: de::Error { Ok(None) } fn visit_some(self, d: D) -> Result where D: Deserializer<'de> { Ok(Some(X::des_sat(d)?)) } } d.deserialize_option(VisitOptAmt::(PhantomData)) } } } pub mod as_btc { //! Serialize and deserialize [`Amount`](crate::Amount) as JSON numbers denominated in BTC. //! Use with `#[serde(with = "amount::serde::as_btc")]`. use serde::{Deserializer, Serializer}; use util::amount::serde::SerdeAmount; pub fn serialize(a: &A, s: S) -> Result { a.ser_btc(s) } pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result { A::des_btc(d) } pub mod opt { //! Serialize and deserialize [Option] as JSON numbers denominated in BTC. //! Use with `#[serde(default, with = "amount::serde::as_btc::opt")]`. use serde::{Deserializer, Serializer, de}; use util::amount::serde::SerdeAmountForOpt; use core::fmt; use core::marker::PhantomData; pub fn serialize( a: &Option, s: S, ) -> Result { match *a { Some(a) => a.ser_btc_opt(s), None => s.serialize_none(), } } pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>( d: D, ) -> Result, D::Error> { struct VisitOptAmt(PhantomData); impl<'de, X :SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "An Option") } fn visit_none(self) -> Result where E: de::Error { Ok(None) } fn visit_some(self, d: D) -> Result where D: Deserializer<'de>, { Ok(Some(X::des_btc(d)?)) } } d.deserialize_option(VisitOptAmt::(PhantomData)) } } } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "std")] use std::panic; use core::str::FromStr; #[cfg(feature = "serde")] use serde_test; #[test] fn add_sub_mul_div() { let sat = Amount::from_sat; let ssat = SignedAmount::from_sat; assert_eq!(sat(15) + sat(15), sat(30)); assert_eq!(sat(15) - sat(15), sat(0)); assert_eq!(sat(14) * 3, sat(42)); assert_eq!(sat(14) / 2, sat(7)); assert_eq!(sat(14) % 3, sat(2)); assert_eq!(ssat(15) - ssat(20), ssat(-5)); assert_eq!(ssat(-14) * 3, ssat(-42)); assert_eq!(ssat(-14) / 2, ssat(-7)); assert_eq!(ssat(-14) % 3, ssat(-2)); let mut b = ssat(-5); b += ssat(13); assert_eq!(b, ssat(8)); b -= ssat(3); assert_eq!(b, ssat(5)); b *= 6; assert_eq!(b, ssat(30)); b /= 3; assert_eq!(b, ssat(10)); b %= 3; assert_eq!(b, ssat(1)); } #[cfg(feature = "std")] #[test] fn test_overflows() { // panic on overflow let result = panic::catch_unwind(|| Amount::max_value() + Amount::from_sat(1)); assert!(result.is_err()); let result = panic::catch_unwind(|| Amount::from_sat(8446744073709551615) * 3); assert!(result.is_err()); } #[test] fn checked_arithmetic() { let sat = Amount::from_sat; let ssat = SignedAmount::from_sat; assert_eq!(sat(42).checked_add(sat(1)), Some(sat(43))); assert_eq!(SignedAmount::max_value().checked_add(ssat(1)), None); assert_eq!(SignedAmount::min_value().checked_sub(ssat(1)), None); assert_eq!(Amount::max_value().checked_add(sat(1)), None); assert_eq!(Amount::min_value().checked_sub(sat(1)), None); assert_eq!(sat(5).checked_sub(sat(3)), Some(sat(2))); assert_eq!(sat(5).checked_sub(sat(6)), None); assert_eq!(ssat(5).checked_sub(ssat(6)), Some(ssat(-1))); assert_eq!(sat(5).checked_rem(2), Some(sat(1))); assert_eq!(sat(5).checked_div(2), Some(sat(2))); // integer division assert_eq!(ssat(-6).checked_div(2), Some(ssat(-3))); assert_eq!(ssat(-5).positive_sub(ssat(3)), None); assert_eq!(ssat(5).positive_sub(ssat(-3)), None); assert_eq!(ssat(3).positive_sub(ssat(5)), None); assert_eq!(ssat(3).positive_sub(ssat(3)), Some(ssat(0))); assert_eq!(ssat(5).positive_sub(ssat(3)), Some(ssat(2))); } #[test] fn floating_point() { use super::Denomination as D; let f = Amount::from_float_in; let sf = SignedAmount::from_float_in; let sat = Amount::from_sat; let ssat = SignedAmount::from_sat; assert_eq!(f(11.22, D::Bitcoin), Ok(sat(1122000000))); assert_eq!(sf(-11.22, D::MilliBitcoin), Ok(ssat(-1122000))); assert_eq!(f(11.22, D::Bit), Ok(sat(1122))); assert_eq!(sf(-1000.0, D::MilliSatoshi), Ok(ssat(-1))); assert_eq!(f(0.0001234, D::Bitcoin), Ok(sat(12340))); assert_eq!(sf(-0.00012345, D::Bitcoin), Ok(ssat(-12345))); assert_eq!(f(-100.0, D::MilliSatoshi), Err(ParseAmountError::Negative)); assert_eq!(f(11.22, D::Satoshi), Err(ParseAmountError::TooPrecise)); assert_eq!(sf(-100.0, D::MilliSatoshi), Err(ParseAmountError::TooPrecise)); assert_eq!(sf(-100.0, D::MilliSatoshi), Err(ParseAmountError::TooPrecise)); assert_eq!(f(42.123456781, D::Bitcoin), Err(ParseAmountError::TooPrecise)); assert_eq!(sf(-184467440738.0, D::Bitcoin), Err(ParseAmountError::TooBig)); assert_eq!(f(18446744073709551617.0, D::Satoshi), Err(ParseAmountError::TooBig)); assert_eq!( f(SignedAmount::max_value().to_float_in(D::Satoshi) + 1.0, D::Satoshi), Err(ParseAmountError::TooBig) ); assert_eq!( f(Amount::max_value().to_float_in(D::Satoshi) + 1.0, D::Satoshi), Err(ParseAmountError::TooBig) ); let btc = move |f| SignedAmount::from_btc(f).unwrap(); assert_eq!(btc(2.5).to_float_in(D::Bitcoin), 2.5); assert_eq!(btc(-2.5).to_float_in(D::MilliBitcoin), -2500.0); assert_eq!(btc(2.5).to_float_in(D::Satoshi), 250000000.0); assert_eq!(btc(-2.5).to_float_in(D::MilliSatoshi), -250000000000.0); let btc = move |f| Amount::from_btc(f).unwrap(); assert_eq!(&btc(0.0012).to_float_in(D::Bitcoin).to_string(), "0.0012") } #[test] fn parsing() { use super::ParseAmountError as E; let btc = Denomination::Bitcoin; let sat = Denomination::Satoshi; let p = Amount::from_str_in; let sp = SignedAmount::from_str_in; assert_eq!(p("x", btc), Err(E::InvalidCharacter('x'))); assert_eq!(p("-", btc), Err(E::InvalidFormat)); assert_eq!(sp("-", btc), Err(E::InvalidFormat)); assert_eq!(p("-1.0x", btc), Err(E::InvalidCharacter('x'))); assert_eq!(p("0.0 ", btc), Err(ParseAmountError::InvalidCharacter(' '))); assert_eq!(p("0.000.000", btc), Err(E::InvalidFormat)); let more_than_max = format!("1{}", Amount::max_value()); assert_eq!(p(&more_than_max, btc), Err(E::TooBig)); assert_eq!(p("0.000000042", btc), Err(E::TooPrecise)); assert_eq!(p("1", btc), Ok(Amount::from_sat(1_000_000_00))); assert_eq!(sp("-.5", btc), Ok(SignedAmount::from_sat(-500_000_00))); assert_eq!(p("1.1", btc), Ok(Amount::from_sat(1_100_000_00))); assert_eq!(p("100", sat), Ok(Amount::from_sat(100))); assert_eq!(p("55", sat), Ok(Amount::from_sat(55))); assert_eq!(p("5500000000000000000", sat), Ok(Amount::from_sat(5_500_000_000_000_000_000))); // Should this even pass? assert_eq!(p("5500000000000000000.", sat), Ok(Amount::from_sat(5_500_000_000_000_000_000))); assert_eq!( p("12345678901.12345678", btc), Ok(Amount::from_sat(12_345_678_901__123_456_78)) ); // make sure satoshi > i64::max_value() is checked. let amount = Amount::from_sat(i64::max_value() as u64); assert_eq!(Amount::from_str_in(&amount.to_string_in(sat), sat), Ok(amount)); assert_eq!(Amount::from_str_in(&(amount+Amount(1)).to_string_in(sat), sat), Err(E::TooBig)); assert_eq!(p("12.000", Denomination::MilliSatoshi), Err(E::TooPrecise)); // exactly 50 chars. assert_eq!(p("100000000000000.0000000000000000000000000000000000", Denomination::Bitcoin), Err(E::TooBig)); // more than 50 chars. assert_eq!(p("100000000000000.00000000000000000000000000000000000", Denomination::Bitcoin), Err(E::InputTooLarge)); } #[test] fn to_string() { use super::Denomination as D; assert_eq!(Amount::ONE_BTC.to_string_in(D::Bitcoin), "1.00000000"); assert_eq!(Amount::ONE_BTC.to_string_in(D::Satoshi), "100000000"); assert_eq!(Amount::ONE_SAT.to_string_in(D::Bitcoin), "0.00000001"); assert_eq!(SignedAmount::from_sat(-42).to_string_in(D::Bitcoin), "-0.00000042"); assert_eq!(Amount::ONE_BTC.to_string_with_denomination(D::Bitcoin), "1.00000000 BTC"); assert_eq!(Amount::ONE_SAT.to_string_with_denomination(D::MilliSatoshi), "1000 msat"); assert_eq!( SignedAmount::ONE_BTC.to_string_with_denomination(D::Satoshi), "100000000 satoshi" ); assert_eq!(Amount::ONE_SAT.to_string_with_denomination(D::Bitcoin), "0.00000001 BTC"); assert_eq!( SignedAmount::from_sat(-42).to_string_with_denomination(D::Bitcoin), "-0.00000042 BTC" ); } #[test] fn test_unsigned_signed_conversion() { use super::ParseAmountError as E; let sa = SignedAmount::from_sat; let ua = Amount::from_sat; assert_eq!(Amount::max_value().to_signed(), Err(E::TooBig)); assert_eq!(ua(i64::max_value() as u64).to_signed(), Ok(sa(i64::max_value()))); assert_eq!(ua(0).to_signed(), Ok(sa(0))); assert_eq!(ua(1).to_signed(), Ok( sa(1))); assert_eq!(ua(1).to_signed(), Ok(sa(1))); assert_eq!(ua(i64::max_value() as u64 + 1).to_signed(), Err(E::TooBig)); assert_eq!(sa(-1).to_unsigned(), Err(E::Negative)); assert_eq!(sa(i64::max_value()).to_unsigned(), Ok(ua(i64::max_value() as u64))); assert_eq!(sa(0).to_unsigned().unwrap().to_signed(), Ok(sa(0))); assert_eq!(sa(1).to_unsigned().unwrap().to_signed(), Ok(sa(1))); assert_eq!(sa(i64::max_value()).to_unsigned().unwrap().to_signed(), Ok(sa(i64::max_value()))); } #[test] fn from_str() { use super::ParseAmountError as E; let p = Amount::from_str; let sp = SignedAmount::from_str; assert_eq!(p("x BTC"), Err(E::InvalidCharacter('x'))); assert_eq!(p("5 BTC BTC"), Err(E::InvalidFormat)); assert_eq!(p("5 5 BTC"), Err(E::InvalidFormat)); assert_eq!(p("5 BCH"), Err(E::UnknownDenomination("BCH".to_owned()))); assert_eq!(p("-1 BTC"), Err(E::Negative)); assert_eq!(p("-0.0 BTC"), Err(E::Negative)); assert_eq!(p("0.123456789 BTC"), Err(E::TooPrecise)); assert_eq!(sp("-0.1 satoshi"), Err(E::TooPrecise)); assert_eq!(p("0.123456 mBTC"), Err(E::TooPrecise)); assert_eq!(sp("-1.001 bits"), Err(E::TooPrecise)); assert_eq!(sp("-200000000000 BTC"), Err(E::TooBig)); assert_eq!(p("18446744073709551616 sat"), Err(E::TooBig)); assert_eq!(sp("0 msat"), Err(E::TooPrecise)); assert_eq!(sp("-0 msat"), Err(E::TooPrecise)); assert_eq!(sp("000 msat"), Err(E::TooPrecise)); assert_eq!(sp("-000 msat"), Err(E::TooPrecise)); assert_eq!(p("0 msat"), Err(E::TooPrecise)); assert_eq!(p("-0 msat"), Err(E::TooPrecise)); assert_eq!(p("000 msat"), Err(E::TooPrecise)); assert_eq!(p("-000 msat"), Err(E::TooPrecise)); assert_eq!(p(".5 bits"), Ok(Amount::from_sat(50))); assert_eq!(sp("-.5 bits"), Ok(SignedAmount::from_sat(-50))); assert_eq!(p("0.00253583 BTC"), Ok(Amount::from_sat(253583))); assert_eq!(sp("-5 satoshi"), Ok(SignedAmount::from_sat(-5))); assert_eq!(p("0.10000000 BTC"), Ok(Amount::from_sat(100_000_00))); assert_eq!(sp("-100 bits"), Ok(SignedAmount::from_sat(-10_000))); } #[test] fn to_from_string_in() { use super::Denomination as D; let ua_str = Amount::from_str_in; let ua_sat = Amount::from_sat; let sa_str = SignedAmount::from_str_in; let sa_sat = SignedAmount::from_sat; assert_eq!("0.50", Amount::from_sat(50).to_string_in(D::Bit)); assert_eq!("-0.50", SignedAmount::from_sat(-50).to_string_in(D::Bit)); assert_eq!("0.00253583", Amount::from_sat(253583).to_string_in(D::Bitcoin)); assert_eq!("-5", SignedAmount::from_sat(-5).to_string_in(D::Satoshi)); assert_eq!("0.10000000", Amount::from_sat(100_000_00).to_string_in(D::Bitcoin)); assert_eq!("-100.00", SignedAmount::from_sat(-10_000).to_string_in(D::Bit)); assert_eq!("2535830", Amount::from_sat(253583).to_string_in(D::NanoBitcoin)); assert_eq!("-100000", SignedAmount::from_sat(-10_000).to_string_in(D::NanoBitcoin)); assert_eq!("2535830000", Amount::from_sat(253583).to_string_in(D::PicoBitcoin)); assert_eq!("-100000000", SignedAmount::from_sat(-10_000).to_string_in(D::PicoBitcoin)); assert_eq!(ua_str(&ua_sat(0).to_string_in(D::Satoshi), D::Satoshi), Ok(ua_sat(0))); assert_eq!(ua_str(&ua_sat(500).to_string_in(D::Bitcoin), D::Bitcoin), Ok(ua_sat(500))); assert_eq!(ua_str(&ua_sat(21_000_000).to_string_in(D::Bit), D::Bit), Ok(ua_sat(21_000_000))); assert_eq!(ua_str(&ua_sat(1).to_string_in(D::MicroBitcoin), D::MicroBitcoin), Ok(ua_sat(1))); assert_eq!(ua_str(&ua_sat(1_000_000_000_000).to_string_in(D::MilliBitcoin), D::MilliBitcoin), Ok(ua_sat(1_000_000_000_000))); assert_eq!(ua_str(&ua_sat(u64::max_value()).to_string_in(D::MilliBitcoin), D::MilliBitcoin), Err(ParseAmountError::TooBig)); assert_eq!(sa_str(&sa_sat(-1).to_string_in(D::MicroBitcoin), D::MicroBitcoin), Ok(sa_sat(-1))); assert_eq!(sa_str(&sa_sat(i64::max_value()).to_string_in(D::Satoshi), D::MicroBitcoin), Err(ParseAmountError::TooBig)); // Test an overflow bug in `abs()` assert_eq!(sa_str(&sa_sat(i64::min_value()).to_string_in(D::Satoshi), D::MicroBitcoin), Err(ParseAmountError::TooBig)); assert_eq!(sa_str(&sa_sat(-1).to_string_in(D::NanoBitcoin), D::NanoBitcoin), Ok(sa_sat(-1))); assert_eq!(sa_str(&sa_sat(i64::max_value()).to_string_in(D::Satoshi), D::NanoBitcoin), Err(ParseAmountError::TooPrecise)); assert_eq!(sa_str(&sa_sat(i64::min_value()).to_string_in(D::Satoshi), D::NanoBitcoin), Err(ParseAmountError::TooPrecise)); assert_eq!(sa_str(&sa_sat(-1).to_string_in(D::PicoBitcoin), D::PicoBitcoin), Ok(sa_sat(-1))); assert_eq!(sa_str(&sa_sat(i64::max_value()).to_string_in(D::Satoshi), D::PicoBitcoin), Err(ParseAmountError::TooPrecise)); assert_eq!(sa_str(&sa_sat(i64::min_value()).to_string_in(D::Satoshi), D::PicoBitcoin), Err(ParseAmountError::TooPrecise)); } #[test] fn to_string_with_denomination_from_str_roundtrip() { use super::Denomination as D; let amt = Amount::from_sat(42); let denom = Amount::to_string_with_denomination; assert_eq!(Amount::from_str(&denom(amt, D::Bitcoin)), Ok(amt)); assert_eq!(Amount::from_str(&denom(amt, D::MilliBitcoin)), Ok(amt)); assert_eq!(Amount::from_str(&denom(amt, D::MicroBitcoin)), Ok(amt)); assert_eq!(Amount::from_str(&denom(amt, D::Bit)), Ok(amt)); assert_eq!(Amount::from_str(&denom(amt, D::Satoshi)), Ok(amt)); assert_eq!(Amount::from_str(&denom(amt, D::NanoBitcoin)), Ok(amt)); assert_eq!(Amount::from_str(&denom(amt, D::MilliSatoshi)), Ok(amt)); assert_eq!(Amount::from_str(&denom(amt, D::PicoBitcoin)), Ok(amt)); assert_eq!(Amount::from_str("42 satoshi BTC"), Err(ParseAmountError::InvalidFormat)); assert_eq!(SignedAmount::from_str("-42 satoshi BTC"), Err(ParseAmountError::InvalidFormat)); } #[cfg(feature = "serde")] #[test] fn serde_as_sat() { #[derive(Serialize, Deserialize, PartialEq, Debug)] struct T { #[serde(with = "::util::amount::serde::as_sat")] pub amt: Amount, #[serde(with = "::util::amount::serde::as_sat")] pub samt: SignedAmount, } serde_test::assert_tokens( &T { amt: Amount::from_sat(123456789), samt: SignedAmount::from_sat(-123456789), }, &[ serde_test::Token::Struct { name: "T", len: 2, }, serde_test::Token::Str("amt"), serde_test::Token::U64(123456789), serde_test::Token::Str("samt"), serde_test::Token::I64(-123456789), serde_test::Token::StructEnd, ], ); } #[cfg(feature = "serde")] #[test] fn serde_as_btc() { use serde_json; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct T { #[serde(with = "::util::amount::serde::as_btc")] pub amt: Amount, #[serde(with = "::util::amount::serde::as_btc")] pub samt: SignedAmount, } let orig = T { amt: Amount::from_sat(21_000_000__000_000_01), samt: SignedAmount::from_sat(-21_000_000__000_000_01), }; let json = "{\"amt\": 21000000.00000001, \ \"samt\": -21000000.00000001}"; let t: T = serde_json::from_str(&json).unwrap(); assert_eq!(t, orig); let value: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(t, serde_json::from_value(value).unwrap()); // errors let t: Result = serde_json::from_str("{\"amt\": 1000000.000000001, \"samt\": 1}"); assert!(t.unwrap_err().to_string().contains(&ParseAmountError::TooPrecise.to_string())); let t: Result = serde_json::from_str("{\"amt\": -1, \"samt\": 1}"); assert!(t.unwrap_err().to_string().contains(&ParseAmountError::Negative.to_string())); } #[cfg(feature = "serde")] #[test] fn serde_as_btc_opt() { use serde_json; #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] struct T { #[serde(default, with = "::util::amount::serde::as_btc::opt")] pub amt: Option, #[serde(default, with = "::util::amount::serde::as_btc::opt")] pub samt: Option, } let with = T { amt: Some(Amount::from_sat(2__500_000_00)), samt: Some(SignedAmount::from_sat(-2__500_000_00)), }; let without = T { amt: None, samt: None, }; // Test Roundtripping for s in [&with, &without].iter() { let v = serde_json::to_string(s).unwrap(); let w : T = serde_json::from_str(&v).unwrap(); assert_eq!(w, **s); } let t: T = serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap(); assert_eq!(t, with); let t: T = serde_json::from_str("{}").unwrap(); assert_eq!(t, without); let value_with: serde_json::Value = serde_json::from_str("{\"amt\": 2.5, \"samt\": -2.5}").unwrap(); assert_eq!(with, serde_json::from_value(value_with).unwrap()); let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); assert_eq!(without, serde_json::from_value(value_without).unwrap()); } #[cfg(feature = "serde")] #[test] fn serde_as_sat_opt() { use serde_json; #[derive(Serialize, Deserialize, PartialEq, Debug, Eq)] struct T { #[serde(default, with = "::util::amount::serde::as_sat::opt")] pub amt: Option, #[serde(default, with = "::util::amount::serde::as_sat::opt")] pub samt: Option, } let with = T { amt: Some(Amount::from_sat(2__500_000_00)), samt: Some(SignedAmount::from_sat(-2__500_000_00)), }; let without = T { amt: None, samt: None, }; // Test Roundtripping for s in [&with, &without].iter() { let v = serde_json::to_string(s).unwrap(); let w : T = serde_json::from_str(&v).unwrap(); assert_eq!(w, **s); } let t: T = serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap(); assert_eq!(t, with); let t: T = serde_json::from_str("{}").unwrap(); assert_eq!(t, without); let value_with: serde_json::Value = serde_json::from_str("{\"amt\": 250000000, \"samt\": -250000000}").unwrap(); assert_eq!(with, serde_json::from_value(value_with).unwrap()); let value_without: serde_json::Value = serde_json::from_str("{}").unwrap(); assert_eq!(without, serde_json::from_value(value_without).unwrap()); } #[test] fn sum_amounts() { assert_eq!(Amount::from_sat(0), vec![].into_iter().sum::()); assert_eq!(SignedAmount::from_sat(0), vec![].into_iter().sum::()); let amounts = vec![ Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21) ]; let sum = amounts.into_iter().sum::(); assert_eq!(Amount::from_sat(1400), sum); let amounts = vec![ SignedAmount::from_sat(-42), SignedAmount::from_sat(1337), SignedAmount::from_sat(21) ]; let sum = amounts.into_iter().sum::(); assert_eq!(SignedAmount::from_sat(1316), sum); } #[test] fn checked_sum_amounts() { assert_eq!(Some(Amount::from_sat(0)), vec![].into_iter().checked_sum()); assert_eq!(Some(SignedAmount::from_sat(0)), vec![].into_iter().checked_sum()); let amounts = vec![ Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21) ]; let sum = amounts.into_iter().checked_sum(); assert_eq!(Some(Amount::from_sat(1400)), sum); let amounts = vec![ Amount::from_sat(u64::max_value()), Amount::from_sat(1337), Amount::from_sat(21) ]; let sum = amounts.into_iter().checked_sum(); assert_eq!(None, sum); let amounts = vec![ SignedAmount::from_sat(i64::min_value()), SignedAmount::from_sat(-1), SignedAmount::from_sat(21) ]; let sum = amounts.into_iter().checked_sum(); assert_eq!(None, sum); let amounts = vec![ SignedAmount::from_sat(i64::max_value()), SignedAmount::from_sat(1), SignedAmount::from_sat(21) ]; let sum = amounts.into_iter().checked_sum(); assert_eq!(None, sum); let amounts = vec![ SignedAmount::from_sat(42), SignedAmount::from_sat(3301), SignedAmount::from_sat(21) ]; let sum = amounts.into_iter().checked_sum(); assert_eq!(Some(SignedAmount::from_sat(3364)), sum); } #[test] fn denomination_string_acceptable_forms() { // Non-exhaustive list of valid forms. let valid = vec!["BTC", "btc", "mBTC", "mbtc", "uBTC", "ubtc", "SATOSHI","Satoshi", "Satoshis", "satoshis", "SAT", "Sat", "sats", "bit", "bits", "nBTC", "pBTC"]; for denom in valid.iter() { assert!(Denomination::from_str(denom).is_ok()); } } #[test] fn disallow_confusing_forms() { // Non-exhaustive list of confusing forms. let confusing = vec!["Msat", "Msats", "MSAT", "MSATS", "MSat", "MSats", "MBTC", "Mbtc", "PBTC"]; for denom in confusing.iter() { match Denomination::from_str(denom) { Ok(_) => panic!("from_str should error for {}", denom), Err(ParseAmountError::PossiblyConfusingDenomination(_)) => {}, Err(e) => panic!("unexpected error: {}", e), } } } #[test] fn disallow_unknown_denomination() { // Non-exhaustive list of unknown forms. let unknown = vec!["NBTC", "UBTC", "ABC", "abc"]; for denom in unknown.iter() { match Denomination::from_str(denom) { Ok(_) => panic!("from_str should error for {}", denom), Err(ParseAmountError::UnknownDenomination(_)) => {}, Err(e) => panic!("unexpected error: {}", e), } } } }