Merge rust-bitcoin/rust-bitcoin#3430: Add checked div by weight to amount

a0c58a4a8b Add checked weight division to Amount (yancy)
8def40a991 Add assertions to checked_weight_mul test (yancy)
16ce70d3a6 Add div_by_weight test to fee_rate (yancy)

Pull request description:

  Adds the checked variant of `amount / weight`.  I also added a test to the non-checked version for comparison so the reviewer knows they compute the same way (integer division rounded down).

  Also added assertion to `checked_weight_mul test` showing the results are rounded up.

ACKs for top commit:
  tcharding:
    ACK a0c58a4a8b
  apoelstra:
    ACK a0c58a4a8b successfully ran local tests

Tree-SHA512: cf14123ed261d100e3261a720c26f8c10368f05225e32eaa246f25ab766d20515db5feb98335d4e3e08a8146a70db65ff64670da3f75e7764e8f86ef534d2663
This commit is contained in:
merge-script 2024-10-10 18:19:16 +00:00
commit 27f6f17974
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 57 additions and 0 deletions

View File

@ -5,6 +5,9 @@
//! This module mainly introduces the [`Amount`] and [`SignedAmount`] types.
//! We refer to the documentation on the types for more information.
#[cfg(feature = "alloc")]
use crate::{Weight, FeeRate};
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
use core::cmp::Ordering;
@ -1039,6 +1042,22 @@ impl Amount {
/// Returns [`None`] if overflow occurred.
pub fn checked_div(self, rhs: u64) -> Option<Amount> { self.0.checked_div(rhs).map(Amount) }
/// Checked weight division.
///
/// Be aware that integer division loses the remainder if no exact division
/// can be made. This method rounds up ensuring the transaction fee-rate is
/// sufficient. If you wish to round-down, use the unchecked version instead.
///
/// [`None`] is returned if an overflow occurred.
#[cfg(feature = "alloc")]
pub fn checked_div_by_weight(self, rhs: Weight) -> Option<FeeRate> {
let sats = self.0.checked_mul(1000)?;
let wu = rhs.to_wu();
let fee_rate = sats.checked_add(wu.checked_sub(1)?)?.checked_div(wu)?;
Some(FeeRate::from_sat_per_kwu(fee_rate))
}
/// Checked remainder.
///
/// Returns [`None`] if overflow occurred.
@ -2219,6 +2238,31 @@ mod tests {
assert_eq!(ssat(-6).checked_div(2), Some(ssat(-3)));
}
#[cfg(feature = "alloc")]
#[test]
fn amount_checked_div_by_weight() {
let weight = Weight::from_kwu(1).unwrap();
let fee_rate = Amount::from_sat(1)
.checked_div_by_weight(weight)
.unwrap();
// 1 sats / 1,000 wu = 1 sats/kwu
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
let weight = Weight::from_wu(381);
let fee_rate = Amount::from_sat(329)
.checked_div_by_weight(weight)
.unwrap();
// 329 sats / 381 wu = 863.5 sats/kwu
// round up to 864
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(864));
let fee_rate = Amount::MAX.checked_div_by_weight(weight);
assert!(fee_rate.is_none());
let fee_rate = Amount::ONE_SAT.checked_div_by_weight(Weight::ZERO);
assert!(fee_rate.is_none());
}
#[test]
#[cfg(not(debug_assertions))]
fn unchecked_amount_add() {

View File

@ -284,6 +284,12 @@ mod tests {
assert_eq!(f, FeeRate(1));
}
#[test]
fn fee_rate_div_by_weight() {
let fee_rate = Amount::from_sat(329) / Weight::from_wu(381);
assert_eq!(fee_rate, FeeRate(863));
}
#[test]
fn checked_add() {
let f = FeeRate(1).checked_add(2).unwrap();
@ -367,6 +373,13 @@ mod tests {
let fee_rate = FeeRate::from_sat_per_vb(3).unwrap();
let fee = fee_rate.checked_mul_by_weight(weight).unwrap();
assert_eq!(Amount::from_sat(9), fee);
let weight = Weight::from_wu(381);
let fee_rate = FeeRate::from_sat_per_kwu(864);
let fee = fee_rate.checked_mul_by_weight(weight).unwrap();
// 381 * 0.864 yields 329.18.
// The result is then rounded up to 330.
assert_eq!(fee, Amount::from_sat(330));
}
#[test]