diff --git a/units/src/amount.rs b/units/src/amount.rs index ca2324845..4a59661b2 100644 --- a/units/src/amount.rs +++ b/units/src/amount.rs @@ -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 { 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 { + 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() { diff --git a/units/src/fee_rate.rs b/units/src/fee_rate.rs index 575187e49..e01a640a1 100644 --- a/units/src/fee_rate.rs +++ b/units/src/fee_rate.rs @@ -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]