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:
parent
3b0286bd56
commit
0ff8d82193
|
@ -751,7 +751,7 @@ fn default_dust_value() {
|
||||||
assert!(script_p2wpkh.is_p2wpkh());
|
assert!(script_p2wpkh.is_p2wpkh());
|
||||||
assert_eq!(script_p2wpkh.minimal_non_dust(), Amount::from_sat_u32(294));
|
assert_eq!(script_p2wpkh.minimal_non_dust(), Amount::from_sat_u32(294));
|
||||||
assert_eq!(
|
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))
|
Some(Amount::from_sat_u32(588))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -765,7 +765,7 @@ fn default_dust_value() {
|
||||||
assert!(script_p2pkh.is_p2pkh());
|
assert!(script_p2pkh.is_p2pkh());
|
||||||
assert_eq!(script_p2pkh.minimal_non_dust(), Amount::from_sat_u32(546));
|
assert_eq!(script_p2pkh.minimal_non_dust(), Amount::from_sat_u32(546));
|
||||||
assert_eq!(
|
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))
|
Some(Amount::from_sat_u32(1092))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1689,7 +1689,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn effective_value_happy_path() {
|
fn effective_value_happy_path() {
|
||||||
let value = "1 cBTC".parse::<Amount>().unwrap();
|
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);
|
let effective_value = effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value);
|
||||||
|
|
||||||
// 10 sat/kwu * 272 wu = 3 sats (rounding up)
|
// 10 sat/kwu * 272 wu = 3 sats (rounding up)
|
||||||
|
|
|
@ -127,7 +127,7 @@ impl Psbt {
|
||||||
/// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point.
|
/// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point.
|
||||||
///
|
///
|
||||||
/// [`extract_tx`]: Psbt::extract_tx
|
/// [`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`].
|
/// 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.
|
// 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
|
let error_fee_rate = psbt
|
||||||
.clone()
|
.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 {
|
.map_err(|e| match e {
|
||||||
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
|
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
|
||||||
_ => panic!(""),
|
_ => panic!(""),
|
||||||
|
@ -1475,7 +1475,7 @@ mod tests {
|
||||||
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
|
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
|
||||||
_ => panic!(""),
|
_ => 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
|
// Lowering the input satoshis by 1 lowers the sat/kwu by 3
|
||||||
|
|
|
@ -2,7 +2,7 @@ use bitcoin::address::Address;
|
||||||
use bitcoin::consensus::encode;
|
use bitcoin::consensus::encode;
|
||||||
use bitcoin::script::{self, ScriptExt as _};
|
use bitcoin::script::{self, ScriptExt as _};
|
||||||
use bitcoin::{FeeRate, Network};
|
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;
|
use honggfuzz::fuzz;
|
||||||
|
|
||||||
fn do_test(data: &[u8]) {
|
fn do_test(data: &[u8]) {
|
||||||
|
@ -17,7 +17,7 @@ fn do_test(data: &[u8]) {
|
||||||
let _ = script.count_sigops_legacy();
|
let _ = script.count_sigops_legacy();
|
||||||
let _ = script.minimal_non_dust();
|
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 _ = script.minimal_non_dust_custom(fee_rate);
|
||||||
|
|
||||||
let mut b = script::Builder::new();
|
let mut b = script::Builder::new();
|
||||||
|
|
|
@ -35,3 +35,16 @@ pub fn consume_u64(data: &mut &[u8]) -> u64 {
|
||||||
u64_bytes[7],
|
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]])
|
||||||
|
}
|
||||||
|
|
|
@ -270,13 +270,13 @@ fn amount_checked_div_by_weight_ceil() {
|
||||||
let weight = Weight::from_kwu(1).unwrap();
|
let weight = Weight::from_kwu(1).unwrap();
|
||||||
let fee_rate = sat(1).div_by_weight_ceil(weight).unwrap();
|
let fee_rate = sat(1).div_by_weight_ceil(weight).unwrap();
|
||||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
// 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 weight = Weight::from_wu(381);
|
||||||
let fee_rate = sat(329).div_by_weight_ceil(weight).unwrap();
|
let fee_rate = sat(329).div_by_weight_ceil(weight).unwrap();
|
||||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||||
// round up to 864
|
// 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);
|
let fee_rate = Amount::ONE_SAT.div_by_weight_ceil(Weight::ZERO);
|
||||||
assert!(fee_rate.is_error());
|
assert!(fee_rate.is_error());
|
||||||
|
@ -288,13 +288,13 @@ fn amount_checked_div_by_weight_floor() {
|
||||||
let weight = Weight::from_kwu(1).unwrap();
|
let weight = Weight::from_kwu(1).unwrap();
|
||||||
let fee_rate = sat(1).div_by_weight_floor(weight).unwrap();
|
let fee_rate = sat(1).div_by_weight_floor(weight).unwrap();
|
||||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
// 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 weight = Weight::from_wu(381);
|
||||||
let fee_rate = sat(329).div_by_weight_floor(weight).unwrap();
|
let fee_rate = sat(329).div_by_weight_floor(weight).unwrap();
|
||||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||||
// round down to 863
|
// 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);
|
let fee_rate = Amount::ONE_SAT.div_by_weight_floor(Weight::ZERO);
|
||||||
assert!(fee_rate.is_error());
|
assert!(fee_rate.is_error());
|
||||||
|
@ -304,7 +304,7 @@ fn amount_checked_div_by_weight_floor() {
|
||||||
#[test]
|
#[test]
|
||||||
fn amount_checked_div_by_fee_rate() {
|
fn amount_checked_div_by_fee_rate() {
|
||||||
let amount = sat(1000);
|
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
|
// Test floor division
|
||||||
let weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
|
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
|
// Test truncation behavior
|
||||||
let amount = sat(1000);
|
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 floor_weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
|
||||||
let ceil_weight = amount.div_by_fee_rate_ceil(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!(floor_weight, Weight::from_wu(333_333));
|
||||||
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
||||||
|
|
||||||
// Test division by zero
|
// 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_floor(zero_fee_rate).is_error());
|
||||||
assert!(amount.div_by_fee_rate_ceil(zero_fee_rate).is_error());
|
assert!(amount.div_by_fee_rate_ceil(zero_fee_rate).is_error());
|
||||||
|
|
||||||
// Test with maximum amount
|
// Test with maximum amount
|
||||||
let max_amount = Amount::MAX;
|
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();
|
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
|
// 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));
|
assert_eq!(weight, Weight::from_wu(2_100_000_000_000_000_000));
|
||||||
|
|
|
@ -439,7 +439,7 @@ impl Amount {
|
||||||
/// let amount = Amount::from_sat(10)?;
|
/// let amount = Amount::from_sat(10)?;
|
||||||
/// let weight = Weight::from_wu(300);
|
/// let weight = Weight::from_wu(300);
|
||||||
/// let fee_rate = amount.div_by_weight_ceil(weight).expect("valid fee rate");
|
/// 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>(())
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub const fn div_by_weight_ceil(self, weight: Weight) -> NumOpResult<FeeRate> {
|
pub const fn div_by_weight_ceil(self, weight: Weight) -> NumOpResult<FeeRate> {
|
||||||
|
|
|
@ -190,12 +190,12 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_div_by_weight() {
|
fn fee_rate_div_by_weight() {
|
||||||
let fee_rate = (Amount::from_sat_u32(329) / Weight::from_wu(381)).unwrap();
|
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]
|
#[test]
|
||||||
fn fee_wu() {
|
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();
|
let weight = Weight::from_vb(3).unwrap();
|
||||||
assert_eq!(fee_rate.to_fee(weight), Amount::from_sat_u32(6));
|
assert_eq!(fee_rate.to_fee(weight), Amount::from_sat_u32(6));
|
||||||
}
|
}
|
||||||
|
@ -204,19 +204,19 @@ mod tests {
|
||||||
fn weight_mul() {
|
fn weight_mul() {
|
||||||
let weight = Weight::from_vb(10).unwrap();
|
let weight = Weight::from_vb(10).unwrap();
|
||||||
let fee: Amount =
|
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);
|
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());
|
assert!(fee.is_error());
|
||||||
|
|
||||||
let weight = Weight::from_vb(3).unwrap();
|
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();
|
let fee = fee_rate.mul_by_weight(weight).unwrap();
|
||||||
assert_eq!(Amount::from_sat_u32(9), fee);
|
assert_eq!(Amount::from_sat_u32(9), fee);
|
||||||
|
|
||||||
let weight = Weight::from_wu(381);
|
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();
|
let fee = weight.mul_by_fee_rate(fee_rate).unwrap();
|
||||||
// 381 * 0.864 yields 329.18.
|
// 381 * 0.864 yields 329.18.
|
||||||
// The result is then rounded up to 330.
|
// The result is then rounded up to 330.
|
||||||
|
@ -226,7 +226,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn multiply() {
|
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 three = Weight::from_vb(3).unwrap();
|
||||||
let six = Amount::from_sat_u32(6);
|
let six = Amount::from_sat_u32(6);
|
||||||
|
|
||||||
|
@ -243,9 +243,9 @@ mod tests {
|
||||||
fn amount_div_by_fee_rate() {
|
fn amount_div_by_fee_rate() {
|
||||||
// Test exact division
|
// Test exact division
|
||||||
let amount = Amount::from_sat_u32(1000);
|
let amount = Amount::from_sat_u32(1000);
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap();
|
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
||||||
let weight = (amount / fee_rate).unwrap();
|
let weight = amount / fee_rate;
|
||||||
assert_eq!(weight, Weight::from_wu(500_000));
|
assert_eq!(weight.unwrap(), Weight::from_wu(500_000));
|
||||||
|
|
||||||
// Test reference division
|
// Test reference division
|
||||||
let weight_ref = (&amount / fee_rate).unwrap();
|
let weight_ref = (&amount / fee_rate).unwrap();
|
||||||
|
@ -257,19 +257,19 @@ mod tests {
|
||||||
|
|
||||||
// Test truncation behavior
|
// Test truncation behavior
|
||||||
let amount = Amount::from_sat_u32(1000);
|
let amount = Amount::from_sat_u32(1000);
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap();
|
let fee_rate = FeeRate::from_sat_per_kwu(3);
|
||||||
let weight = (amount / fee_rate).unwrap();
|
let weight = amount / fee_rate;
|
||||||
// 1000 * 1000 = 1,000,000 msats
|
// 1000 * 1000 = 1,000,000 msats
|
||||||
// 1,000,000 / 3 = 333,333.33... wu
|
// 1,000,000 / 3 = 333,333.33... wu
|
||||||
// Should truncate down to 333,333 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
|
// Verify that ceiling division gives different result
|
||||||
let ceil_weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap();
|
let ceil_weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap();
|
||||||
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
||||||
|
|
||||||
// Test that division by zero returns None
|
// 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_floor(zero_rate).is_error());
|
||||||
assert!(amount.div_by_fee_rate_ceil(zero_rate).is_error());
|
assert!(amount.div_by_fee_rate_ceil(zero_rate).is_error());
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,19 +50,15 @@ impl FeeRate {
|
||||||
/// The minimum fee rate required to broadcast a transaction.
|
/// The minimum fee rate required to broadcast a transaction.
|
||||||
///
|
///
|
||||||
/// The value matches the default Bitcoin Core policy at the time of library release.
|
/// 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.
|
/// 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,
|
/// Constructs a new [`FeeRate`] from satoshis per 1000 weight units.
|
||||||
/// returning `None` if overflow occurred.
|
pub const fn from_sat_per_kwu(sat_kwu: u32) -> Self {
|
||||||
pub const fn from_sat_per_kwu(sat_kwu: u64) -> Option<Self> {
|
let fee_rate = (sat_kwu as u64) * 4_000; // No `Into` in const context.
|
||||||
// No `map()` in const context.
|
FeeRate::from_sat_per_mvb(fee_rate)
|
||||||
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 amount per 1000 weight units.
|
/// 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,
|
/// Constructs a new [`FeeRate`] from satoshis per virtual byte.
|
||||||
/// returning `None` if overflow occurred.
|
pub const fn from_sat_per_vb(sat_vb: u32) -> Self {
|
||||||
pub const fn from_sat_per_vb(sat_vb: u64) -> Option<Self> {
|
let fee_rate = (sat_vb as u64) * 1_000_000; // No `Into` in const context.
|
||||||
// No `map()` in const context.
|
FeeRate::from_sat_per_mvb(fee_rate)
|
||||||
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 amount per virtual byte.
|
/// Constructs a new [`FeeRate`] from amount per virtual byte.
|
||||||
|
@ -93,20 +85,10 @@ impl FeeRate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new [`FeeRate`] from satoshis per virtual bytes.
|
/// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes).
|
||||||
pub const fn from_sat_per_vb_u32(sat_vb: u32) -> Self {
|
pub const fn from_sat_per_kvb(sat_kvb: u32) -> Self {
|
||||||
let sat_vb = sat_vb as u64; // No `Into` in const context.
|
let fee_rate = (sat_kvb as u64) * 1_000; // No `Into` in const context.
|
||||||
FeeRate::from_sat_per_mvb(sat_vb * 1_000_000)
|
FeeRate::from_sat_per_mvb(fee_rate)
|
||||||
}
|
|
||||||
|
|
||||||
/// 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).
|
/// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes).
|
||||||
|
@ -301,18 +283,18 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn feerate_div_nonzero() {
|
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();
|
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));
|
||||||
assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100).unwrap());
|
assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn addition() {
|
fn addition() {
|
||||||
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
let one = FeeRate::from_sat_per_kwu(1);
|
||||||
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
let two = FeeRate::from_sat_per_kwu(2);
|
||||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
let three = FeeRate::from_sat_per_kwu(3);
|
||||||
|
|
||||||
assert!(one + two == three);
|
assert!(one + two == three);
|
||||||
assert!(&one + two == three);
|
assert!(&one + two == three);
|
||||||
|
@ -323,9 +305,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn subtract() {
|
fn subtract() {
|
||||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
let three = FeeRate::from_sat_per_kwu(3);
|
||||||
let seven = FeeRate::from_sat_per_kwu(7).unwrap();
|
let seven = FeeRate::from_sat_per_kwu(7);
|
||||||
let ten = FeeRate::from_sat_per_kwu(10).unwrap();
|
let ten = FeeRate::from_sat_per_kwu(10);
|
||||||
|
|
||||||
assert_eq!(ten - seven, three);
|
assert_eq!(ten - seven, three);
|
||||||
assert_eq!(&ten - seven, three);
|
assert_eq!(&ten - seven, three);
|
||||||
|
@ -335,44 +317,45 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_assign() {
|
fn add_assign() {
|
||||||
let mut f = FeeRate::from_sat_per_kwu(1).unwrap();
|
let mut f = FeeRate::from_sat_per_kwu(1);
|
||||||
f += FeeRate::from_sat_per_kwu(2).unwrap();
|
f += FeeRate::from_sat_per_kwu(2);
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap());
|
assert_eq!(f, FeeRate::from_sat_per_kwu(3));
|
||||||
|
|
||||||
let mut f = FeeRate::from_sat_per_kwu(1).unwrap();
|
let mut f = FeeRate::from_sat_per_kwu(1);
|
||||||
f += &FeeRate::from_sat_per_kwu(2).unwrap();
|
f += &FeeRate::from_sat_per_kwu(2);
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap());
|
assert_eq!(f, FeeRate::from_sat_per_kwu(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sub_assign() {
|
fn sub_assign() {
|
||||||
let mut f = FeeRate::from_sat_per_kwu(3).unwrap();
|
let mut f = FeeRate::from_sat_per_kwu(3);
|
||||||
f -= FeeRate::from_sat_per_kwu(2).unwrap();
|
f -= FeeRate::from_sat_per_kwu(2);
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap());
|
assert_eq!(f, FeeRate::from_sat_per_kwu(1));
|
||||||
|
|
||||||
let mut f = FeeRate::from_sat_per_kwu(3).unwrap();
|
let mut f = FeeRate::from_sat_per_kwu(3);
|
||||||
f -= &FeeRate::from_sat_per_kwu(2).unwrap();
|
f -= &FeeRate::from_sat_per_kwu(2);
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap());
|
assert_eq!(f, FeeRate::from_sat_per_kwu(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_add() {
|
fn checked_add() {
|
||||||
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
let one = FeeRate::from_sat_per_kwu(1);
|
||||||
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
let two = FeeRate::from_sat_per_kwu(2);
|
||||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
let three = FeeRate::from_sat_per_kwu(3);
|
||||||
|
|
||||||
assert_eq!(one.checked_add(two).unwrap(), three);
|
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);
|
let fee_rate = FeeRate::from_sat_per_mvb(u64::MAX).checked_add(one);
|
||||||
assert!(fee_rate.is_none());
|
assert!(fee_rate.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_sub() {
|
fn checked_sub() {
|
||||||
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
let one = FeeRate::from_sat_per_kwu(1);
|
||||||
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
let two = FeeRate::from_sat_per_kwu(2);
|
||||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
let three = FeeRate::from_sat_per_kwu(3);
|
||||||
assert_eq!(three.checked_sub(two).unwrap(), one);
|
assert_eq!(three.checked_sub(two).unwrap(), one);
|
||||||
|
|
||||||
let fee_rate = FeeRate::ZERO.checked_sub(one);
|
let fee_rate = FeeRate::ZERO.checked_sub(one);
|
||||||
|
@ -390,35 +373,25 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_from_sat_per_vb() {
|
fn fee_rate_from_sat_per_vb() {
|
||||||
let fee_rate = FeeRate::from_sat_per_vb(10).expect("expected feerate in sat/kwu");
|
let fee_rate = FeeRate::from_sat_per_vb(10);
|
||||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500).unwrap());
|
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_from_sat_per_kvb() {
|
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));
|
assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(11_000));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_from_sat_per_vb_overflow() {
|
fn from_sat_per_vb() {
|
||||||
let fee_rate = FeeRate::from_sat_per_vb(u64::MAX);
|
let fee_rate = FeeRate::from_sat_per_vb(10);
|
||||||
assert!(fee_rate.is_none());
|
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]
|
#[test]
|
||||||
fn raw_feerate() {
|
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_kwu_floor(), 749);
|
||||||
assert_eq!(fee_rate.to_sat_per_vb_floor(), 2);
|
assert_eq!(fee_rate.to_sat_per_vb_floor(), 2);
|
||||||
assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3);
|
assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3);
|
||||||
|
@ -427,24 +400,22 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_mul() {
|
fn checked_mul() {
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
||||||
.unwrap()
|
|
||||||
.checked_mul(10)
|
.checked_mul(10)
|
||||||
.expect("expected feerate in sat/kwu");
|
.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());
|
assert!(fee_rate.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_div() {
|
fn checked_div() {
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
||||||
.unwrap()
|
|
||||||
.checked_div(10)
|
.checked_div(10)
|
||||||
.expect("expected feerate in sat/kwu");
|
.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());
|
assert!(fee_rate.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,15 +33,19 @@ pub mod as_sat_per_kwu_floor {
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
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> {
|
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
||||||
u64::serialize(&f.to_sat_per_kwu_floor(), s)
|
u64::serialize(&f.to_sat_per_kwu_floor(), s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
||||||
FeeRate::from_sat_per_kwu(u64::deserialize(d)?)
|
let sat = u64::deserialize(d)?;
|
||||||
.ok_or_else(|| serde::de::Error::custom("overflowed sats/kwu"))
|
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 {
|
pub mod opt {
|
||||||
|
@ -100,8 +104,7 @@ pub mod as_sat_per_vb_floor {
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::fee_rate::serde::OverflowError;
|
use crate::{Amount, FeeRate};
|
||||||
use crate::fee_rate::FeeRate;
|
|
||||||
|
|
||||||
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
||||||
u64::serialize(&f.to_sat_per_vb_floor(), s)
|
u64::serialize(&f.to_sat_per_vb_floor(), s)
|
||||||
|
@ -109,9 +112,12 @@ pub mod as_sat_per_vb_floor {
|
||||||
|
|
||||||
// Errors on overflow.
|
// Errors on overflow.
|
||||||
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
||||||
FeeRate::from_sat_per_vb(u64::deserialize(d)?)
|
let sat = u64::deserialize(d)?;
|
||||||
.ok_or(OverflowError)
|
FeeRate::from_per_vb(
|
||||||
.map_err(serde::de::Error::custom)
|
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 {
|
pub mod opt {
|
||||||
|
@ -171,8 +177,7 @@ pub mod as_sat_per_vb_ceil {
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::fee_rate::serde::OverflowError;
|
use crate::{Amount, FeeRate};
|
||||||
use crate::fee_rate::FeeRate;
|
|
||||||
|
|
||||||
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
||||||
u64::serialize(&f.to_sat_per_vb_ceil(), s)
|
u64::serialize(&f.to_sat_per_vb_ceil(), s)
|
||||||
|
@ -180,9 +185,12 @@ pub mod as_sat_per_vb_ceil {
|
||||||
|
|
||||||
// Errors on overflow.
|
// Errors on overflow.
|
||||||
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
||||||
FeeRate::from_sat_per_vb(u64::deserialize(d)?)
|
let sat = u64::deserialize(d)?;
|
||||||
.ok_or(OverflowError)
|
FeeRate::from_per_vb(
|
||||||
.map_err(serde::de::Error::custom)
|
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 {
|
pub mod opt {
|
||||||
|
|
|
@ -58,7 +58,7 @@ use crate::{Amount, FeeRate, SignedAmount, Weight};
|
||||||
/// let a = Amount::from_sat(123).expect("valid amount");
|
/// let a = Amount::from_sat(123).expect("valid amount");
|
||||||
/// let b = Amount::from_sat(467).expect("valid amount");
|
/// let b = Amount::from_sat(467).expect("valid amount");
|
||||||
/// // Fee rate for transaction.
|
/// // 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.
|
/// // Somewhat contrived example to show addition operator chained with division.
|
||||||
/// let max_fee = a + b;
|
/// let max_fee = a + b;
|
||||||
|
|
Loading…
Reference in New Issue