amount: move MIN/MAX constants and constructors inside the privacy boundary

It's conceptually a bit tortured to have an `Amount` type defined in a
private module, with an _unchecked method allowing you to set values out
of range, which needs to be used outside of the module to *define* the
range and the constructors that check it.

Move the constants and constructors inside the privacy module, where they
can be written directly. This is easier to understand and eliminates a couple
_unchecked calls.
This commit is contained in:
Andrew Poelstra 2025-03-18 14:28:18 +00:00
parent 004d073184
commit d0d7a15604
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 64 additions and 58 deletions

View File

@ -17,6 +17,8 @@ use super::{
};
mod encapsulate {
use super::OutOfRangeError;
/// A signed amount.
///
/// The [`SignedAmount`] type can be used to express Bitcoin amounts that support arithmetic and
@ -50,6 +52,11 @@ mod encapsulate {
pub struct SignedAmount(i64);
impl SignedAmount {
/// The maximum value of an amount.
pub const MAX: Self = Self(21_000_000 * 100_000_000);
/// The minimum value of an amount.
pub const MIN: Self = Self(-21_000_000 * 100_000_000);
/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
///
/// Caller to guarantee that `satoshi` is within valid range.
@ -74,6 +81,31 @@ mod encapsulate {
/// assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
/// ```
pub const fn to_sat(self) -> i64 { self.0 }
/// Constructs a new [`SignedAmount`] from the given number of satoshis.
///
/// # Errors
///
/// If `satoshi` is outside of valid range (see [`Self::MAX_MONEY`]).
///
/// # Examples
///
/// ```
/// # use bitcoin_units::{amount, SignedAmount};
/// # let sat = -100_000;
/// let amount = SignedAmount::from_sat(sat)?;
/// assert_eq!(amount.to_sat(), sat);
/// # Ok::<_, amount::OutOfRangeError>(())
/// ```
pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
if satoshi < Self::MIN.to_sat() {
Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
} else if satoshi > Self::MAX_MONEY.to_sat() {
Err(OutOfRangeError { is_signed: true, is_greater_than_max: true })
} else {
Ok(Self(satoshi))
}
}
}
}
#[doc(inline)]
@ -89,36 +121,7 @@ impl SignedAmount {
/// Exactly fifty bitcoin.
pub const FIFTY_BTC: Self = SignedAmount::from_btc_i16(50);
/// The maximum value allowed as an amount. Useful for sanity checking.
pub const MAX_MONEY: Self = SignedAmount::from_sat_unchecked(21_000_000 * 100_000_000);
/// The minimum value of an amount.
pub const MIN: Self = SignedAmount::from_sat_unchecked(-21_000_000 * 100_000_000);
/// The maximum value of an amount.
pub const MAX: Self = SignedAmount::MAX_MONEY;
/// Constructs a new [`SignedAmount`] from the given number of satoshis.
///
/// # Errors
///
/// If `satoshi` is outside of valid range (see [`Self::MAX_MONEY`]).
///
/// # Examples
///
/// ```
/// # use bitcoin_units::{amount, SignedAmount};
/// # let sat = -100_000;
/// let amount = SignedAmount::from_sat(sat)?;
/// assert_eq!(amount.to_sat(), sat);
/// # Ok::<_, amount::OutOfRangeError>(())
/// ```
pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
if satoshi < Self::MIN.to_sat() {
Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
} else if satoshi > Self::MAX_MONEY.to_sat() {
Err(OutOfRangeError { is_signed: true, is_greater_than_max: true })
} else {
Ok(SignedAmount::from_sat_unchecked(satoshi))
}
}
pub const MAX_MONEY: Self = Self::MAX;
/// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
///

View File

@ -17,6 +17,8 @@ use super::{
};
mod encapsulate {
use super::OutOfRangeError;
/// An amount.
///
/// The [`Amount`] type can be used to express Bitcoin amounts that support arithmetic and
@ -50,6 +52,11 @@ mod encapsulate {
pub struct Amount(u64);
impl Amount {
/// The maximum value of an amount.
pub const MAX: Self = Self(21_000_000 * 100_000_000);
/// The minimum value of an amount.
pub const MIN: Self = Self(0);
/// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
///
/// Caller to guarantee that `satoshi` is within valid range. See [`Self::MAX`].
@ -72,6 +79,29 @@ mod encapsulate {
/// assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
/// ```
pub const fn to_sat(self) -> u64 { self.0 }
/// Constructs a new [`Amount`] from the given number of satoshis.
///
/// # Errors
///
/// If `satoshi` is outside of valid range (greater than [`Self::MAX_MONEY`]).
///
/// # Examples
///
/// ```
/// # use bitcoin_units::{amount, Amount};
/// # let sat = 100_000;
/// let amount = Amount::from_sat(sat)?;
/// assert_eq!(amount.to_sat(), sat);
/// # Ok::<_, amount::OutOfRangeError>(())
/// ```
pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
if satoshi > Self::MAX_MONEY.to_sat() {
Err(OutOfRangeError { is_signed: false, is_greater_than_max: true })
} else {
Ok(Self(satoshi))
}
}
}
}
#[doc(inline)]
@ -87,37 +117,10 @@ impl Amount {
/// Exactly fifty bitcoin.
pub const FIFTY_BTC: Self = Amount::from_btc_u16(50);
/// The maximum value allowed as an amount. Useful for sanity checking.
pub const MAX_MONEY: Self = Amount::from_sat_unchecked(21_000_000 * 100_000_000);
/// The minimum value of an amount.
pub const MIN: Self = Amount::ZERO;
/// The maximum value of an amount.
pub const MAX: Self = Amount::MAX_MONEY;
pub const MAX_MONEY: Self = Amount::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.
/// Constructs a new [`Amount`] from the given number of satoshis.
///
/// # Errors
///
/// If `satoshi` is outside of valid range (greater than [`Self::MAX_MONEY`]).
///
/// # Examples
///
/// ```
/// # use bitcoin_units::{amount, Amount};
/// # let sat = 100_000;
/// let amount = Amount::from_sat(sat)?;
/// assert_eq!(amount.to_sat(), sat);
/// # Ok::<_, amount::OutOfRangeError>(())
/// ```
pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
if satoshi > Self::MAX_MONEY.to_sat() {
Err(OutOfRangeError { is_signed: false, is_greater_than_max: true })
} else {
Ok(Self::from_sat_unchecked(satoshi))
}
}
/// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
///
/// # Errors