diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs index 9d418a50c..4a69d718f 100644 --- a/bitcoin/examples/taproot-psbt.rs +++ b/bitcoin/examples/taproot-psbt.rs @@ -49,7 +49,7 @@ const UTXO_1: P2trUtxo = P2trUtxo { script_pubkey: UTXO_SCRIPT_PUBKEY, pubkey: UTXO_PUBKEY, master_fingerprint: UTXO_MASTER_FINGERPRINT, - amount_in_sats: Amount::from_int_btc(50), + amount_in_sats: Amount::from_int_btc_const(50), derivation_path: BIP86_DERIVATION_PATH, }; @@ -60,7 +60,7 @@ const UTXO_2: P2trUtxo = P2trUtxo { script_pubkey: UTXO_SCRIPT_PUBKEY, pubkey: UTXO_PUBKEY, master_fingerprint: UTXO_MASTER_FINGERPRINT, - amount_in_sats: Amount::from_int_btc(50), + amount_in_sats: Amount::from_int_btc_const(50), derivation_path: BIP86_DERIVATION_PATH, }; @@ -71,7 +71,7 @@ const UTXO_3: P2trUtxo = P2trUtxo { script_pubkey: UTXO_SCRIPT_PUBKEY, pubkey: UTXO_PUBKEY, master_fingerprint: UTXO_MASTER_FINGERPRINT, - amount_in_sats: Amount::from_int_btc(50), + amount_in_sats: Amount::from_int_btc_const(50), derivation_path: BIP86_DERIVATION_PATH, }; diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs index ed365f6ac..78219c73e 100644 --- a/units/src/amount/signed.rs +++ b/units/src/amount/signed.rs @@ -46,19 +46,49 @@ impl SignedAmount { /// The maximum value of an amount. pub const MAX: SignedAmount = SignedAmount(i64::MAX); - /// Create an [`SignedAmount`] with satoshi precision and the given number of satoshis. + /// Creates a [`SignedAmount`] with satoshi precision and the given number of satoshis. pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) } /// Gets the number of satoshis in this [`SignedAmount`]. pub const fn to_sat(self) -> i64 { self.0 } - /// Convert from a value expressing bitcoins to an [`SignedAmount`]. + /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]. #[cfg(feature = "alloc")] pub fn from_btc(btc: f64) -> Result { SignedAmount::from_float_in(btc, Denomination::Bitcoin) } - /// Parse a decimal string as a value in the given denomination. + /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]. + /// + /// # Errors + /// + /// The function errors if the argument multiplied by the number of sats + /// per bitcoin overflows an `i64` type. + pub fn from_int_btc(btc: i64) -> Result { + match btc.checked_mul(100_000_000) { + Some(amount) => Ok(SignedAmount::from_sat(amount)), + None => Err(OutOfRangeError { + is_signed: true, + is_greater_than_max: true, + }) + } + } + + /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`] + /// in const context. + /// + /// # Panics + /// + /// The function panics if the argument multiplied by the number of sats + /// per bitcoin overflows an `i64` type. + pub const fn from_int_btc_const(btc: i64) -> SignedAmount { + match btc.checked_mul(100_000_000) { + Some(amount) => SignedAmount::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`]. @@ -75,10 +105,10 @@ impl SignedAmount { } } - /// 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`]. + /// Parses amounts with denomination suffix as produced by [`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)?; SignedAmount::from_str_in(amt, denom).map_err(Into::into) @@ -94,9 +124,15 @@ impl SignedAmount { /// Express this [`SignedAmount`] as a floating-point value in Bitcoin. /// - /// Equivalent to `to_float_in(Denomination::Bitcoin)`. - /// /// Please be aware of the risk of using floating-point numbers. + /// + /// # Examples + /// + /// ``` + /// # use bitcoin_units::amount::{SignedAmount, Denomination}; + /// let amount = SignedAmount::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) } @@ -118,7 +154,7 @@ impl SignedAmount { SignedAmount::from_str_in(&value.to_string(), denom) } - /// Create an object that implements [`fmt::Display`] using specified denomination. + /// Creates an object that implements [`fmt::Display`] using specified denomination. pub fn display_in(self, denomination: Denomination) -> Display { Display { sats_abs: self.unsigned_abs().to_sat(), @@ -127,7 +163,7 @@ impl SignedAmount { } } - /// Create an object that implements [`fmt::Display`] dynamically selecting denomination. + /// 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. @@ -139,7 +175,7 @@ impl SignedAmount { } } - /// Format the value of this [`SignedAmount`] in the given denomination. + /// Formats the value of this [`SignedAmount`] in the given denomination. /// /// Does not include the denomination. #[rustfmt::skip] @@ -148,14 +184,14 @@ impl SignedAmount { fmt_satoshi_in(self.unsigned_abs().to_sat(), self.is_negative(), f, denom, false, FormatOptions::default()) } - /// Get a string number of this [`SignedAmount`] in the given denomination. + /// Returns a formatted string representing this [`SignedAmount`] 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() } - /// Get a formatted string of this [`SignedAmount`] in the given denomination, - /// suffixed with the abbreviation for the denomination. + /// Returns a formatted string representing 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() @@ -166,7 +202,7 @@ impl SignedAmount { /// Get the absolute value of this [`SignedAmount`]. pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) } - /// Get the absolute value of this [`SignedAmount`] returning [`Amount`]. + /// Gets the absolute value of this [`SignedAmount`] returning [`Amount`]. pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.0.unsigned_abs()) } /// Returns a number representing sign of this [`SignedAmount`]. @@ -218,8 +254,7 @@ impl SignedAmount { /// Checked integer division. /// - /// Be aware that integer division loses the remainder if no exact division - /// can be made. + /// 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: i64) -> Option { @@ -361,12 +396,7 @@ impl FromStr for SignedAmount { /// Parses a string slice where the slice includes a denomination. /// - /// If the string slice is zero or negative zero, then no denomination is required. - /// - /// # Returns - /// - /// `Ok(Amount)` if the string amount and denomination parse successfully, - /// otherwise, return `Err(ParseError)`. + /// If the returned value would be zero or negative zero, then no denomination is required. fn from_str(s: &str) -> Result { let result = SignedAmount::from_str_with_denomination(s); diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs index a695bb101..9c3e6bf9d 100644 --- a/units/src/amount/tests.rs +++ b/units/src/amount/tests.rs @@ -65,13 +65,13 @@ fn from_str_zero_without_denomination() { #[test] fn from_int_btc() { - let amt = Amount::from_int_btc(2); + let amt = Amount::from_int_btc_const(2); assert_eq!(Amount::from_sat(200_000_000), amt); } #[should_panic] #[test] -fn from_int_btc_panic() { Amount::from_int_btc(u64::MAX); } +fn from_int_btc_panic() { Amount::from_int_btc_const(u64::MAX); } #[test] fn test_signed_amount_try_from_amount() { diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs index 46738bbd7..c38257c83 100644 --- a/units/src/amount/unsigned.rs +++ b/units/src/amount/unsigned.rs @@ -46,9 +46,9 @@ impl Amount { /// Exactly one satoshi. pub const ONE_SAT: Amount = Amount(1); /// Exactly one bitcoin. - pub const ONE_BTC: Amount = Self::from_int_btc(1); + 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(21_000_000); + 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. @@ -62,20 +62,36 @@ impl Amount { /// 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`]. + /// Converts from a value expressing a whole number of bitcoin 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`] + /// 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(btc: u64) -> Amount { + /// 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"), @@ -95,10 +111,10 @@ impl Amount { 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`]. + /// Parses amounts with denomination suffix as produced by [`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) @@ -174,14 +190,14 @@ impl Amount { fmt_satoshi_in(self.to_sat(), false, f, denom, false, FormatOptions::default()) } - /// Returns a formatted string of this [`Amount`] in the given denomination. + /// Returns a formatted string representing 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. + /// Returns a formatted string representing 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() @@ -210,8 +226,8 @@ impl Amount { /// Checked integer division. /// - /// Be aware that integer division loses the remainder if no exact division - /// can be made. + /// 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) } @@ -219,9 +235,9 @@ impl Amount { /// /// 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. + /// sufficient. If you wish to round down use `amount / weight`. /// - /// [`None`] is returned if an overflow occurred. + /// Returns [`None`] if overflow occurred. #[cfg(feature = "alloc")] pub fn checked_div_by_weight(self, rhs: Weight) -> Option { let sats = self.0.checked_mul(1000)?; @@ -269,7 +285,9 @@ impl default::Default for Amount { } impl fmt::Debug for Amount { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} SAT", self.to_sat()) } + 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. @@ -343,12 +361,7 @@ impl FromStr for Amount { /// 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)`. + /// If the returned value would be zero or negative zero, then no denomination is required. fn from_str(s: &str) -> Result { let result = Amount::from_str_with_denomination(s); diff --git a/units/src/fee_rate.rs b/units/src/fee_rate.rs index 237de0f5b..b3974422c 100644 --- a/units/src/fee_rate.rs +++ b/units/src/fee_rate.rs @@ -211,6 +211,10 @@ impl Mul for FeeRate { impl Div for Amount { type Output = FeeRate; + /// Truncating integer division. + /// + /// This is likely the wrong thing for a user dividing an amount by a weight. Consider using + /// `checked_div_by_weight` instead. fn div(self, rhs: Weight) -> Self::Output { FeeRate(self.to_sat() * 1000 / rhs.to_wu()) } } diff --git a/units/src/lib.rs b/units/src/lib.rs index f0e3994a1..b9e7e960a 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -4,6 +4,7 @@ //! //! This library provides basic types used by the Rust Bitcoin ecosystem. +#![no_std] // Experimental features we need. #![cfg_attr(docsrs, feature(doc_auto_cfg))] // Coding conventions. @@ -13,7 +14,6 @@ // Exclude lints we don't think are valuable. #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. -#![no_std] #[cfg(feature = "alloc")] extern crate alloc;