From d0d7a156047b1567bc065abba1454fd52a3d32d6 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 18 Mar 2025 14:28:18 +0000 Subject: [PATCH] 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. --- units/src/amount/signed.rs | 63 +++++++++++++++++++----------------- units/src/amount/unsigned.rs | 59 +++++++++++++++++---------------- 2 files changed, 64 insertions(+), 58 deletions(-) diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs index 262106673..069eceee0 100644 --- a/units/src/amount/signed.rs +++ b/units/src/amount/signed.rs @@ -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 { + 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 { - 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`]. /// diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs index 25533675f..d82c3f881 100644 --- a/units/src/amount/unsigned.rs +++ b/units/src/amount/unsigned.rs @@ -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 { + 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 { - 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