amount: Move Amount to private unsigned module
In an effort to make the `amount` module more readable move the `Amount` type to a private submodule. Re-export everything so this is not a breaking change. Code move and re-exports only.
This commit is contained in:
parent
2d4c0fa6c1
commit
13f9fd1b77
|
@ -11,6 +11,7 @@ pub mod serde;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
mod unsigned;
|
||||||
#[cfg(kani)]
|
#[cfg(kani)]
|
||||||
mod verification;
|
mod verification;
|
||||||
|
|
||||||
|
@ -20,21 +21,20 @@ use core::cmp::Ordering;
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use core::{default, fmt, ops};
|
use core::{default, fmt, ops};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
use ::serde::{Deserialize, Serialize};
|
|
||||||
#[cfg(feature = "arbitrary")]
|
#[cfg(feature = "arbitrary")]
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
|
|
||||||
use self::error::MissingDigitsKind;
|
use self::error::MissingDigitsKind;
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use crate::{FeeRate, Weight};
|
|
||||||
|
|
||||||
#[rustfmt::skip] // Keep public re-exports separate.
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::error::{
|
pub use self::{
|
||||||
InputTooLargeError, InvalidCharacterError, MissingDenominationError, MissingDigitsError,
|
error::{
|
||||||
OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
|
InputTooLargeError, InvalidCharacterError, MissingDenominationError, MissingDigitsError,
|
||||||
PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
|
OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
|
||||||
|
PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
|
||||||
|
},
|
||||||
|
unsigned::Amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A set of denominations in which amounts can be expressed.
|
/// A set of denominations in which amounts can be expressed.
|
||||||
|
@ -499,375 +499,6 @@ fn fmt_satoshi_in(
|
||||||
Ok(())
|
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, ParseAmountError> {
|
|
||||||
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<Amount, ParseAmountError> {
|
|
||||||
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<Amount, ParseError> {
|
|
||||||
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::<f64>().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<Amount, ParseAmountError> {
|
|
||||||
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<Amount> {
|
|
||||||
self.0.checked_add(rhs.0).map(Amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checked subtraction.
|
|
||||||
///
|
|
||||||
/// Returns [`None`] if overflow occurred.
|
|
||||||
pub fn checked_sub(self, rhs: Amount) -> Option<Amount> {
|
|
||||||
self.0.checked_sub(rhs.0).map(Amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checked multiplication.
|
|
||||||
///
|
|
||||||
/// Returns [`None`] if overflow occurred.
|
|
||||||
pub fn checked_mul(self, rhs: u64) -> Option<Amount> { 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<Amount> { 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<FeeRate> {
|
|
||||||
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<Amount> { 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<SignedAmount, OutOfRangeError> {
|
|
||||||
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<Self> {
|
|
||||||
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<u64> for Amount {
|
|
||||||
type Output = Amount;
|
|
||||||
|
|
||||||
fn rem(self, modulus: u64) -> Self {
|
|
||||||
self.checked_rem(modulus).expect("Amount remainder error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::RemAssign<u64> for Amount {
|
|
||||||
fn rem_assign(&mut self, modulus: u64) { *self = *self % modulus }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::Mul<u64> for Amount {
|
|
||||||
type Output = Amount;
|
|
||||||
|
|
||||||
fn mul(self, rhs: u64) -> Self::Output {
|
|
||||||
self.checked_mul(rhs).expect("Amount multiplication error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::MulAssign<u64> for Amount {
|
|
||||||
fn mul_assign(&mut self, rhs: u64) { *self = *self * rhs }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::Div<u64> for Amount {
|
|
||||||
type Output = Amount;
|
|
||||||
|
|
||||||
fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).expect("Amount division error") }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::DivAssign<u64> 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<Self, Self::Err> {
|
|
||||||
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<SignedAmount> for Amount {
|
|
||||||
type Error = OutOfRangeError;
|
|
||||||
|
|
||||||
fn try_from(value: SignedAmount) -> Result<Self, Self::Error> { value.to_unsigned() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::iter::Sum for Amount {
|
|
||||||
fn sum<I: Iterator<Item = Self>>(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.
|
/// A helper/builder that displays amount with specified settings.
|
||||||
///
|
///
|
||||||
/// This provides richer interface than [`fmt::Formatter`]:
|
/// This provides richer interface than [`fmt::Formatter`]:
|
||||||
|
@ -1083,7 +714,7 @@ impl SignedAmount {
|
||||||
pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
|
pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
|
||||||
|
|
||||||
/// Get the absolute value of this [`SignedAmount`] returning [`Amount`].
|
/// 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`].
|
/// Returns a number representing sign of this [`SignedAmount`].
|
||||||
///
|
///
|
||||||
|
|
|
@ -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, ParseAmountError> {
|
||||||
|
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<Amount, ParseAmountError> {
|
||||||
|
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<Amount, ParseError> {
|
||||||
|
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::<f64>().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<Amount, ParseAmountError> {
|
||||||
|
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<Amount> {
|
||||||
|
self.0.checked_add(rhs.0).map(Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checked subtraction.
|
||||||
|
///
|
||||||
|
/// Returns [`None`] if overflow occurred.
|
||||||
|
pub fn checked_sub(self, rhs: Amount) -> Option<Amount> {
|
||||||
|
self.0.checked_sub(rhs.0).map(Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checked multiplication.
|
||||||
|
///
|
||||||
|
/// Returns [`None`] if overflow occurred.
|
||||||
|
pub fn checked_mul(self, rhs: u64) -> Option<Amount> { 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<Amount> { 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<FeeRate> {
|
||||||
|
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<Amount> { 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<SignedAmount, OutOfRangeError> {
|
||||||
|
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<Self> {
|
||||||
|
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<u64> for Amount {
|
||||||
|
type Output = Amount;
|
||||||
|
|
||||||
|
fn rem(self, modulus: u64) -> Self {
|
||||||
|
self.checked_rem(modulus).expect("Amount remainder error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::RemAssign<u64> for Amount {
|
||||||
|
fn rem_assign(&mut self, modulus: u64) { *self = *self % modulus }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Mul<u64> for Amount {
|
||||||
|
type Output = Amount;
|
||||||
|
|
||||||
|
fn mul(self, rhs: u64) -> Self::Output {
|
||||||
|
self.checked_mul(rhs).expect("Amount multiplication error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::MulAssign<u64> for Amount {
|
||||||
|
fn mul_assign(&mut self, rhs: u64) { *self = *self * rhs }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Div<u64> for Amount {
|
||||||
|
type Output = Amount;
|
||||||
|
|
||||||
|
fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).expect("Amount division error") }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::DivAssign<u64> 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<Self, Self::Err> {
|
||||||
|
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<SignedAmount> for Amount {
|
||||||
|
type Error = OutOfRangeError;
|
||||||
|
|
||||||
|
fn try_from(value: SignedAmount) -> Result<Self, Self::Error> { value.to_unsigned() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::iter::Sum for Amount {
|
||||||
|
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||||
|
let sats: u64 = iter.map(|amt| amt.0).sum();
|
||||||
|
Amount::from_sat(sats)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue