diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs index d1ec3c58f..eaa909142 100644 --- a/units/src/amount/mod.rs +++ b/units/src/amount/mod.rs @@ -11,6 +11,7 @@ pub mod serde; #[cfg(test)] mod tests; +mod unsigned; #[cfg(kani)] mod verification; @@ -20,21 +21,20 @@ use core::cmp::Ordering; use core::str::FromStr; use core::{default, fmt, ops}; -#[cfg(feature = "serde")] -use ::serde::{Deserialize, Serialize}; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; use self::error::MissingDigitsKind; -#[cfg(feature = "alloc")] -use crate::{FeeRate, Weight}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] -pub use self::error::{ - InputTooLargeError, InvalidCharacterError, MissingDenominationError, MissingDigitsError, - OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError, - PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError, +pub use self::{ + error::{ + InputTooLargeError, InvalidCharacterError, MissingDenominationError, MissingDigitsError, + OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError, + PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError, + }, + unsigned::Amount, }; /// A set of denominations in which amounts can be expressed. @@ -499,375 +499,6 @@ fn fmt_satoshi_in( Ok(()) } -/// An amount. -/// -/// The [`Amount`] type can be used to express Bitcoin amounts that support -/// 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)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -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 = Self::from_int_btc(1); - /// The maximum value allowed as an amount. Useful for sanity checking. - pub const MAX_MONEY: Amount = Self::from_int_btc(21_000_000); - /// The minimum value of an amount. - pub const MIN: Amount = Amount::ZERO; - /// The maximum value of an amount. - pub const MAX: Amount = Amount(u64::MAX); - /// The number of bytes that an amount contributes to the size of a transaction. - pub const SIZE: usize = 8; // Serialized length of a u64. - - /// Creates an [`Amount`] with satoshi precision and the given number of satoshis. - pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) } - - /// Gets the number of satoshis in this [`Amount`]. - pub const fn to_sat(self) -> u64 { self.0 } - - /// Converts from a value expressing bitcoins to an [`Amount`]. - #[cfg(feature = "alloc")] - pub fn from_btc(btc: f64) -> Result { - Amount::from_float_in(btc, Denomination::Bitcoin) - } - - /// Converts from a value expressing integer values of bitcoins to an [`Amount`] - /// in const context. - /// - /// # Panics - /// - /// The function panics if the argument multiplied by the number of sats - /// per bitcoin overflows a u64 type. - pub const fn from_int_btc(btc: u64) -> Amount { - match btc.checked_mul(100_000_000) { - Some(amount) => Amount::from_sat(amount), - None => panic!("checked_mul overflowed"), - } - } - - /// Parses 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).map_err(|error| error.convert(false))?; - if negative { - return Err(ParseAmountError::OutOfRange(OutOfRangeError::negative())); - } - 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 (amt, denom) = split_amount_and_denomination(s)?; - Amount::from_str_in(amt, denom).map_err(Into::into) - } - - /// Expresses this [`Amount`] as a floating-point value in the given denomination. - /// - /// Please be aware of the risk of using floating-point numbers. - #[cfg(feature = "alloc")] - pub fn to_float_in(self, denom: Denomination) -> f64 { - self.to_string_in(denom).parse::().unwrap() - } - - /// Expresses this [`Amount`] as a floating-point value in Bitcoin. - /// - /// Please be aware of the risk of using floating-point numbers. - /// - /// # Examples - /// - /// ``` - /// # use bitcoin_units::amount::{Amount, Denomination}; - /// let amount = Amount::from_sat(100_000); - /// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin)) - /// ``` - #[cfg(feature = "alloc")] - pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) } - - /// Converts this [`Amount`] in floating-point notation with a given - /// denomination. - /// - /// # Errors - /// - /// If the amount is too big, too precise or negative. - /// - /// Please be aware of the risk of using floating-point numbers. - #[cfg(feature = "alloc")] - pub fn from_float_in(value: f64, denom: Denomination) -> Result { - if value < 0.0 { - return Err(OutOfRangeError::negative().into()); - } - // 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) - } - - /// Creates an object that implements [`fmt::Display`] using specified denomination. - pub fn display_in(self, denomination: Denomination) -> Display { - Display { - sats_abs: self.to_sat(), - is_negative: false, - style: DisplayStyle::FixedDenomination { denomination, show_denomination: false }, - } - } - - /// Creates an object that implements [`fmt::Display`] dynamically selecting denomination. - /// - /// This will use BTC for values greater than or equal to 1 BTC and satoshis otherwise. To - /// avoid confusion the denomination is always shown. - pub fn display_dynamic(self) -> Display { - Display { - sats_abs: self.to_sat(), - is_negative: false, - style: DisplayStyle::DynamicDenomination, - } - } - - /// Formats the value of this [`Amount`] in the given denomination. - /// - /// Does not include the denomination. - #[rustfmt::skip] - #[deprecated(since = "TBD", note = "use `display_in()` instead")] - pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result { - fmt_satoshi_in(self.to_sat(), false, f, denom, false, FormatOptions::default()) - } - - /// Returns a formatted string of this [`Amount`] in the given denomination. - /// - /// Does not include the denomination. - #[cfg(feature = "alloc")] - pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() } - - /// Returns a formatted string of this [`Amount`] in the given denomination, - /// suffixed with the abbreviation for the denomination. - #[cfg(feature = "alloc")] - pub fn to_string_with_denomination(self, denom: Denomination) -> String { - self.display_in(denom).show_denomination().to_string() - } - - // 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 weight division. - /// - /// Be aware that integer division loses the remainder if no exact division - /// can be made. This method rounds up ensuring the transaction fee-rate is - /// sufficient. If you wish to round-down, use the unchecked version instead. - /// - /// [`None`] is returned if an overflow occurred. - #[cfg(feature = "alloc")] - pub fn checked_div_by_weight(self, rhs: Weight) -> Option { - let sats = self.0.checked_mul(1000)?; - let wu = rhs.to_wu(); - - let fee_rate = sats.checked_add(wu.checked_sub(1)?)?.checked_div(wu)?; - Some(FeeRate::from_sat_per_kwu(fee_rate)) - } - - /// Checked remainder. - /// - /// Returns [`None`] if overflow occurred. - pub fn checked_rem(self, rhs: u64) -> Option { self.0.checked_rem(rhs).map(Amount) } - - /// Unchecked addition. - /// - /// Computes `self + rhs`. - /// - /// # Panics - /// - /// On overflow, panics in debug mode, wraps in release mode. - pub fn unchecked_add(self, rhs: Amount) -> Amount { Self(self.0 + rhs.0) } - - /// Unchecked subtraction. - /// - /// Computes `self - rhs`. - /// - /// # Panics - /// - /// On overflow, panics in debug mode, wraps in release mode. - pub fn unchecked_sub(self, rhs: Amount) -> Amount { Self(self.0 - rhs.0) } - - /// Converts to a signed amount. - pub fn to_signed(self) -> Result { - if self.to_sat() > SignedAmount::MAX.to_sat() as u64 { - Err(OutOfRangeError::too_big(true)) - } else { - Ok(SignedAmount::from_sat(self.to_sat() as i64)) - } - } -} - -#[cfg(feature = "arbitrary")] -impl<'a> Arbitrary<'a> for Amount { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - let a = u64::arbitrary(u)?; - Ok(Amount(a)) - } -} - -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, "{} SAT", self.to_sat()) } -} - -// 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 { - fmt::Display::fmt(&self.display_in(Denomination::Bitcoin).show_denomination(), f) - } -} - -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 = ParseError; - - /// Parses a string slice where the slice includes a denomination. - /// - /// If the string slice is zero, then no denomination is required. - /// - /// # Returns - /// - /// `Ok(Amount)` if the string amount and denomination parse successfully, - /// otherwise, return `Err(ParseError)`. - fn from_str(s: &str) -> Result { - let result = Amount::from_str_with_denomination(s); - - match result { - Err(ParseError::MissingDenomination(_)) => { - let d = Amount::from_str_in(s, Denomination::Satoshi); - - if d == Ok(Amount::ZERO) { - Ok(Amount::ZERO) - } else { - result - } - } - _ => result, - } - } -} - -impl TryFrom for Amount { - type Error = OutOfRangeError; - - fn try_from(value: SignedAmount) -> Result { value.to_unsigned() } -} - -impl core::iter::Sum for Amount { - fn sum>(iter: I) -> Self { - let sats: u64 = iter.map(|amt| amt.0).sum(); - Amount::from_sat(sats) - } -} - /// A helper/builder that displays amount with specified settings. /// /// This provides richer interface than [`fmt::Formatter`]: @@ -1083,7 +714,7 @@ impl SignedAmount { pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) } /// Get the absolute value of this [`SignedAmount`] returning [`Amount`]. - pub fn unsigned_abs(self) -> Amount { Amount(self.0.unsigned_abs()) } + pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.0.unsigned_abs()) } /// Returns a number representing sign of this [`SignedAmount`]. /// diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs new file mode 100644 index 000000000..f638a1e13 --- /dev/null +++ b/units/src/amount/unsigned.rs @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! An unsigned bitcoin amount. + +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; +use core::str::FromStr; +use core::{default, fmt, ops}; + +#[cfg(feature = "serde")] +use ::serde::{Deserialize, Serialize}; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; + +use super::{ + fmt_satoshi_in, parse_signed_to_satoshi, split_amount_and_denomination, Denomination, Display, + DisplayStyle, FormatOptions, OutOfRangeError, ParseAmountError, ParseError, SignedAmount, +}; +#[cfg(feature = "alloc")] +use crate::{FeeRate, Weight}; + +/// An amount. +/// +/// The [`Amount`] type can be used to express Bitcoin amounts that support +/// 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)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +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 = Self::from_int_btc(1); + /// The maximum value allowed as an amount. Useful for sanity checking. + pub const MAX_MONEY: Amount = Self::from_int_btc(21_000_000); + /// The minimum value of an amount. + pub const MIN: Amount = Amount::ZERO; + /// The maximum value of an amount. + pub const MAX: Amount = Amount(u64::MAX); + /// The number of bytes that an amount contributes to the size of a transaction. + pub const SIZE: usize = 8; // Serialized length of a u64. + + /// Creates an [`Amount`] with satoshi precision and the given number of satoshis. + pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) } + + /// Gets the number of satoshis in this [`Amount`]. + pub const fn to_sat(self) -> u64 { self.0 } + + /// Converts from a value expressing bitcoins to an [`Amount`]. + #[cfg(feature = "alloc")] + pub fn from_btc(btc: f64) -> Result { + Amount::from_float_in(btc, Denomination::Bitcoin) + } + + /// Converts from a value expressing integer values of bitcoins to an [`Amount`] + /// in const context. + /// + /// # Panics + /// + /// The function panics if the argument multiplied by the number of sats + /// per bitcoin overflows a u64 type. + pub const fn from_int_btc(btc: u64) -> Amount { + match btc.checked_mul(100_000_000) { + Some(amount) => Amount::from_sat(amount), + None => panic!("checked_mul overflowed"), + } + } + + /// Parses 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).map_err(|error| error.convert(false))?; + if negative { + return Err(ParseAmountError::OutOfRange(OutOfRangeError::negative())); + } + 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 (amt, denom) = split_amount_and_denomination(s)?; + Amount::from_str_in(amt, denom).map_err(Into::into) + } + + /// Expresses this [`Amount`] as a floating-point value in the given denomination. + /// + /// Please be aware of the risk of using floating-point numbers. + #[cfg(feature = "alloc")] + pub fn to_float_in(self, denom: Denomination) -> f64 { + self.to_string_in(denom).parse::().unwrap() + } + + /// Expresses this [`Amount`] as a floating-point value in Bitcoin. + /// + /// Please be aware of the risk of using floating-point numbers. + /// + /// # Examples + /// + /// ``` + /// # use bitcoin_units::amount::{Amount, Denomination}; + /// let amount = Amount::from_sat(100_000); + /// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin)) + /// ``` + #[cfg(feature = "alloc")] + pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) } + + /// Converts this [`Amount`] in floating-point notation with a given + /// denomination. + /// + /// # Errors + /// + /// If the amount is too big, too precise or negative. + /// + /// Please be aware of the risk of using floating-point numbers. + #[cfg(feature = "alloc")] + pub fn from_float_in(value: f64, denom: Denomination) -> Result { + if value < 0.0 { + return Err(OutOfRangeError::negative().into()); + } + // 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) + } + + /// Creates an object that implements [`fmt::Display`] using specified denomination. + pub fn display_in(self, denomination: Denomination) -> Display { + Display { + sats_abs: self.to_sat(), + is_negative: false, + style: DisplayStyle::FixedDenomination { denomination, show_denomination: false }, + } + } + + /// Creates an object that implements [`fmt::Display`] dynamically selecting denomination. + /// + /// This will use BTC for values greater than or equal to 1 BTC and satoshis otherwise. To + /// avoid confusion the denomination is always shown. + pub fn display_dynamic(self) -> Display { + Display { + sats_abs: self.to_sat(), + is_negative: false, + style: DisplayStyle::DynamicDenomination, + } + } + + /// Formats the value of this [`Amount`] in the given denomination. + /// + /// Does not include the denomination. + #[rustfmt::skip] + #[deprecated(since = "TBD", note = "use `display_in()` instead")] + pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result { + fmt_satoshi_in(self.to_sat(), false, f, denom, false, FormatOptions::default()) + } + + /// Returns a formatted string of this [`Amount`] in the given denomination. + /// + /// Does not include the denomination. + #[cfg(feature = "alloc")] + pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() } + + /// Returns a formatted string of this [`Amount`] in the given denomination, + /// suffixed with the abbreviation for the denomination. + #[cfg(feature = "alloc")] + pub fn to_string_with_denomination(self, denom: Denomination) -> String { + self.display_in(denom).show_denomination().to_string() + } + + // 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 weight division. + /// + /// Be aware that integer division loses the remainder if no exact division + /// can be made. This method rounds up ensuring the transaction fee-rate is + /// sufficient. If you wish to round-down, use the unchecked version instead. + /// + /// [`None`] is returned if an overflow occurred. + #[cfg(feature = "alloc")] + pub fn checked_div_by_weight(self, rhs: Weight) -> Option { + let sats = self.0.checked_mul(1000)?; + let wu = rhs.to_wu(); + + let fee_rate = sats.checked_add(wu.checked_sub(1)?)?.checked_div(wu)?; + Some(FeeRate::from_sat_per_kwu(fee_rate)) + } + + /// Checked remainder. + /// + /// Returns [`None`] if overflow occurred. + pub fn checked_rem(self, rhs: u64) -> Option { self.0.checked_rem(rhs).map(Amount) } + + /// Unchecked addition. + /// + /// Computes `self + rhs`. + /// + /// # Panics + /// + /// On overflow, panics in debug mode, wraps in release mode. + pub fn unchecked_add(self, rhs: Amount) -> Amount { Self(self.0 + rhs.0) } + + /// Unchecked subtraction. + /// + /// Computes `self - rhs`. + /// + /// # Panics + /// + /// On overflow, panics in debug mode, wraps in release mode. + pub fn unchecked_sub(self, rhs: Amount) -> Amount { Self(self.0 - rhs.0) } + + /// Converts to a signed amount. + pub fn to_signed(self) -> Result { + if self.to_sat() > SignedAmount::MAX.to_sat() as u64 { + Err(OutOfRangeError::too_big(true)) + } else { + Ok(SignedAmount::from_sat(self.to_sat() as i64)) + } + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Amount { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let a = u64::arbitrary(u)?; + Ok(Amount(a)) + } +} + +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, "{} SAT", self.to_sat()) } +} + +// 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 { + fmt::Display::fmt(&self.display_in(Denomination::Bitcoin).show_denomination(), f) + } +} + +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 = ParseError; + + /// Parses a string slice where the slice includes a denomination. + /// + /// If the string slice is zero, then no denomination is required. + /// + /// # Returns + /// + /// `Ok(Amount)` if the string amount and denomination parse successfully, + /// otherwise, return `Err(ParseError)`. + fn from_str(s: &str) -> Result { + let result = Amount::from_str_with_denomination(s); + + match result { + Err(ParseError::MissingDenomination(_)) => { + let d = Amount::from_str_in(s, Denomination::Satoshi); + + if d == Ok(Amount::ZERO) { + Ok(Amount::ZERO) + } else { + result + } + } + _ => result, + } + } +} + +impl TryFrom for Amount { + type Error = OutOfRangeError; + + fn try_from(value: SignedAmount) -> Result { value.to_unsigned() } +} + +impl core::iter::Sum for Amount { + fn sum>(iter: I) -> Self { + let sats: u64 = iter.map(|amt| amt.0).sum(); + Amount::from_sat(sats) + } +}