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]
fn amount_checked_div_by_weight_ceil() {
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
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap());
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
// round up to 864
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());
}
@ -286,17 +286,17 @@ fn amount_checked_div_by_weight_ceil() {
#[test]
fn amount_checked_div_by_weight_floor() {
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
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap());
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
// round down to 863
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());
}
@ -307,31 +307,31 @@ fn amount_checked_div_by_fee_rate() {
let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap();
// 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
assert_eq!(weight, Weight::from_wu(500_000));
// 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
// Test truncation behavior
let amount = sat(1000);
let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap();
let floor_weight = amount.checked_div_by_fee_rate_floor(fee_rate).unwrap();
let ceil_weight = amount.checked_div_by_fee_rate_ceil(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();
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();
assert!(amount.checked_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_floor(zero_fee_rate).is_none());
assert!(amount.div_by_fee_rate_ceil(zero_fee_rate).is_none());
// Test with maximum amount
let max_amount = Amount::MAX;
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
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.
#[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();
if wu == 0 {
return None;
@ -441,12 +441,12 @@ impl Amount {
/// # use bitcoin_units::{amount, Amount, FeeRate, Weight};
/// let amount = Amount::from_sat(10)?;
/// 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));
/// # Ok::<_, amount::OutOfRangeError>(())
/// ```
#[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();
if wu == 0 {
return None;
@ -471,7 +471,7 @@ impl Amount {
///
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
#[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(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) {
return Some(Weight::from_wu(wu));
@ -487,7 +487,7 @@ impl Amount {
///
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
#[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.
let rate = fee_rate.to_sat_per_kwu_ceil();
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
//! 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`.
//!
//! We support various `core::ops` traits all of which return [`NumOpResult<T>`].
//!
//! For specific methods see:
//!
//! * [`Amount::checked_div_by_weight_floor`]
//! * [`Amount::checked_div_by_weight_ceil`]
//! * [`Amount::checked_div_by_fee_rate_floor`]
//! * [`Amount::checked_div_by_fee_rate_ceil`]
//! * [`Weight::checked_mul_by_fee_rate`]
//! * [`FeeRate::checked_mul_by_weight`]
//! * [`Amount::div_by_weight_floor`]
//! * [`Amount::div_by_weight_ceil`]
//! * [`Amount::div_by_fee_rate_floor`]
//! * [`Amount::div_by_fee_rate_ceil`]
//! * [`Weight::mul_by_fee_rate`]
//! * [`FeeRate::mul_by_weight`]
//! * [`FeeRate::to_fee`]
use core::ops;
@ -33,7 +33,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<FeeRate> for Weight {
type Output = NumOpResult<Amount>;
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),
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 {
type Output = NumOpResult<Amount>;
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),
None => R::Error(E::while_doing(MathOp::Mul)),
}
@ -114,7 +114,7 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<FeeRate>;
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> {
@ -155,7 +155,7 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<Weight>;
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> {
@ -211,25 +211,25 @@ mod tests {
}
#[test]
fn checked_weight_mul() {
fn weight_mul() {
let weight = Weight::from_vb(10).unwrap();
let fee: Amount = FeeRate::from_sat_per_vb(10)
.unwrap()
.checked_mul_by_weight(weight)
.mul_by_weight(weight)
.expect("expected Amount");
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());
let weight = Weight::from_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);
let weight = Weight::from_wu(381);
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.
// The result is then rounded up to 330.
assert_eq!(fee, Amount::from_sat_u32(330));
@ -277,12 +277,12 @@ mod tests {
assert_eq!(weight, Weight::from_wu(333_333));
// 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));
// Test that division by zero returns None
let zero_rate = FeeRate::from_sat_per_kwu(0).unwrap();
assert!(amount.checked_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_floor(zero_rate).is_none());
assert!(amount.div_by_fee_rate_ceil(zero_rate).is_none());
}
}

View File

@ -196,7 +196,7 @@ impl FeeRate {
/// wrapping.
pub const fn to_fee(self, weight: Weight) -> Amount {
// No `unwrap_or()` in const context.
match self.checked_mul_by_weight(weight) {
match self.mul_by_weight(weight) {
Some(fee) => fee,
None => Amount::MAX,
}
@ -208,7 +208,7 @@ impl FeeRate {
/// This is equivalent to `Self::checked_mul_by_weight()`.
#[must_use]
#[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`]
/// if an overflow occurred.
@ -227,7 +227,7 @@ impl FeeRate {
///
/// Returns [`None`] if overflow occurred.
#[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();
if let Some(fee_kwu) = self.to_sat_per_kwu_floor().checked_mul(wu) {
// Bump by 999 to do ceil division using kwu.

View File

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