Remove checked_ prefix from fee functions

The `Amount` and `FeeRate` types are not simple int wrappers, as such we
do not need to mimic the stdlib too closely. Having the `checked_`
prefix to the functions that do fee calcualtions adds no additional
meaning. Note that I'm about to change the return type to use
`NumOpResult` further justifying this change.

Note we leave the 'Checked foo' in the functions docs title because
the functions are  still checked.
This commit is contained in:
Tobin C. Harding 2025-06-12 11:17:32 +10:00
parent 052514e6ff
commit 75106e6d82
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
5 changed files with 42 additions and 42 deletions

View File

@ -268,17 +268,17 @@ fn positive_sub() {
#[test] #[test]
fn amount_checked_div_by_weight_ceil() { 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).checked_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).unwrap());
let weight = Weight::from_wu(381); let weight = Weight::from_wu(381);
let fee_rate = sat(329).checked_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).unwrap());
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_ceil(Weight::ZERO); let fee_rate = Amount::ONE_SAT.div_by_weight_ceil(Weight::ZERO);
assert!(fee_rate.is_none()); assert!(fee_rate.is_none());
} }
@ -286,17 +286,17 @@ fn amount_checked_div_by_weight_ceil() {
#[test] #[test]
fn amount_checked_div_by_weight_floor() { 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).checked_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).unwrap());
let weight = Weight::from_wu(381); let weight = Weight::from_wu(381);
let fee_rate = sat(329).checked_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).unwrap());
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_floor(Weight::ZERO); let fee_rate = Amount::ONE_SAT.div_by_weight_floor(Weight::ZERO);
assert!(fee_rate.is_none()); assert!(fee_rate.is_none());
} }
@ -307,31 +307,31 @@ fn amount_checked_div_by_fee_rate() {
let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap(); let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap();
// Test floor division // Test floor division
let weight = amount.checked_div_by_fee_rate_floor(fee_rate).unwrap(); let weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
// 1000 sats / (2 sats/kwu) = 500,000 wu // 1000 sats / (2 sats/kwu) = 500,000 wu
assert_eq!(weight, Weight::from_wu(500_000)); assert_eq!(weight, Weight::from_wu(500_000));
// Test ceiling division // Test ceiling division
let weight = amount.checked_div_by_fee_rate_ceil(fee_rate).unwrap(); let weight = amount.div_by_fee_rate_ceil(fee_rate).unwrap();
assert_eq!(weight, Weight::from_wu(500_000)); // Same result for exact division assert_eq!(weight, Weight::from_wu(500_000)); // Same result for exact division
// 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).unwrap();
let floor_weight = amount.checked_div_by_fee_rate_floor(fee_rate).unwrap(); let floor_weight = amount.div_by_fee_rate_floor(fee_rate).unwrap();
let ceil_weight = amount.checked_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).unwrap();
assert!(amount.checked_div_by_fee_rate_floor(zero_fee_rate).is_none()); assert!(amount.div_by_fee_rate_floor(zero_fee_rate).is_none());
assert!(amount.checked_div_by_fee_rate_ceil(zero_fee_rate).is_none()); assert!(amount.div_by_fee_rate_ceil(zero_fee_rate).is_none());
// 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).unwrap();
let weight = max_amount.checked_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));
} }

View File

@ -411,7 +411,7 @@ impl Amount {
/// ///
/// Returns [`None`] if overflow occurred. /// Returns [`None`] if overflow occurred.
#[must_use] #[must_use]
pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option<FeeRate> { pub const fn div_by_weight_floor(self, weight: Weight) -> Option<FeeRate> {
let wu = weight.to_wu(); let wu = weight.to_wu();
if wu == 0 { if wu == 0 {
return None; return None;
@ -441,12 +441,12 @@ impl Amount {
/// # use bitcoin_units::{amount, Amount, FeeRate, Weight}; /// # use bitcoin_units::{amount, Amount, FeeRate, Weight};
/// 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.checked_div_by_weight_ceil(weight); /// let fee_rate = amount.div_by_weight_ceil(weight);
/// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34)); /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
/// # Ok::<_, amount::OutOfRangeError>(()) /// # Ok::<_, amount::OutOfRangeError>(())
/// ``` /// ```
#[must_use] #[must_use]
pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> { pub const fn div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
let wu = weight.to_wu(); let wu = weight.to_wu();
if wu == 0 { if wu == 0 {
return None; return None;
@ -471,7 +471,7 @@ impl Amount {
/// ///
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero. /// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
#[must_use] #[must_use]
pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> { pub const fn div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> {
if let Some(msats) = self.to_sat().checked_mul(1000) { if let Some(msats) = self.to_sat().checked_mul(1000) {
if let Some(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) { if let Some(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) {
return Some(Weight::from_wu(wu)); return Some(Weight::from_wu(wu));
@ -487,7 +487,7 @@ impl Amount {
/// ///
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero. /// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
#[must_use] #[must_use]
pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> { pub const fn div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> {
// Use ceil because result is used as the divisor. // Use ceil because result is used as the divisor.
let rate = fee_rate.to_sat_per_kwu_ceil(); let rate = fee_rate.to_sat_per_kwu_ceil();
if rate == 0 { if rate == 0 {

View File

@ -8,19 +8,19 @@
//! Either the weight or fee rate can be calculated if one knows the total fee and either of the //! Either the weight or fee rate can be calculated if one knows the total fee and either of the
//! other values. Note however that such calculations truncate (as for integer division). //! other values. Note however that such calculations truncate (as for integer division).
//! //!
//! We provide `fee.checked_div_by_weight_ceil(weight)` to calculate a minimum threshold fee rate //! We provide `fee.div_by_weight_ceil(weight)` to calculate a minimum threshold fee rate
//! required to pay at least `fee` for transaction with `weight`. //! required to pay at least `fee` for transaction with `weight`.
//! //!
//! We support various `core::ops` traits all of which return [`NumOpResult<T>`]. //! We support various `core::ops` traits all of which return [`NumOpResult<T>`].
//! //!
//! For specific methods see: //! For specific methods see:
//! //!
//! * [`Amount::checked_div_by_weight_floor`] //! * [`Amount::div_by_weight_floor`]
//! * [`Amount::checked_div_by_weight_ceil`] //! * [`Amount::div_by_weight_ceil`]
//! * [`Amount::checked_div_by_fee_rate_floor`] //! * [`Amount::div_by_fee_rate_floor`]
//! * [`Amount::checked_div_by_fee_rate_ceil`] //! * [`Amount::div_by_fee_rate_ceil`]
//! * [`Weight::checked_mul_by_fee_rate`] //! * [`Weight::mul_by_fee_rate`]
//! * [`FeeRate::checked_mul_by_weight`] //! * [`FeeRate::mul_by_weight`]
//! * [`FeeRate::to_fee`] //! * [`FeeRate::to_fee`]
use core::ops; use core::ops;
@ -33,7 +33,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<FeeRate> for Weight { impl ops::Mul<FeeRate> for Weight {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
fn mul(self, rhs: FeeRate) -> Self::Output { fn mul(self, rhs: FeeRate) -> Self::Output {
match rhs.checked_mul_by_weight(self) { match rhs.mul_by_weight(self) {
Some(amount) => R::Valid(amount), Some(amount) => R::Valid(amount),
None => R::Error(E::while_doing(MathOp::Mul)), None => R::Error(E::while_doing(MathOp::Mul)),
} }
@ -73,7 +73,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<Weight> for FeeRate { impl ops::Mul<Weight> for FeeRate {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
fn mul(self, rhs: Weight) -> Self::Output { fn mul(self, rhs: Weight) -> Self::Output {
match self.checked_mul_by_weight(rhs) { match self.mul_by_weight(rhs) {
Some(amount) => R::Valid(amount), Some(amount) => R::Valid(amount),
None => R::Error(E::while_doing(MathOp::Mul)), None => R::Error(E::while_doing(MathOp::Mul)),
} }
@ -114,7 +114,7 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<FeeRate>; type Output = NumOpResult<FeeRate>;
fn div(self, rhs: Weight) -> Self::Output { fn div(self, rhs: Weight) -> Self::Output {
self.checked_div_by_weight_floor(rhs).valid_or_error(MathOp::Div) self.div_by_weight_floor(rhs).valid_or_error(MathOp::Div)
} }
} }
impl ops::Div<Weight> for NumOpResult<Amount> { impl ops::Div<Weight> for NumOpResult<Amount> {
@ -155,7 +155,7 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<Weight>; type Output = NumOpResult<Weight>;
fn div(self, rhs: FeeRate) -> Self::Output { fn div(self, rhs: FeeRate) -> Self::Output {
self.checked_div_by_fee_rate_floor(rhs).valid_or_error(MathOp::Div) self.div_by_fee_rate_floor(rhs).valid_or_error(MathOp::Div)
} }
} }
impl ops::Div<FeeRate> for NumOpResult<Amount> { impl ops::Div<FeeRate> for NumOpResult<Amount> {
@ -211,25 +211,25 @@ mod tests {
} }
#[test] #[test]
fn checked_weight_mul() { fn weight_mul() {
let weight = Weight::from_vb(10).unwrap(); let weight = Weight::from_vb(10).unwrap();
let fee: Amount = FeeRate::from_sat_per_vb(10) let fee: Amount = FeeRate::from_sat_per_vb(10)
.unwrap() .unwrap()
.checked_mul_by_weight(weight) .mul_by_weight(weight)
.expect("expected Amount"); .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().checked_mul_by_weight(Weight::MAX); let fee = FeeRate::from_sat_per_kwu(10).unwrap().mul_by_weight(Weight::MAX);
assert!(fee.is_none()); assert!(fee.is_none());
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).unwrap();
let fee = fee_rate.checked_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).unwrap();
let fee = weight.checked_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.
assert_eq!(fee, Amount::from_sat_u32(330)); assert_eq!(fee, Amount::from_sat_u32(330));
@ -277,12 +277,12 @@ mod tests {
assert_eq!(weight, Weight::from_wu(333_333)); assert_eq!(weight, Weight::from_wu(333_333));
// Verify that ceiling division gives different result // Verify that ceiling division gives different result
let ceil_weight = amount.checked_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).unwrap();
assert!(amount.checked_div_by_fee_rate_floor(zero_rate).is_none()); assert!(amount.div_by_fee_rate_floor(zero_rate).is_none());
assert!(amount.checked_div_by_fee_rate_ceil(zero_rate).is_none()); assert!(amount.div_by_fee_rate_ceil(zero_rate).is_none());
} }
} }

View File

@ -196,7 +196,7 @@ impl FeeRate {
/// wrapping. /// wrapping.
pub const fn to_fee(self, weight: Weight) -> Amount { pub const fn to_fee(self, weight: Weight) -> Amount {
// No `unwrap_or()` in const context. // No `unwrap_or()` in const context.
match self.checked_mul_by_weight(weight) { match self.mul_by_weight(weight) {
Some(fee) => fee, Some(fee) => fee,
None => Amount::MAX, None => Amount::MAX,
} }
@ -208,7 +208,7 @@ impl FeeRate {
/// This is equivalent to `Self::checked_mul_by_weight()`. /// This is equivalent to `Self::checked_mul_by_weight()`.
#[must_use] #[must_use]
#[deprecated(since = "TBD", note = "use `to_fee()` instead")] #[deprecated(since = "TBD", note = "use `to_fee()` instead")]
pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.checked_mul_by_weight(weight) } pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.mul_by_weight(weight) }
/// Calculates the fee by multiplying this fee rate by weight, in virtual bytes, returning [`None`] /// Calculates the fee by multiplying this fee rate by weight, in virtual bytes, returning [`None`]
/// if an overflow occurred. /// if an overflow occurred.
@ -227,7 +227,7 @@ impl FeeRate {
/// ///
/// Returns [`None`] if overflow occurred. /// Returns [`None`] if overflow occurred.
#[must_use] #[must_use]
pub const fn checked_mul_by_weight(self, weight: Weight) -> Option<Amount> { pub const fn mul_by_weight(self, weight: Weight) -> Option<Amount> {
let wu = weight.to_wu(); let wu = weight.to_wu();
if let Some(fee_kwu) = self.to_sat_per_kwu_floor().checked_mul(wu) { if let Some(fee_kwu) = self.to_sat_per_kwu_floor().checked_mul(wu) {
// Bump by 999 to do ceil division using kwu. // Bump by 999 to do ceil division using kwu.

View File

@ -171,8 +171,8 @@ impl Weight {
/// ///
/// Returns [`None`] if overflow occurred. /// Returns [`None`] if overflow occurred.
#[must_use] #[must_use]
pub const fn checked_mul_by_fee_rate(self, fee_rate: FeeRate) -> Option<Amount> { pub const fn mul_by_fee_rate(self, fee_rate: FeeRate) -> Option<Amount> {
fee_rate.checked_mul_by_weight(self) fee_rate.mul_by_weight(self)
} }
} }