Add Amount division by FeeRate
Implement `checked_div_by_fee_rate_floor` and `checked_div_by_fee_rate_ceil` methods to compute the maximum and minimum transaction weights. These methods provide precise weight calculations using both floor and ceiling divisions. - Introduce `checked_div_by_fee_rate_floor` for floor-based weight division. - Add `checked_div_by_fee_rate_ceil` for ceiling-based weight division. - Update `ops::Div` implementation for `Amount` to use floor division by default. - Include unit tests to validate both floor and ceiling division methods.
This commit is contained in:
parent
93cd9a4f74
commit
bcc38c40e0
|
@ -220,6 +220,48 @@ fn amount_checked_div_by_weight_floor() {
|
||||||
assert!(fee_rate.is_none());
|
assert!(fee_rate.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
#[test]
|
||||||
|
fn amount_checked_div_by_fee_rate() {
|
||||||
|
let amount = Amount::from_sat(1000);
|
||||||
|
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
||||||
|
|
||||||
|
// Test floor division
|
||||||
|
let weight = amount.checked_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();
|
||||||
|
assert_eq!(weight, Weight::from_wu(500_000)); // Same result for exact division
|
||||||
|
|
||||||
|
// Test truncation behavior
|
||||||
|
let amount = Amount::from_sat(1000);
|
||||||
|
let fee_rate = FeeRate::from_sat_per_kwu(3);
|
||||||
|
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();
|
||||||
|
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);
|
||||||
|
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());
|
||||||
|
|
||||||
|
// Test with maximum amount
|
||||||
|
let max_amount = Amount::MAX;
|
||||||
|
let small_fee_rate = FeeRate::from_sat_per_kwu(1);
|
||||||
|
let weight = max_amount.checked_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));
|
||||||
|
|
||||||
|
// Test overflow case
|
||||||
|
let tiny_fee_rate = FeeRate::from_sat_per_kwu(1);
|
||||||
|
let large_amount = Amount::from_sat(u64::MAX);
|
||||||
|
assert!(large_amount.checked_div_by_fee_rate_floor(tiny_fee_rate).is_none());
|
||||||
|
assert!(large_amount.checked_div_by_fee_rate_ceil(tiny_fee_rate).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
#[test]
|
#[test]
|
||||||
fn floating_point() {
|
fn floating_point() {
|
||||||
|
|
|
@ -68,6 +68,48 @@ impl Amount {
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checked fee rate floor division.
|
||||||
|
///
|
||||||
|
/// Computes the maximum weight that would result in a fee less than or equal to this amount
|
||||||
|
/// at the given `fee_rate`. Uses floor division to ensure the resulting weight doesn't cause
|
||||||
|
/// the fee to exceed the 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> {
|
||||||
|
match self.to_sat().checked_mul(1000) {
|
||||||
|
Some(amount_msats) => match amount_msats.checked_div(fee_rate.to_sat_per_kwu()) {
|
||||||
|
Some(wu) => Some(Weight::from_wu(wu)),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checked fee rate ceiling division.
|
||||||
|
///
|
||||||
|
/// Computes the minimum weight that would result in a fee greater than or equal to this amount
|
||||||
|
/// at the given `fee_rate`. Uses ceiling division to ensure the resulting weight is sufficient.
|
||||||
|
///
|
||||||
|
/// 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> {
|
||||||
|
let rate = fee_rate.to_sat_per_kwu();
|
||||||
|
match self.to_sat().checked_mul(1000) {
|
||||||
|
Some(amount_msats) => match rate.checked_sub(1) {
|
||||||
|
Some(rate_minus_one) => match amount_msats.checked_add(rate_minus_one) {
|
||||||
|
Some(rounded_msats) => match rounded_msats.checked_div(rate) {
|
||||||
|
Some(wu) => Some(Weight::from_wu(wu)),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FeeRate {
|
impl FeeRate {
|
||||||
|
@ -136,6 +178,21 @@ impl ops::Div<Weight> for Amount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ops::Div<FeeRate> for Amount {
|
||||||
|
type Output = Weight;
|
||||||
|
|
||||||
|
/// Truncating integer division.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This operation will panic if `fee_rate` is zero or the division results in overflow.
|
||||||
|
///
|
||||||
|
/// Note: This uses floor division. For ceiling division use [`Amount::checked_div_by_fee_rate_ceil`].
|
||||||
|
fn div(self, rhs: FeeRate) -> Self::Output {
|
||||||
|
self.checked_div_by_fee_rate_floor(rhs).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -199,4 +256,31 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(two * three, six);
|
assert_eq!(two * three, six);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn amount_div_by_fee_rate() {
|
||||||
|
// Test exact division
|
||||||
|
let amount = Amount::from_sat(1000);
|
||||||
|
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
||||||
|
let weight = amount / fee_rate;
|
||||||
|
assert_eq!(weight, Weight::from_wu(500_000));
|
||||||
|
|
||||||
|
// Test truncation behavior
|
||||||
|
let amount = Amount::from_sat(1000);
|
||||||
|
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));
|
||||||
|
|
||||||
|
// Verify that ceiling division gives different result
|
||||||
|
let ceil_weight = amount.checked_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);
|
||||||
|
assert!(amount.checked_div_by_fee_rate_floor(zero_rate).is_none());
|
||||||
|
assert!(amount.checked_div_by_fee_rate_ceil(zero_rate).is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue