From 2ec1c2a0448c92bf12eb56d8c6bcf2d96b8bcf53 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 18 Mar 2025 09:37:10 +1100 Subject: [PATCH] units: Make from_int_btc_const take a 16 bit integer The `from_int_btc_const` constructors are specifically designed for easily creating amount types in const context but currently they return an error which is annoying to handle in const context. If we make the `whole_bitcoin` parameter a 16 bit integer this gives us a nicer const constructor with the downside that it can only create values upto a maximum of - unsigned: 65_536 - signed: 32_767 That is plenty high enough for most use cases. Then use the new `from_int_btc_const` in associated consts. Note that because `from_sat` checks max (and min) values we must define max and min from sats directly. --- units/src/amount/signed.rs | 24 +++++++++--------------- units/src/amount/tests.rs | 2 +- units/src/amount/unsigned.rs | 24 +++++++++--------------- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs index 3f7927b47..2119f488d 100644 --- a/units/src/amount/signed.rs +++ b/units/src/amount/signed.rs @@ -77,9 +77,9 @@ impl SignedAmount { /// Exactly one satoshi. pub const ONE_SAT: Self = SignedAmount::from_sat_unchecked(1); /// Exactly one bitcoin. - pub const ONE_BTC: Self = SignedAmount::from_sat_unchecked(100_000_000); + pub const ONE_BTC: Self = SignedAmount::from_int_btc_const(1); /// Exactly fifty bitcoin. - pub const FIFTY_BTC: Self = SignedAmount::from_sat_unchecked(50 * 100_000_000); + pub const FIFTY_BTC: Self = SignedAmount::from_int_btc_const(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. @@ -134,27 +134,21 @@ impl SignedAmount { } /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]. - /// - /// # Errors - /// - /// If `whole_bitcoin` is greater than `21_000_000`. #[allow(clippy::missing_panics_doc)] - pub fn from_int_btc>(whole_bitcoin: T) -> Result { + pub fn from_int_btc>(whole_bitcoin: T) -> SignedAmount { SignedAmount::from_int_btc_const(whole_bitcoin.into()) } /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`] /// in const context. - /// - /// # Errors - /// - /// If `whole_bitcoin` is greater than `21_000_000`. #[allow(clippy::missing_panics_doc)] - pub const fn from_int_btc_const(whole_bitcoin: i32) -> Result { + pub const fn from_int_btc_const(whole_bitcoin: i16) -> SignedAmount { let btc = whole_bitcoin as i64; // Can't call `into` in const context. - match btc.checked_mul(100_000_000) { - Some(amount) => SignedAmount::from_sat(amount), - None => panic!("cannot overflow in i64"), + let sats = btc * 100_000_000; + + match SignedAmount::from_sat(sats) { + Ok(amount) => amount, + Err(_) => panic!("unreachable - 65536 BTC is within range"), } } diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs index fedeb78cd..5fc495588 100644 --- a/units/src/amount/tests.rs +++ b/units/src/amount/tests.rs @@ -102,7 +102,7 @@ fn from_str_zero_without_denomination() { #[test] fn from_int_btc() { - let amt = Amount::from_int_btc_const(2).unwrap(); + let amt = Amount::from_int_btc_const(2); assert_eq!(sat(200_000_000), amt); } diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs index 1b328f964..ab1abb9c3 100644 --- a/units/src/amount/unsigned.rs +++ b/units/src/amount/unsigned.rs @@ -75,9 +75,9 @@ impl Amount { /// Exactly one satoshi. pub const ONE_SAT: Self = Amount::from_sat_unchecked(1); /// Exactly one bitcoin. - pub const ONE_BTC: Self = Amount::from_sat_unchecked(100_000_000); + pub const ONE_BTC: Self = Amount::from_int_btc_const(1); /// Exactly fifty bitcoin. - pub const FIFTY_BTC: Self = Amount::from_sat_unchecked(50 * 100_000_000); + pub const FIFTY_BTC: Self = Amount::from_int_btc_const(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. @@ -132,27 +132,21 @@ impl Amount { } /// Converts from a value expressing a whole number of bitcoin to an [`Amount`]. - /// - /// # Errors - /// - /// If `whole_bitcoin` is greater than `21_000_000`. #[allow(clippy::missing_panics_doc)] - pub fn from_int_btc>(whole_bitcoin: T) -> Result { + pub fn from_int_btc>(whole_bitcoin: T) -> Amount { Amount::from_int_btc_const(whole_bitcoin.into()) } /// Converts from a value expressing a whole number of bitcoin to an [`Amount`] /// in const context. - /// - /// # Errors - /// - /// If `whole_bitcoin` is greater than `21_000_000`. #[allow(clippy::missing_panics_doc)] - pub const fn from_int_btc_const(whole_bitcoin: u32) -> Result { + pub const fn from_int_btc_const(whole_bitcoin: u16) -> Amount { let btc = whole_bitcoin as u64; // Can't call `into` in const context. - match btc.checked_mul(100_000_000) { - Some(amount) => Amount::from_sat(amount), - None => panic!("cannot overflow a u64"), + let sats = btc * 100_000_000; + + match Amount::from_sat(sats) { + Ok(amount) => amount, + Err(_) => panic!("unreachable - 65536 BTC is within range"), } }