Skip to content

Commit 1e4f018

Browse files
committed
Merge rust-bitcoin#4613: Inline fee functions back into their respective modules
20c84ce units: Make fee module public (Tobin C. Harding) 251e6a8 Inline checked mul function back into weight module (Tobin C. Harding) a8610a9 Inline checked mul / to fee back into fee_rate module (Tobin C. Harding) e17c391 Inline checked div functions back into unsigned module (Tobin C. Harding) Pull request description: Move all the `fee` functions back onto the respective impl blocks for the associated type. Finally, make the `fee` module public so users get the docs. If this is not like we can drop the last patch. Close: rust-bitcoin#4609 ACKs for top commit: apoelstra: ACK 20c84ce; successfully ran local tests Tree-SHA512: e4c9701245fcd0c7881baaf01a7f9d0be1ed80bc3fa848a289fbc6104375d8db5a2a092e9f1aa0b556dcc45349d053418cff8d1c281dda3cefc1e38c6ac2ba85
2 parents aab9c2d + 20c84ce commit 1e4f018

File tree

5 files changed

+184
-177
lines changed

5 files changed

+184
-177
lines changed

units/src/amount/unsigned.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use super::{
1515
parse_signed_to_satoshi, split_amount_and_denomination, Denomination, Display, DisplayStyle,
1616
OutOfRangeError, ParseAmountError, ParseError, SignedAmount,
1717
};
18+
use crate::{FeeRate, Weight};
1819

1920
mod encapsulate {
2021
use super::OutOfRangeError;
@@ -402,6 +403,106 @@ impl Amount {
402403
SignedAmount::from_sat(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range.
403404
.expect("range of Amount is within range of SignedAmount")
404405
}
406+
407+
/// Checked weight floor division.
408+
///
409+
/// Be aware that integer division loses the remainder if no exact division
410+
/// can be made. See also [`Self::checked_div_by_weight_ceil`].
411+
///
412+
/// Returns [`None`] if overflow occurred.
413+
#[must_use]
414+
pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option<FeeRate> {
415+
let wu = weight.to_wu();
416+
if wu == 0 {
417+
return None;
418+
}
419+
420+
// Mul by 1,000 because we use per/kwu.
421+
match self.to_sat().checked_mul(1_000) {
422+
Some(sats) => {
423+
let fee_rate = sats / wu;
424+
FeeRate::from_sat_per_kwu(fee_rate)
425+
}
426+
None => None,
427+
}
428+
}
429+
430+
/// Checked weight ceiling division.
431+
///
432+
/// Be aware that integer division loses the remainder if no exact division
433+
/// can be made. This method rounds up ensuring the transaction fee rate is
434+
/// sufficient. See also [`Self::checked_div_by_weight_floor`].
435+
///
436+
/// Returns [`None`] if overflow occurred.
437+
///
438+
/// # Examples
439+
///
440+
/// ```
441+
/// # use bitcoin_units::{amount, Amount, FeeRate, Weight};
442+
/// let amount = Amount::from_sat(10)?;
443+
/// let weight = Weight::from_wu(300);
444+
/// let fee_rate = amount.checked_div_by_weight_ceil(weight);
445+
/// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
446+
/// # Ok::<_, amount::OutOfRangeError>(())
447+
/// ```
448+
#[must_use]
449+
pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
450+
let wu = weight.to_wu();
451+
if wu == 0 {
452+
return None;
453+
}
454+
455+
// Mul by 1,000 because we use per/kwu.
456+
if let Some(sats) = self.to_sat().checked_mul(1_000) {
457+
// No need to used checked arithmetic because wu is non-zero.
458+
if let Some(bump) = sats.checked_add(wu - 1) {
459+
let fee_rate = bump / wu;
460+
return FeeRate::from_sat_per_kwu(fee_rate);
461+
}
462+
}
463+
None
464+
}
465+
466+
/// Checked fee rate floor division.
467+
///
468+
/// Computes the maximum weight that would result in a fee less than or equal to this amount
469+
/// at the given `fee_rate`. Uses floor division to ensure the resulting weight doesn't cause
470+
/// the fee to exceed the amount.
471+
///
472+
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
473+
#[must_use]
474+
pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> {
475+
if let Some(msats) = self.to_sat().checked_mul(1000) {
476+
if let Some(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) {
477+
return Some(Weight::from_wu(wu));
478+
}
479+
}
480+
None
481+
}
482+
483+
/// Checked fee rate ceiling division.
484+
///
485+
/// Computes the minimum weight that would result in a fee greater than or equal to this amount
486+
/// at the given `fee_rate`. Uses ceiling division to ensure the resulting weight is sufficient.
487+
///
488+
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
489+
#[must_use]
490+
pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> {
491+
// Use ceil because result is used as the divisor.
492+
let rate = fee_rate.to_sat_per_kwu_ceil();
493+
if rate == 0 {
494+
return None;
495+
}
496+
497+
if let Some(msats) = self.to_sat().checked_mul(1000) {
498+
// No need to used checked arithmetic because rate is non-zero.
499+
if let Some(bump) = msats.checked_add(rate - 1) {
500+
let wu = bump / rate;
501+
return Some(Weight::from_wu(wu));
502+
}
503+
}
504+
None
505+
}
405506
}
406507

407508
impl default::Default for Amount {

units/src/fee.rs

Lines changed: 12 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -10,173 +10,25 @@
1010
//!
1111
//! We provide `fee.checked_div_by_weight_ceil(weight)` to calculate a minimum threshold fee rate
1212
//! required to pay at least `fee` for transaction with `weight`.
13+
//!
14+
//! We support various `core::ops` traits all of which return [`NumOpResult<T>`].
15+
//!
16+
//! For specific methods see:
17+
//!
18+
//! * [`Amount::checked_div_by_weight_floor`]
19+
//! * [`Amount::checked_div_by_weight_ceil`]
20+
//! * [`Amount::checked_div_by_fee_rate_floor`]
21+
//! * [`Amount::checked_div_by_fee_rate_ceil`]
22+
//! * [`Weight::checked_mul_by_fee_rate`]
23+
//! * [`FeeRate::checked_mul_by_weight`]
24+
//! * [`FeeRate::to_fee`]
1325
1426
use core::ops;
1527

1628
use NumOpResult as R;
1729

1830
use crate::{Amount, FeeRate, MathOp, NumOpError as E, NumOpResult, OptionExt, Weight};
1931

20-
impl Amount {
21-
/// Checked weight floor division.
22-
///
23-
/// Be aware that integer division loses the remainder if no exact division
24-
/// can be made. See also [`Self::checked_div_by_weight_ceil`].
25-
///
26-
/// Returns [`None`] if overflow occurred.
27-
#[must_use]
28-
pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option<FeeRate> {
29-
let wu = weight.to_wu();
30-
if wu == 0 {
31-
return None;
32-
}
33-
34-
// Mul by 1,000 because we use per/kwu.
35-
match self.to_sat().checked_mul(1_000) {
36-
Some(sats) => {
37-
let fee_rate = sats / wu;
38-
FeeRate::from_sat_per_kwu(fee_rate)
39-
}
40-
None => None,
41-
}
42-
}
43-
44-
/// Checked weight ceiling division.
45-
///
46-
/// Be aware that integer division loses the remainder if no exact division
47-
/// can be made. This method rounds up ensuring the transaction fee rate is
48-
/// sufficient. See also [`Self::checked_div_by_weight_floor`].
49-
///
50-
/// Returns [`None`] if overflow occurred.
51-
///
52-
/// # Examples
53-
///
54-
/// ```
55-
/// # use bitcoin_units::{amount, Amount, FeeRate, Weight};
56-
/// let amount = Amount::from_sat(10)?;
57-
/// let weight = Weight::from_wu(300);
58-
/// let fee_rate = amount.checked_div_by_weight_ceil(weight);
59-
/// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
60-
/// # Ok::<_, amount::OutOfRangeError>(())
61-
/// ```
62-
#[must_use]
63-
pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
64-
let wu = weight.to_wu();
65-
if wu == 0 {
66-
return None;
67-
}
68-
69-
// Mul by 1,000 because we use per/kwu.
70-
if let Some(sats) = self.to_sat().checked_mul(1_000) {
71-
// No need to used checked arithmetic because wu is non-zero.
72-
if let Some(bump) = sats.checked_add(wu - 1) {
73-
let fee_rate = bump / wu;
74-
return FeeRate::from_sat_per_kwu(fee_rate);
75-
}
76-
}
77-
None
78-
}
79-
80-
/// Checked fee rate floor division.
81-
///
82-
/// Computes the maximum weight that would result in a fee less than or equal to this amount
83-
/// at the given `fee_rate`. Uses floor division to ensure the resulting weight doesn't cause
84-
/// the fee to exceed the amount.
85-
///
86-
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
87-
#[must_use]
88-
pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> {
89-
if let Some(msats) = self.to_sat().checked_mul(1000) {
90-
if let Some(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) {
91-
return Some(Weight::from_wu(wu));
92-
}
93-
}
94-
None
95-
}
96-
97-
/// Checked fee rate ceiling division.
98-
///
99-
/// Computes the minimum weight that would result in a fee greater than or equal to this amount
100-
/// at the given `fee_rate`. Uses ceiling division to ensure the resulting weight is sufficient.
101-
///
102-
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
103-
#[must_use]
104-
pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> {
105-
// Use ceil because result is used as the divisor.
106-
let rate = fee_rate.to_sat_per_kwu_ceil();
107-
if rate == 0 {
108-
return None;
109-
}
110-
111-
if let Some(msats) = self.to_sat().checked_mul(1000) {
112-
// No need to used checked arithmetic because rate is non-zero.
113-
if let Some(bump) = msats.checked_add(rate - 1) {
114-
let wu = bump / rate;
115-
return Some(Weight::from_wu(wu));
116-
}
117-
}
118-
None
119-
}
120-
}
121-
122-
impl FeeRate {
123-
/// Calculates the fee by multiplying this fee rate by weight.
124-
///
125-
/// Computes the absolute fee amount for a given [`Weight`] at this fee rate. When the resulting
126-
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
127-
/// enough instead of falling short if rounded down.
128-
///
129-
/// If the calculation would overflow we saturate to [`Amount::MAX`]. Since such a fee can never
130-
/// be paid this is meaningful as an error case while still removing the possibility of silently
131-
/// wrapping.
132-
pub const fn to_fee(self, weight: Weight) -> Amount {
133-
// No `unwrap_or()` in const context.
134-
match self.checked_mul_by_weight(weight) {
135-
Some(fee) => fee,
136-
None => Amount::MAX,
137-
}
138-
}
139-
140-
/// Calculates the fee by multiplying this fee rate by weight, in weight units, returning [`None`]
141-
/// if an overflow occurred.
142-
///
143-
/// This is equivalent to `Self::checked_mul_by_weight()`.
144-
#[must_use]
145-
#[deprecated(since = "TBD", note = "use `to_fee()` instead")]
146-
pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.checked_mul_by_weight(weight) }
147-
148-
/// Calculates the fee by multiplying this fee rate by weight, in virtual bytes, returning [`None`]
149-
/// if an overflow occurred.
150-
///
151-
/// This is equivalent to converting `vb` to [`Weight`] using [`Weight::from_vb`] and then calling
152-
/// `Self::fee_wu(weight)`.
153-
#[must_use]
154-
#[deprecated(since = "TBD", note = "use Weight::from_vb and then `to_fee()` instead")]
155-
pub fn fee_vb(self, vb: u64) -> Option<Amount> { Weight::from_vb(vb).map(|w| self.to_fee(w)) }
156-
157-
/// Checked weight multiplication.
158-
///
159-
/// Computes the absolute fee amount for a given [`Weight`] at this fee rate. When the resulting
160-
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
161-
/// enough instead of falling short if rounded down.
162-
///
163-
/// Returns [`None`] if overflow occurred.
164-
#[must_use]
165-
pub const fn checked_mul_by_weight(self, weight: Weight) -> Option<Amount> {
166-
let wu = weight.to_wu();
167-
if let Some(fee_kwu) = self.to_sat_per_kwu_floor().checked_mul(wu) {
168-
// Bump by 999 to do ceil division using kwu.
169-
if let Some(bump) = fee_kwu.checked_add(999) {
170-
let fee = bump / 1_000;
171-
if let Ok(fee_amount) = Amount::from_sat(fee) {
172-
return Some(fee_amount);
173-
}
174-
}
175-
}
176-
None
177-
}
178-
}
179-
18032
crate::internal_macros::impl_op_for_references! {
18133
impl ops::Mul<FeeRate> for Weight {
18234
type Output = NumOpResult<Amount>;
@@ -341,20 +193,6 @@ crate::internal_macros::impl_op_for_references! {
341193
}
342194
}
343195

344-
impl Weight {
345-
/// Checked fee rate multiplication.
346-
///
347-
/// Computes the absolute fee amount for a given [`FeeRate`] at this weight. When the resulting
348-
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
349-
/// enough instead of falling short if rounded down.
350-
///
351-
/// Returns [`None`] if overflow occurred.
352-
#[must_use]
353-
pub const fn checked_mul_by_fee_rate(self, fee_rate: FeeRate) -> Option<Amount> {
354-
fee_rate.checked_mul_by_weight(self)
355-
}
356-
}
357-
358196
#[cfg(test)]
359197
mod tests {
360198
use super::*;

units/src/fee_rate/mod.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use arbitrary::{Arbitrary, Unstructured};
1313

1414
use NumOpResult as R;
1515

16-
use crate::{Amount,MathOp, NumOpError as E, NumOpResult};
16+
use crate::{Amount, MathOp, NumOpError as E, NumOpResult, Weight};
1717

1818
mod encapsulate {
1919
/// Fee rate.
@@ -184,6 +184,62 @@ impl FeeRate {
184184
None => None,
185185
}
186186
}
187+
188+
/// Calculates the fee by multiplying this fee rate by weight.
189+
///
190+
/// Computes the absolute fee amount for a given [`Weight`] at this fee rate. When the resulting
191+
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
192+
/// enough instead of falling short if rounded down.
193+
///
194+
/// If the calculation would overflow we saturate to [`Amount::MAX`]. Since such a fee can never
195+
/// be paid this is meaningful as an error case while still removing the possibility of silently
196+
/// wrapping.
197+
pub const fn to_fee(self, weight: Weight) -> Amount {
198+
// No `unwrap_or()` in const context.
199+
match self.checked_mul_by_weight(weight) {
200+
Some(fee) => fee,
201+
None => Amount::MAX,
202+
}
203+
}
204+
205+
/// Calculates the fee by multiplying this fee rate by weight, in weight units, returning [`None`]
206+
/// if an overflow occurred.
207+
///
208+
/// This is equivalent to `Self::checked_mul_by_weight()`.
209+
#[must_use]
210+
#[deprecated(since = "TBD", note = "use `to_fee()` instead")]
211+
pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.checked_mul_by_weight(weight) }
212+
213+
/// Calculates the fee by multiplying this fee rate by weight, in virtual bytes, returning [`None`]
214+
/// if an overflow occurred.
215+
///
216+
/// This is equivalent to converting `vb` to [`Weight`] using [`Weight::from_vb`] and then calling
217+
/// `Self::fee_wu(weight)`.
218+
#[must_use]
219+
#[deprecated(since = "TBD", note = "use Weight::from_vb and then `to_fee()` instead")]
220+
pub fn fee_vb(self, vb: u64) -> Option<Amount> { Weight::from_vb(vb).map(|w| self.to_fee(w)) }
221+
222+
/// Checked weight multiplication.
223+
///
224+
/// Computes the absolute fee amount for a given [`Weight`] at this fee rate. When the resulting
225+
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
226+
/// enough instead of falling short if rounded down.
227+
///
228+
/// Returns [`None`] if overflow occurred.
229+
#[must_use]
230+
pub const fn checked_mul_by_weight(self, weight: Weight) -> Option<Amount> {
231+
let wu = weight.to_wu();
232+
if let Some(fee_kwu) = self.to_sat_per_kwu_floor().checked_mul(wu) {
233+
// Bump by 999 to do ceil division using kwu.
234+
if let Some(bump) = fee_kwu.checked_add(999) {
235+
let fee = bump / 1_000;
236+
if let Ok(fee_amount) = Amount::from_sat(fee) {
237+
return Some(fee_amount);
238+
}
239+
}
240+
}
241+
None
242+
}
187243
}
188244

189245
crate::internal_macros::impl_op_for_references! {

0 commit comments

Comments
 (0)