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`.
This commit is contained in:
Tobin C. Harding 2025-06-16 09:14:47 +10:00
parent 3b0286bd56
commit 0ff8d82193
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
11 changed files with 120 additions and 128 deletions

View File

@ -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))
);
}

View File

@ -1689,7 +1689,7 @@ mod tests {
#[test]
fn effective_value_happy_path() {
let value = "1 cBTC".parse::<Amount>().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)

View File

@ -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

View File

@ -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();

View File

@ -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]])
}

View File

@ -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));

View File

@ -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<FeeRate> {

View File

@ -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());
}

View File

@ -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<Self> {
// 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<Self> {
// 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<Self> {
// 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());
}

View File

@ -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<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
u64::serialize(&f.to_sat_per_kwu_floor(), s)
}
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
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<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
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, D::Error> {
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<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
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, D::Error> {
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 {

View File

@ -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;