// 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_const(1); /// The maximum value allowed as an amount. Useful for sanity checking. pub const MAX_MONEY: Amount = Self::from_int_btc_const(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 a whole number of bitcoin to an [`Amount`]. /// /// # Errors /// /// The function errors if the argument multiplied by the number of sats /// per bitcoin overflows a `u64` type. pub fn from_int_btc(btc: u64) -> Result { match btc.checked_mul(100_000_000) { Some(amount) => Ok(Amount::from_sat(amount)), None => Err(OutOfRangeError { is_signed: false, is_greater_than_max: true, }) } } /// Converts from a value expressing a whole number of bitcoin 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_const(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 `amount / weight`. /// /// [`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)) } } } 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({} 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) } } #[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)) } }