From 0ff8d82193ef562c1aedabe98a6283d09f803c0c Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 16 Jun 2025 09:14:47 +1000 Subject: [PATCH] Make FeeRate from sat constructors infallible We now have constructors that take an arbitrary size fee rate (`Amount`). The `from_sat_per_foo` constructors can be made infallible by taking a `u32` instead of `u64`. This makes the API more ergonomic but limits the fee rate to just under 42 BTC which is plenty. Note we just delete the `from_sat_per_vb_u32` function because it is unreleased, in the past we had `from_sat_per_vb_unchecked` so we could put that back in if we wanted to be a bit more kind to downstream. Can be done later, we likely want to go over the public API before release and add a few things back in that we forgot to deprecate or could not for some reason during dev. Fuzz with a new function that consumes a `u32`. --- bitcoin/src/blockdata/script/tests.rs | 4 +- bitcoin/src/blockdata/transaction.rs | 2 +- bitcoin/src/psbt/mod.rs | 6 +- .../bitcoin/deserialize_script.rs | 4 +- fuzz/src/fuzz_utils.rs | 13 ++ units/src/amount/tests.rs | 16 +- units/src/amount/unsigned.rs | 2 +- units/src/fee.rs | 28 ++-- units/src/fee_rate/mod.rs | 137 +++++++----------- units/src/fee_rate/serde.rs | 34 +++-- units/src/result.rs | 2 +- 11 files changed, 120 insertions(+), 128 deletions(-) diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs index 590d233d3..0268d850e 100644 --- a/bitcoin/src/blockdata/script/tests.rs +++ b/bitcoin/src/blockdata/script/tests.rs @@ -751,7 +751,7 @@ fn default_dust_value() { assert!(script_p2wpkh.is_p2wpkh()); assert_eq!(script_p2wpkh.minimal_non_dust(), Amount::from_sat_u32(294)); assert_eq!( - script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_u32(6)), + script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb(6)), Some(Amount::from_sat_u32(588)) ); @@ -765,7 +765,7 @@ fn default_dust_value() { assert!(script_p2pkh.is_p2pkh()); assert_eq!(script_p2pkh.minimal_non_dust(), Amount::from_sat_u32(546)); assert_eq!( - script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_u32(6)), + script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb(6)), Some(Amount::from_sat_u32(1092)) ); } diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index f169f4168..98369280e 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -1689,7 +1689,7 @@ mod tests { #[test] fn effective_value_happy_path() { let value = "1 cBTC".parse::().unwrap(); - let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(10); let effective_value = effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value); // 10 sat/kwu * 272 wu = 3 sats (rounding up) diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 09d7f5353..ea1ae2d3a 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -127,7 +127,7 @@ impl Psbt { /// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point. /// /// [`extract_tx`]: Psbt::extract_tx - pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_u32(25_000); + pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb(25_000); /// An alias for [`extract_tx_fee_rate_limit`]. /// @@ -1437,7 +1437,7 @@ mod tests { // Large fee rate errors if we pass in 1 sat/vb so just use this to get the error fee rate returned. let error_fee_rate = psbt .clone() - .extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_vb_u32(1)) + .extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_vb(1)) .map_err(|e| match e { ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate, _ => panic!(""), @@ -1475,7 +1475,7 @@ mod tests { ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate, _ => panic!(""), }), - Err(FeeRate::from_sat_per_kwu(6250003).unwrap()) // 6250000 is 25k sat/vbyte + Err(FeeRate::from_sat_per_kwu(6250003)) // 6250000 is 25k sat/vbyte ); // Lowering the input satoshis by 1 lowers the sat/kwu by 3 diff --git a/fuzz/fuzz_targets/bitcoin/deserialize_script.rs b/fuzz/fuzz_targets/bitcoin/deserialize_script.rs index c227c59b4..276da5022 100644 --- a/fuzz/fuzz_targets/bitcoin/deserialize_script.rs +++ b/fuzz/fuzz_targets/bitcoin/deserialize_script.rs @@ -2,7 +2,7 @@ use bitcoin::address::Address; use bitcoin::consensus::encode; use bitcoin::script::{self, ScriptExt as _}; use bitcoin::{FeeRate, Network}; -use bitcoin_fuzz::fuzz_utils::{consume_random_bytes, consume_u64}; +use bitcoin_fuzz::fuzz_utils::{consume_random_bytes, consume_u32}; use honggfuzz::fuzz; fn do_test(data: &[u8]) { @@ -17,7 +17,7 @@ fn do_test(data: &[u8]) { let _ = script.count_sigops_legacy(); let _ = script.minimal_non_dust(); - let fee_rate = FeeRate::from_sat_per_kwu(consume_u64(&mut new_data)).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(consume_u32(&mut new_data)); let _ = script.minimal_non_dust_custom(fee_rate); let mut b = script::Builder::new(); diff --git a/fuzz/src/fuzz_utils.rs b/fuzz/src/fuzz_utils.rs index 8a072db98..883729c3c 100644 --- a/fuzz/src/fuzz_utils.rs +++ b/fuzz/src/fuzz_utils.rs @@ -35,3 +35,16 @@ pub fn consume_u64(data: &mut &[u8]) -> u64 { u64_bytes[7], ]) } + +#[allow(dead_code)] +pub fn consume_u32(data: &mut &[u8]) -> u32 { + // We need at least 4 bytes to read a u32 + if data.len() < 4 { + return 0; + } + + let (u32_bytes, rest) = data.split_at(4); + *data = rest; + + u32::from_le_bytes([u32_bytes[0], u32_bytes[1], u32_bytes[2], u32_bytes[3]]) +} diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs index a8b4a516f..1eebfe6f4 100644 --- a/units/src/amount/tests.rs +++ b/units/src/amount/tests.rs @@ -270,13 +270,13 @@ fn amount_checked_div_by_weight_ceil() { let weight = Weight::from_kwu(1).unwrap(); let fee_rate = sat(1).div_by_weight_ceil(weight).unwrap(); // 1 sats / 1,000 wu = 1 sats/kwu - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap()); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1)); let weight = Weight::from_wu(381); let fee_rate = sat(329).div_by_weight_ceil(weight).unwrap(); // 329 sats / 381 wu = 863.5 sats/kwu // round up to 864 - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864).unwrap()); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864)); let fee_rate = Amount::ONE_SAT.div_by_weight_ceil(Weight::ZERO); assert!(fee_rate.is_error()); @@ -288,13 +288,13 @@ fn amount_checked_div_by_weight_floor() { let weight = Weight::from_kwu(1).unwrap(); let fee_rate = sat(1).div_by_weight_floor(weight).unwrap(); // 1 sats / 1,000 wu = 1 sats/kwu - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap()); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1)); let weight = Weight::from_wu(381); let fee_rate = sat(329).div_by_weight_floor(weight).unwrap(); // 329 sats / 381 wu = 863.5 sats/kwu // round down to 863 - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863).unwrap()); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863)); let fee_rate = Amount::ONE_SAT.div_by_weight_floor(Weight::ZERO); assert!(fee_rate.is_error()); @@ -304,7 +304,7 @@ fn amount_checked_div_by_weight_floor() { #[test] fn amount_checked_div_by_fee_rate() { let amount = sat(1000); - let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(2); // Test floor division let weight = amount.div_by_fee_rate_floor(fee_rate).unwrap(); @@ -317,20 +317,20 @@ fn amount_checked_div_by_fee_rate() { // Test truncation behavior let amount = sat(1000); - let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(3); let floor_weight = amount.div_by_fee_rate_floor(fee_rate).unwrap(); let ceil_weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap(); assert_eq!(floor_weight, Weight::from_wu(333_333)); assert_eq!(ceil_weight, Weight::from_wu(333_334)); // Test division by zero - let zero_fee_rate = FeeRate::from_sat_per_kwu(0).unwrap(); + let zero_fee_rate = FeeRate::from_sat_per_kwu(0); assert!(amount.div_by_fee_rate_floor(zero_fee_rate).is_error()); assert!(amount.div_by_fee_rate_ceil(zero_fee_rate).is_error()); // Test with maximum amount let max_amount = Amount::MAX; - let small_fee_rate = FeeRate::from_sat_per_kwu(1).unwrap(); + let small_fee_rate = FeeRate::from_sat_per_kwu(1); let weight = max_amount.div_by_fee_rate_floor(small_fee_rate).unwrap(); // 21_000_000_0000_0000 sats / (1 sat/kwu) = 2_100_000_000_000_000_000 wu assert_eq!(weight, Weight::from_wu(2_100_000_000_000_000_000)); diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs index 2ebc34031..57e27e120 100644 --- a/units/src/amount/unsigned.rs +++ b/units/src/amount/unsigned.rs @@ -439,7 +439,7 @@ impl Amount { /// let amount = Amount::from_sat(10)?; /// let weight = Weight::from_wu(300); /// let fee_rate = amount.div_by_weight_ceil(weight).expect("valid fee rate"); - /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34).expect("valid fee rate")); + /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34)); /// # Ok::<_, amount::OutOfRangeError>(()) /// ``` pub const fn div_by_weight_ceil(self, weight: Weight) -> NumOpResult { diff --git a/units/src/fee.rs b/units/src/fee.rs index 16eb0e5df..0cd69ad8c 100644 --- a/units/src/fee.rs +++ b/units/src/fee.rs @@ -190,12 +190,12 @@ mod tests { #[test] fn fee_rate_div_by_weight() { let fee_rate = (Amount::from_sat_u32(329) / Weight::from_wu(381)).unwrap(); - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863).unwrap()); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863)); } #[test] fn fee_wu() { - let fee_rate = FeeRate::from_sat_per_vb(2).unwrap(); + let fee_rate = FeeRate::from_sat_per_vb(2); let weight = Weight::from_vb(3).unwrap(); assert_eq!(fee_rate.to_fee(weight), Amount::from_sat_u32(6)); } @@ -204,19 +204,19 @@ mod tests { fn weight_mul() { let weight = Weight::from_vb(10).unwrap(); let fee: Amount = - FeeRate::from_sat_per_vb(10).unwrap().mul_by_weight(weight).expect("expected Amount"); + FeeRate::from_sat_per_vb(10).mul_by_weight(weight).expect("expected Amount"); assert_eq!(Amount::from_sat_u32(100), fee); - let fee = FeeRate::from_sat_per_kwu(10).unwrap().mul_by_weight(Weight::MAX); + let fee = FeeRate::from_sat_per_kwu(10).mul_by_weight(Weight::MAX); assert!(fee.is_error()); let weight = Weight::from_vb(3).unwrap(); - let fee_rate = FeeRate::from_sat_per_vb(3).unwrap(); + let fee_rate = FeeRate::from_sat_per_vb(3); let fee = fee_rate.mul_by_weight(weight).unwrap(); assert_eq!(Amount::from_sat_u32(9), fee); let weight = Weight::from_wu(381); - let fee_rate = FeeRate::from_sat_per_kwu(864).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(864); let fee = weight.mul_by_fee_rate(fee_rate).unwrap(); // 381 * 0.864 yields 329.18. // The result is then rounded up to 330. @@ -226,7 +226,7 @@ mod tests { #[test] #[allow(clippy::op_ref)] fn multiply() { - let two = FeeRate::from_sat_per_vb(2).unwrap(); + let two = FeeRate::from_sat_per_vb(2); let three = Weight::from_vb(3).unwrap(); let six = Amount::from_sat_u32(6); @@ -243,9 +243,9 @@ mod tests { fn amount_div_by_fee_rate() { // Test exact division let amount = Amount::from_sat_u32(1000); - let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap(); - let weight = (amount / fee_rate).unwrap(); - assert_eq!(weight, Weight::from_wu(500_000)); + let fee_rate = FeeRate::from_sat_per_kwu(2); + let weight = amount / fee_rate; + assert_eq!(weight.unwrap(), Weight::from_wu(500_000)); // Test reference division let weight_ref = (&amount / fee_rate).unwrap(); @@ -257,19 +257,19 @@ mod tests { // Test truncation behavior let amount = Amount::from_sat_u32(1000); - let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap(); - let weight = (amount / fee_rate).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(3); + let weight = amount / fee_rate; // 1000 * 1000 = 1,000,000 msats // 1,000,000 / 3 = 333,333.33... wu // Should truncate down to 333,333 wu - assert_eq!(weight, Weight::from_wu(333_333)); + assert_eq!(weight.unwrap(), Weight::from_wu(333_333)); // Verify that ceiling division gives different result let ceil_weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap(); assert_eq!(ceil_weight, Weight::from_wu(333_334)); // Test that division by zero returns None - let zero_rate = FeeRate::from_sat_per_kwu(0).unwrap(); + let zero_rate = FeeRate::from_sat_per_kwu(0); assert!(amount.div_by_fee_rate_floor(zero_rate).is_error()); assert!(amount.div_by_fee_rate_ceil(zero_rate).is_error()); } diff --git a/units/src/fee_rate/mod.rs b/units/src/fee_rate/mod.rs index ddff10fee..6c223f956 100644 --- a/units/src/fee_rate/mod.rs +++ b/units/src/fee_rate/mod.rs @@ -50,19 +50,15 @@ impl FeeRate { /// The minimum fee rate required to broadcast a transaction. /// /// The value matches the default Bitcoin Core policy at the time of library release. - pub const BROADCAST_MIN: FeeRate = FeeRate::from_sat_per_vb_u32(1); + pub const BROADCAST_MIN: FeeRate = FeeRate::from_sat_per_vb(1); /// The fee rate used to compute dust amount. - pub const DUST: FeeRate = FeeRate::from_sat_per_vb_u32(3); + pub const DUST: FeeRate = FeeRate::from_sat_per_vb(3); - /// Constructs a new [`FeeRate`] from satoshis per 1000 weight units, - /// returning `None` if overflow occurred. - pub const fn from_sat_per_kwu(sat_kwu: u64) -> Option { - // No `map()` in const context. - match sat_kwu.checked_mul(4_000) { - Some(fee_rate) => Some(FeeRate::from_sat_per_mvb(fee_rate)), - None => None, - } + /// Constructs a new [`FeeRate`] from satoshis per 1000 weight units. + pub const fn from_sat_per_kwu(sat_kwu: u32) -> Self { + let fee_rate = (sat_kwu as u64) * 4_000; // No `Into` in const context. + FeeRate::from_sat_per_mvb(fee_rate) } /// Constructs a new [`FeeRate`] from amount per 1000 weight units. @@ -74,14 +70,10 @@ impl FeeRate { } } - /// Constructs a new [`FeeRate`] from satoshis per virtual byte, - /// returning `None` if overflow occurred. - pub const fn from_sat_per_vb(sat_vb: u64) -> Option { - // No `map()` in const context. - match sat_vb.checked_mul(1_000_000) { - Some(fee_rate) => Some(FeeRate::from_sat_per_mvb(fee_rate)), - None => None, - } + /// Constructs a new [`FeeRate`] from satoshis per virtual byte. + pub const fn from_sat_per_vb(sat_vb: u32) -> Self { + let fee_rate = (sat_vb as u64) * 1_000_000; // No `Into` in const context. + FeeRate::from_sat_per_mvb(fee_rate) } /// Constructs a new [`FeeRate`] from amount per virtual byte. @@ -93,20 +85,10 @@ impl FeeRate { } } - /// Constructs a new [`FeeRate`] from satoshis per virtual bytes. - pub const fn from_sat_per_vb_u32(sat_vb: u32) -> Self { - let sat_vb = sat_vb as u64; // No `Into` in const context. - FeeRate::from_sat_per_mvb(sat_vb * 1_000_000) - } - - /// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes), - /// returning `None` if overflow occurred. - pub const fn from_sat_per_kvb(sat_kvb: u64) -> Option { - // No `map()` in const context. - match sat_kvb.checked_mul(1_000) { - Some(fee_rate) => Some(FeeRate::from_sat_per_mvb(fee_rate)), - None => None, - } + /// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes). + pub const fn from_sat_per_kvb(sat_kvb: u32) -> Self { + let fee_rate = (sat_kvb as u64) * 1_000; // No `Into` in const context. + FeeRate::from_sat_per_mvb(fee_rate) } /// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes). @@ -301,18 +283,18 @@ mod tests { #[test] #[allow(clippy::op_ref)] fn feerate_div_nonzero() { - let rate = FeeRate::from_sat_per_kwu(200).unwrap(); + let rate = FeeRate::from_sat_per_kwu(200); let divisor = NonZeroU64::new(2).unwrap(); - assert_eq!(rate / divisor, FeeRate::from_sat_per_kwu(100).unwrap()); - assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100).unwrap()); + assert_eq!(rate / divisor, FeeRate::from_sat_per_kwu(100)); + assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100)); } #[test] #[allow(clippy::op_ref)] fn addition() { - let one = FeeRate::from_sat_per_kwu(1).unwrap(); - let two = FeeRate::from_sat_per_kwu(2).unwrap(); - let three = FeeRate::from_sat_per_kwu(3).unwrap(); + let one = FeeRate::from_sat_per_kwu(1); + let two = FeeRate::from_sat_per_kwu(2); + let three = FeeRate::from_sat_per_kwu(3); assert!(one + two == three); assert!(&one + two == three); @@ -323,9 +305,9 @@ mod tests { #[test] #[allow(clippy::op_ref)] fn subtract() { - let three = FeeRate::from_sat_per_kwu(3).unwrap(); - let seven = FeeRate::from_sat_per_kwu(7).unwrap(); - let ten = FeeRate::from_sat_per_kwu(10).unwrap(); + let three = FeeRate::from_sat_per_kwu(3); + let seven = FeeRate::from_sat_per_kwu(7); + let ten = FeeRate::from_sat_per_kwu(10); assert_eq!(ten - seven, three); assert_eq!(&ten - seven, three); @@ -335,44 +317,45 @@ mod tests { #[test] fn add_assign() { - let mut f = FeeRate::from_sat_per_kwu(1).unwrap(); - f += FeeRate::from_sat_per_kwu(2).unwrap(); - assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap()); + let mut f = FeeRate::from_sat_per_kwu(1); + f += FeeRate::from_sat_per_kwu(2); + assert_eq!(f, FeeRate::from_sat_per_kwu(3)); - let mut f = FeeRate::from_sat_per_kwu(1).unwrap(); - f += &FeeRate::from_sat_per_kwu(2).unwrap(); - assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap()); + let mut f = FeeRate::from_sat_per_kwu(1); + f += &FeeRate::from_sat_per_kwu(2); + assert_eq!(f, FeeRate::from_sat_per_kwu(3)); } #[test] fn sub_assign() { - let mut f = FeeRate::from_sat_per_kwu(3).unwrap(); - f -= FeeRate::from_sat_per_kwu(2).unwrap(); - assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap()); + let mut f = FeeRate::from_sat_per_kwu(3); + f -= FeeRate::from_sat_per_kwu(2); + assert_eq!(f, FeeRate::from_sat_per_kwu(1)); - let mut f = FeeRate::from_sat_per_kwu(3).unwrap(); - f -= &FeeRate::from_sat_per_kwu(2).unwrap(); - assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap()); + let mut f = FeeRate::from_sat_per_kwu(3); + f -= &FeeRate::from_sat_per_kwu(2); + assert_eq!(f, FeeRate::from_sat_per_kwu(1)); } #[test] fn checked_add() { - let one = FeeRate::from_sat_per_kwu(1).unwrap(); - let two = FeeRate::from_sat_per_kwu(2).unwrap(); - let three = FeeRate::from_sat_per_kwu(3).unwrap(); + let one = FeeRate::from_sat_per_kwu(1); + let two = FeeRate::from_sat_per_kwu(2); + let three = FeeRate::from_sat_per_kwu(3); assert_eq!(one.checked_add(two).unwrap(), three); - assert!(FeeRate::from_sat_per_kvb(u64::MAX).is_none()); // sanity check. + // Sanity check - no overflow adding one to per kvb max. + let _ = FeeRate::from_sat_per_kvb(u32::MAX).checked_add(one).unwrap(); let fee_rate = FeeRate::from_sat_per_mvb(u64::MAX).checked_add(one); assert!(fee_rate.is_none()); } #[test] fn checked_sub() { - let one = FeeRate::from_sat_per_kwu(1).unwrap(); - let two = FeeRate::from_sat_per_kwu(2).unwrap(); - let three = FeeRate::from_sat_per_kwu(3).unwrap(); + let one = FeeRate::from_sat_per_kwu(1); + let two = FeeRate::from_sat_per_kwu(2); + let three = FeeRate::from_sat_per_kwu(3); assert_eq!(three.checked_sub(two).unwrap(), one); let fee_rate = FeeRate::ZERO.checked_sub(one); @@ -390,35 +373,25 @@ mod tests { #[test] fn fee_rate_from_sat_per_vb() { - let fee_rate = FeeRate::from_sat_per_vb(10).expect("expected feerate in sat/kwu"); - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500).unwrap()); + let fee_rate = FeeRate::from_sat_per_vb(10); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500)); } #[test] fn fee_rate_from_sat_per_kvb() { - let fee_rate = FeeRate::from_sat_per_kvb(11).unwrap(); + let fee_rate = FeeRate::from_sat_per_kvb(11); assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(11_000)); } #[test] - fn fee_rate_from_sat_per_vb_overflow() { - let fee_rate = FeeRate::from_sat_per_vb(u64::MAX); - assert!(fee_rate.is_none()); + fn from_sat_per_vb() { + let fee_rate = FeeRate::from_sat_per_vb(10); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500)); } - #[test] - fn from_sat_per_vb_u32() { - let fee_rate = FeeRate::from_sat_per_vb_u32(10); - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500).unwrap()); - } - - #[test] - #[cfg(debug_assertions)] - fn from_sat_per_vb_u32_cannot_panic() { FeeRate::from_sat_per_vb_u32(u32::MAX); } - #[test] fn raw_feerate() { - let fee_rate = FeeRate::from_sat_per_kwu(749).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(749); assert_eq!(fee_rate.to_sat_per_kwu_floor(), 749); assert_eq!(fee_rate.to_sat_per_vb_floor(), 2); assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3); @@ -427,24 +400,22 @@ mod tests { #[test] fn checked_mul() { let fee_rate = FeeRate::from_sat_per_kwu(10) - .unwrap() .checked_mul(10) .expect("expected feerate in sat/kwu"); - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(100).unwrap()); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(100)); - let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap().checked_mul(u64::MAX); + let fee_rate = FeeRate::from_sat_per_kwu(10).checked_mul(u64::MAX); assert!(fee_rate.is_none()); } #[test] fn checked_div() { let fee_rate = FeeRate::from_sat_per_kwu(10) - .unwrap() .checked_div(10) .expect("expected feerate in sat/kwu"); - assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap()); + assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1)); - let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap().checked_div(0); + let fee_rate = FeeRate::from_sat_per_kwu(10).checked_div(0); assert!(fee_rate.is_none()); } diff --git a/units/src/fee_rate/serde.rs b/units/src/fee_rate/serde.rs index 27750dce6..61390976b 100644 --- a/units/src/fee_rate/serde.rs +++ b/units/src/fee_rate/serde.rs @@ -33,15 +33,19 @@ pub mod as_sat_per_kwu_floor { use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use crate::FeeRate; + use crate::{Amount, FeeRate}; pub fn serialize(f: &FeeRate, s: S) -> Result { u64::serialize(&f.to_sat_per_kwu_floor(), s) } pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result { - FeeRate::from_sat_per_kwu(u64::deserialize(d)?) - .ok_or_else(|| serde::de::Error::custom("overflowed sats/kwu")) + let sat = u64::deserialize(d)?; + FeeRate::from_per_kwu( + Amount::from_sat(sat).map_err(|_| serde::de::Error::custom("amount out of range"))?, + ) + .into_result() + .map_err(|_| serde::de::Error::custom("fee rate too big for sats/kwu")) } pub mod opt { @@ -100,8 +104,7 @@ pub mod as_sat_per_vb_floor { use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use crate::fee_rate::serde::OverflowError; - use crate::fee_rate::FeeRate; + use crate::{Amount, FeeRate}; pub fn serialize(f: &FeeRate, s: S) -> Result { u64::serialize(&f.to_sat_per_vb_floor(), s) @@ -109,9 +112,12 @@ pub mod as_sat_per_vb_floor { // Errors on overflow. pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result { - FeeRate::from_sat_per_vb(u64::deserialize(d)?) - .ok_or(OverflowError) - .map_err(serde::de::Error::custom) + let sat = u64::deserialize(d)?; + FeeRate::from_per_vb( + Amount::from_sat(sat).map_err(|_| serde::de::Error::custom("amount out of range"))?, + ) + .into_result() + .map_err(|_| serde::de::Error::custom("fee rate too big for sats/vb")) } pub mod opt { @@ -171,8 +177,7 @@ pub mod as_sat_per_vb_ceil { use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use crate::fee_rate::serde::OverflowError; - use crate::fee_rate::FeeRate; + use crate::{Amount, FeeRate}; pub fn serialize(f: &FeeRate, s: S) -> Result { u64::serialize(&f.to_sat_per_vb_ceil(), s) @@ -180,9 +185,12 @@ pub mod as_sat_per_vb_ceil { // Errors on overflow. pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result { - FeeRate::from_sat_per_vb(u64::deserialize(d)?) - .ok_or(OverflowError) - .map_err(serde::de::Error::custom) + let sat = u64::deserialize(d)?; + FeeRate::from_per_vb( + Amount::from_sat(sat).map_err(|_| serde::de::Error::custom("amount out of range"))?, + ) + .into_result() + .map_err(|_| serde::de::Error::custom("fee rate too big for sats/vb")) } pub mod opt { diff --git a/units/src/result.rs b/units/src/result.rs index a6619ac68..36f9094e0 100644 --- a/units/src/result.rs +++ b/units/src/result.rs @@ -58,7 +58,7 @@ use crate::{Amount, FeeRate, SignedAmount, Weight}; /// let a = Amount::from_sat(123).expect("valid amount"); /// let b = Amount::from_sat(467).expect("valid amount"); /// // Fee rate for transaction. -/// let fee_rate = FeeRate::from_sat_per_vb(1).unwrap(); +/// let fee_rate = FeeRate::from_sat_per_vb(1); /// /// // Somewhat contrived example to show addition operator chained with division. /// let max_fee = a + b;