Merge rust-bitcoin/rust-bitcoin#4534: Make `FeeRate` use MvB internally
56516757ad
Add code comment to amount calculation (Tobin C. Harding)8cf1dc39b5
Fix incorrect code comment (Tobin C. Harding)7c186e6081
Refactor fee functions (Tobin C. Harding)bf0776e3dd
Remove panic using checked arithmetic (Tobin C. Harding)b65860067f
Make fee functions const (Tobin C. Harding)9b2fc021b2
Improve rustdocs on FeeRate (Tobin C. Harding)1bd1e89458
Re-introduce FeeRate encapsulate module (Tobin C. Harding)b27d8e5819
Change the internal representation of FeeRate (Tobin C. Harding)2e0b88ba76
bitcoin: Fix dust 'fee' identifiers (Tobin C. Harding)399bca531c
Reduce the FeeRate::MAX value (Tobin C. Harding)d174c06a4a
Saturate to_fee to Amount::MAX (Tobin C. Harding)64ac33754f
Add missing argument docs (Tobin C. Harding)fe0a448e78
Temporarily remove const from fee calc function (Tobin C. Harding)b929022d56
Add floor/ceil versions of to_sat_per_kwu (Tobin C. Harding)64098e4578
Remove encapsulate module from fee rate (Tobin C. Harding) Pull request description: The `FeeRate` is a bit entangled with amount and weight. Also we have an off-by-one bug caused by rounding errors and the fact that we use kwu internally. We can get more precision in the fee rate by internally using per million virtual bytes. - Fix: #4516 - Fix: #4497 - Fix: #3806 ACKs for top commit: apoelstra: ACK 56516757ad8874d8121dba468946546bb8fd7d4e; successfully ran local tests Tree-SHA512: 0c6e7e2c9420886d5332e32519838d6ea415d269abda916e51d5847aa2475c87fa4abfc29b5f75e8b10c44df67ae29d823aa93f7cfabbc89eb27f2173b103242
This commit is contained in:
commit
a13ba99c11
|
@ -289,8 +289,8 @@ crate::internal_macros::define_extension_trait! {
|
|||
/// To use the default Bitcoin Core value, use [`minimal_non_dust`].
|
||||
///
|
||||
/// [`minimal_non_dust`]: Script::minimal_non_dust
|
||||
fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> Option<Amount> {
|
||||
self.minimal_non_dust_internal(dust_relay_fee.to_sat_per_kwu() * 4)
|
||||
fn minimal_non_dust_custom(&self, dust_relay: FeeRate) -> Option<Amount> {
|
||||
self.minimal_non_dust_internal(dust_relay.to_sat_per_kvb_ceil())
|
||||
}
|
||||
|
||||
/// Counts the sigops for this Script using accurate counting.
|
||||
|
@ -407,10 +407,10 @@ mod sealed {
|
|||
|
||||
crate::internal_macros::define_extension_trait! {
|
||||
pub(crate) trait ScriptExtPriv impl for Script {
|
||||
fn minimal_non_dust_internal(&self, dust_relay_fee: u64) -> Option<Amount> {
|
||||
fn minimal_non_dust_internal(&self, dust_relay_fee_rate_per_kvb: u64) -> Option<Amount> {
|
||||
// This must never be lower than Bitcoin Core's GetDustThreshold() (as of v0.21) as it may
|
||||
// otherwise allow users to create transactions which likely can never be broadcast/confirmed.
|
||||
let sats = dust_relay_fee
|
||||
let sats = dust_relay_fee_rate_per_kvb
|
||||
.checked_mul(if self.is_op_return() {
|
||||
0
|
||||
} else if self.is_witness_program() {
|
||||
|
|
|
@ -18,7 +18,6 @@ use hashes::sha256d;
|
|||
use internals::{compact_size, write_err, ToU64};
|
||||
use io::{BufRead, Write};
|
||||
use primitives::Sequence;
|
||||
use units::NumOpResult;
|
||||
|
||||
use super::Weight;
|
||||
use crate::consensus::{self, encode, Decodable, Encodable};
|
||||
|
@ -776,19 +775,18 @@ impl Decodable for Transaction {
|
|||
///
|
||||
/// * `fee_rate` - the fee rate of the transaction being created.
|
||||
/// * `input_weight_prediction` - the predicted input weight.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This will return [`NumOpResult::Error`] if the fee calculation (fee_rate * weight) overflows.
|
||||
/// Otherwise, [`NumOpResult::Valid`] will wrap the successful calculation.
|
||||
/// * `value` - The value of the output we are spending.
|
||||
pub fn effective_value(
|
||||
fee_rate: FeeRate,
|
||||
input_weight_prediction: InputWeightPrediction,
|
||||
value: Amount,
|
||||
) -> NumOpResult<SignedAmount> {
|
||||
) -> SignedAmount {
|
||||
let weight = input_weight_prediction.total_weight();
|
||||
let fee = fee_rate.to_fee(weight);
|
||||
|
||||
fee_rate.to_fee(weight).map(Amount::to_signed).and_then(|fee| value.to_signed() - fee) // Cannot overflow.
|
||||
// Cannot overflow because after conversion to signed Amount::MIN - Amount::MAX
|
||||
// still fits in SignedAmount::MAX (0 - MAX = -MAX).
|
||||
(value.to_signed() - fee.to_signed()).expect("cannot overflow")
|
||||
}
|
||||
|
||||
/// Predicts the weight of a to-be-constructed transaction.
|
||||
|
@ -1691,11 +1689,10 @@ mod tests {
|
|||
#[test]
|
||||
fn effective_value_happy_path() {
|
||||
let value = "1 cBTC".parse::<Amount>().unwrap();
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10);
|
||||
let effective_value =
|
||||
effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value).unwrap();
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap();
|
||||
let effective_value = effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value);
|
||||
|
||||
// 10 sat/kwu * 272 wu = 4 sats (rounding up)
|
||||
// 10 sat/kwu * 272 wu = 3 sats (rounding up)
|
||||
let expected_fee = "3 sats".parse::<SignedAmount>().unwrap();
|
||||
let expected_effective_value = (value.to_signed() - expected_fee).unwrap();
|
||||
assert_eq!(effective_value, expected_effective_value);
|
||||
|
@ -1705,7 +1702,8 @@ mod tests {
|
|||
fn effective_value_fee_rate_does_not_overflow() {
|
||||
let eff_value =
|
||||
effective_value(FeeRate::MAX, InputWeightPrediction::P2WPKH_MAX, Amount::ZERO);
|
||||
assert!(eff_value.is_error());
|
||||
let want = SignedAmount::from_sat(-1254378597012250).unwrap(); // U64::MAX / 4_000 because of FeeRate::MAX
|
||||
assert_eq!(eff_value, want)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1178,8 +1178,11 @@ impl fmt::Display for ExtractTxError {
|
|||
use ExtractTxError::*;
|
||||
|
||||
match *self {
|
||||
AbsurdFeeRate { fee_rate, .. } =>
|
||||
write!(f, "an absurdly high fee rate of {} sat/kwu", fee_rate.to_sat_per_kwu()),
|
||||
AbsurdFeeRate { fee_rate, .. } => write!(
|
||||
f,
|
||||
"an absurdly high fee rate of {} sat/kwu",
|
||||
fee_rate.to_sat_per_kwu_floor()
|
||||
),
|
||||
MissingInputValue { .. } => write!(
|
||||
f,
|
||||
"one of the inputs lacked value information (witness_utxo or non_witness_utxo)"
|
||||
|
@ -1353,9 +1356,15 @@ mod tests {
|
|||
use crate::Sequence;
|
||||
|
||||
/// Fee rate in sat/kwu for a high-fee PSBT with an input=5_000_000_000_000, output=1000
|
||||
const ABSURD_FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(15_060_240_960_843);
|
||||
/// Fee rate which is just below absurd threshold (1 sat/kwu less)
|
||||
const JUST_BELOW_ABSURD_FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(15_060_240_960_842);
|
||||
const ABSURD_FEE_RATE: FeeRate = match FeeRate::from_sat_per_kwu(15_060_240_960_843) {
|
||||
Some(fee_rate) => fee_rate,
|
||||
None => panic!("unreachable - no unwrap in Rust 1.63 in const"),
|
||||
};
|
||||
const JUST_BELOW_ABSURD_FEE_RATE: FeeRate = match FeeRate::from_sat_per_kwu(15_060_240_960_842)
|
||||
{
|
||||
Some(fee_rate) => fee_rate,
|
||||
None => panic!("unreachable - no unwrap in Rust 1.63 in const"),
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
pub fn hex_psbt(s: &str) -> Result<Psbt, crate::psbt::error::Error> {
|
||||
|
@ -1472,7 +1481,7 @@ mod tests {
|
|||
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
|
||||
_ => panic!(""),
|
||||
}),
|
||||
Err(FeeRate::from_sat_per_kwu(6250003)) // 6250000 is 25k sat/vbyte
|
||||
Err(FeeRate::from_sat_per_kwu(6250003).unwrap()) // 6250000 is 25k sat/vbyte
|
||||
);
|
||||
|
||||
// Lowering the input satoshis by 1 lowers the sat/kwu by 3
|
||||
|
|
|
@ -17,7 +17,7 @@ fn do_test(data: &[u8]) {
|
|||
let _ = script.count_sigops_legacy();
|
||||
let _ = script.minimal_non_dust();
|
||||
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(consume_u64(&mut new_data));
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(consume_u64(&mut new_data)).unwrap();
|
||||
let _ = script.minimal_non_dust_custom(fee_rate);
|
||||
|
||||
let mut b = script::Builder::new();
|
||||
|
|
|
@ -270,13 +270,13 @@ 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();
|
||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
|
||||
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();
|
||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||
// round up to 864
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(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);
|
||||
assert!(fee_rate.is_none());
|
||||
|
@ -288,13 +288,13 @@ 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();
|
||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
|
||||
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();
|
||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||
// round down to 863
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(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);
|
||||
assert!(fee_rate.is_none());
|
||||
|
@ -304,7 +304,7 @@ fn amount_checked_div_by_weight_floor() {
|
|||
#[test]
|
||||
fn amount_checked_div_by_fee_rate() {
|
||||
let amount = sat(1000);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
||||
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();
|
||||
|
@ -317,20 +317,20 @@ fn amount_checked_div_by_fee_rate() {
|
|||
|
||||
// Test truncation behavior
|
||||
let amount = sat(1000);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(3);
|
||||
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();
|
||||
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);
|
||||
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());
|
||||
|
||||
// Test with maximum amount
|
||||
let max_amount = Amount::MAX;
|
||||
let small_fee_rate = FeeRate::from_sat_per_kwu(1);
|
||||
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();
|
||||
// 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));
|
||||
|
|
145
units/src/fee.rs
145
units/src/fee.rs
|
@ -26,12 +26,17 @@ impl Amount {
|
|||
/// Returns [`None`] if overflow occurred.
|
||||
#[must_use]
|
||||
pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option<FeeRate> {
|
||||
// No `?` operator in const context.
|
||||
let wu = weight.to_wu();
|
||||
if wu == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Mul by 1,000 because we use per/kwu.
|
||||
match self.to_sat().checked_mul(1_000) {
|
||||
Some(res) => match res.checked_div(weight.to_wu()) {
|
||||
Some(fee_rate) => Some(FeeRate::from_sat_per_kwu(fee_rate)),
|
||||
None => None,
|
||||
},
|
||||
Some(sats) => {
|
||||
let fee_rate = sats / wu;
|
||||
FeeRate::from_sat_per_kwu(fee_rate)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -51,20 +56,22 @@ impl Amount {
|
|||
/// let amount = Amount::from_sat(10)?;
|
||||
/// let weight = Weight::from_wu(300);
|
||||
/// let fee_rate = amount.checked_div_by_weight_ceil(weight);
|
||||
/// assert_eq!(fee_rate, Some(FeeRate::from_sat_per_kwu(34)));
|
||||
/// 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> {
|
||||
let wu = weight.to_wu();
|
||||
// No `?` operator in const context.
|
||||
if wu == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Mul by 1,000 because we use per/kwu.
|
||||
if let Some(sats) = self.to_sat().checked_mul(1_000) {
|
||||
if let Some(wu_minus_one) = wu.checked_sub(1) {
|
||||
if let Some(sats_plus_wu_minus_one) = sats.checked_add(wu_minus_one) {
|
||||
if let Some(fee_rate) = sats_plus_wu_minus_one.checked_div(wu) {
|
||||
return Some(FeeRate::from_sat_per_kwu(fee_rate));
|
||||
}
|
||||
}
|
||||
// No need to used checked arithmetic because wu is non-zero.
|
||||
if let Some(bump) = sats.checked_add(wu - 1) {
|
||||
let fee_rate = bump / wu;
|
||||
return FeeRate::from_sat_per_kwu(fee_rate);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
@ -79,13 +86,12 @@ 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> {
|
||||
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,
|
||||
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));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Checked fee rate ceiling division.
|
||||
|
@ -96,30 +102,39 @@ 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> {
|
||||
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,
|
||||
// Use ceil because result is used as the divisor.
|
||||
let rate = fee_rate.to_sat_per_kwu_ceil();
|
||||
if rate == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(msats) = self.to_sat().checked_mul(1000) {
|
||||
// No need to used checked arithmetic because rate is non-zero.
|
||||
if let Some(bump) = msats.checked_add(rate - 1) {
|
||||
let wu = bump / rate;
|
||||
return Some(Weight::from_wu(wu));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl FeeRate {
|
||||
/// Calculates the fee by multiplying this fee rate by weight, in weight units, returning
|
||||
/// [`NumOpResult::Error`] if an overflow occurred.
|
||||
/// Calculates the fee by multiplying this fee rate by weight.
|
||||
///
|
||||
/// This is equivalent to `Self::checked_mul_by_weight()`.
|
||||
pub const fn to_fee(self, weight: Weight) -> NumOpResult<Amount> {
|
||||
self.checked_mul_by_weight(weight)
|
||||
/// Computes the absolute fee amount for a given [`Weight`] at this fee rate. When the resulting
|
||||
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
|
||||
/// enough instead of falling short if rounded down.
|
||||
///
|
||||
/// If the calculation would overflow we saturate to [`Amount::MAX`]. Since such a fee can never
|
||||
/// be paid this is meaningful as an error case while still removing the possibility of silently
|
||||
/// wrapping.
|
||||
pub const fn to_fee(self, weight: Weight) -> Amount {
|
||||
// No `unwrap_or()` in const context.
|
||||
match self.checked_mul_by_weight(weight) {
|
||||
Some(fee) => fee,
|
||||
None => Amount::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the fee by multiplying this fee rate by weight, in weight units, returning [`None`]
|
||||
|
@ -128,9 +143,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).ok()
|
||||
}
|
||||
pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.checked_mul_by_weight(weight) }
|
||||
|
||||
/// Calculates the fee by multiplying this fee rate by weight, in virtual bytes, returning [`None`]
|
||||
/// if an overflow occurred.
|
||||
|
@ -139,9 +152,7 @@ impl FeeRate {
|
|||
/// `Self::fee_wu(weight)`.
|
||||
#[must_use]
|
||||
#[deprecated(since = "TBD", note = "use Weight::from_vb and then `to_fee()` instead")]
|
||||
pub fn fee_vb(self, vb: u64) -> Option<Amount> {
|
||||
Weight::from_vb(vb).and_then(|w| self.to_fee(w).ok())
|
||||
}
|
||||
pub fn fee_vb(self, vb: u64) -> Option<Amount> { Weight::from_vb(vb).map(|w| self.to_fee(w)) }
|
||||
|
||||
/// Checked weight multiplication.
|
||||
///
|
||||
|
@ -149,16 +160,19 @@ impl FeeRate {
|
|||
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
|
||||
/// enough instead of falling short if rounded down.
|
||||
///
|
||||
/// Returns [`NumOpResult::Error`] if overflow occurred.
|
||||
pub const fn checked_mul_by_weight(self, weight: Weight) -> NumOpResult<Amount> {
|
||||
if let Some(fee) = self.to_sat_per_kwu().checked_mul(weight.to_wu()) {
|
||||
if let Some(round_up) = fee.checked_add(999) {
|
||||
if let Ok(ret) = Amount::from_sat(round_up / 1_000) {
|
||||
return NumOpResult::Valid(ret);
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub const fn checked_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.
|
||||
if let Some(bump) = fee_kwu.checked_add(999) {
|
||||
let fee = bump / 1_000;
|
||||
if let Ok(fee_amount) = Amount::from_sat(fee) {
|
||||
return Some(fee_amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
NumOpResult::Error(E::while_doing(MathOp::Mul))
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +180,10 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Mul<FeeRate> for Weight {
|
||||
type Output = NumOpResult<Amount>;
|
||||
fn mul(self, rhs: FeeRate) -> Self::Output {
|
||||
rhs.checked_mul_by_weight(self)
|
||||
match rhs.checked_mul_by_weight(self) {
|
||||
Some(amount) => R::Valid(amount),
|
||||
None => R::Error(E::while_doing(MathOp::Mul)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ops::Mul<FeeRate> for NumOpResult<Weight> {
|
||||
|
@ -203,7 +220,10 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Mul<Weight> for FeeRate {
|
||||
type Output = NumOpResult<Amount>;
|
||||
fn mul(self, rhs: Weight) -> Self::Output {
|
||||
self.checked_mul_by_weight(rhs)
|
||||
match self.checked_mul_by_weight(rhs) {
|
||||
Some(amount) => R::Valid(amount),
|
||||
None => R::Error(E::while_doing(MathOp::Mul)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ops::Mul<Weight> for NumOpResult<FeeRate> {
|
||||
|
@ -328,7 +348,7 @@ impl Weight {
|
|||
/// enough instead of falling short if rounded down.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub const fn checked_mul_by_fee_rate(self, fee_rate: FeeRate) -> NumOpResult<Amount> {
|
||||
pub const fn checked_mul_by_fee_rate(self, fee_rate: FeeRate) -> Option<Amount> {
|
||||
fee_rate.checked_mul_by_weight(self)
|
||||
}
|
||||
}
|
||||
|
@ -340,17 +360,14 @@ mod tests {
|
|||
#[test]
|
||||
fn fee_rate_div_by_weight() {
|
||||
let fee_rate = (Amount::from_sat_u32(329) / Weight::from_wu(381)).unwrap();
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_wu() {
|
||||
let operation = FeeRate::from_sat_per_kwu(10).to_fee(Weight::MAX).unwrap_err().operation();
|
||||
assert!(operation.is_multiplication());
|
||||
|
||||
let fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
|
||||
let weight = Weight::from_vb(3).unwrap();
|
||||
assert_eq!(fee_rate.to_fee(weight).unwrap(), Amount::from_sat_u32(6));
|
||||
assert_eq!(fee_rate.to_fee(weight), Amount::from_sat_u32(6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -362,8 +379,8 @@ mod tests {
|
|||
.expect("expected Amount");
|
||||
assert_eq!(Amount::from_sat_u32(100), fee);
|
||||
|
||||
let fee = FeeRate::from_sat_per_kwu(10).checked_mul_by_weight(Weight::MAX);
|
||||
assert!(fee.is_error());
|
||||
let fee = FeeRate::from_sat_per_kwu(10).unwrap().checked_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();
|
||||
|
@ -371,7 +388,7 @@ mod tests {
|
|||
assert_eq!(Amount::from_sat_u32(9), fee);
|
||||
|
||||
let weight = Weight::from_wu(381);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(864);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(864).unwrap();
|
||||
let fee = weight.checked_mul_by_fee_rate(fee_rate).unwrap();
|
||||
// 381 * 0.864 yields 329.18.
|
||||
// The result is then rounded up to 330.
|
||||
|
@ -398,7 +415,7 @@ mod tests {
|
|||
fn amount_div_by_fee_rate() {
|
||||
// Test exact division
|
||||
let amount = Amount::from_sat_u32(1000);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(2);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
let weight = (amount / fee_rate).unwrap();
|
||||
assert_eq!(weight, Weight::from_wu(500_000));
|
||||
|
||||
|
@ -412,7 +429,7 @@ mod tests {
|
|||
|
||||
// Test truncation behavior
|
||||
let amount = Amount::from_sat_u32(1000);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(3);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||
let weight = (amount / fee_rate).unwrap();
|
||||
// 1000 * 1000 = 1,000,000 msats
|
||||
// 1,000,000 / 3 = 333,333.33... wu
|
||||
|
@ -424,7 +441,7 @@ mod tests {
|
|||
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);
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -14,74 +14,98 @@ use arbitrary::{Arbitrary, Unstructured};
|
|||
mod encapsulate {
|
||||
/// Fee rate.
|
||||
///
|
||||
/// This is an integer newtype representing fee rate in `sat/kwu`. It provides protection
|
||||
/// against mixing up the types as well as basic formatting features.
|
||||
/// This is an integer newtype representing fee rate. It provides protection
|
||||
/// against mixing up the types, conversion functions, and basic formatting.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct FeeRate(u64);
|
||||
|
||||
impl FeeRate {
|
||||
/// Constructs a new [`FeeRate`] from satoshis per 1000 weight units.
|
||||
pub const fn from_sat_per_kwu(sat_kwu: u64) -> Self { FeeRate(sat_kwu) }
|
||||
/// Constructs a new [`FeeRate`] from satoshis per 1,000,000 virtual bytes.
|
||||
pub(crate) const fn from_sat_per_mvb(sat_mvb: u64) -> Self { Self(sat_mvb) }
|
||||
|
||||
/// Returns raw fee rate.
|
||||
///
|
||||
/// Can be used instead of `into()` to avoid inference issues.
|
||||
pub const fn to_sat_per_kwu(self) -> u64 { self.0 }
|
||||
/// Converts to sat/MvB.
|
||||
pub(crate) const fn to_sat_per_mvb(self) -> u64 { self.0 }
|
||||
}
|
||||
}
|
||||
#[doc(inline)]
|
||||
pub use encapsulate::FeeRate;
|
||||
|
||||
impl FeeRate {
|
||||
/// 0 sat/kwu.
|
||||
/// The zero fee rate.
|
||||
///
|
||||
/// Equivalent to [`MIN`](Self::MIN), may better express intent in some contexts.
|
||||
pub const ZERO: FeeRate = FeeRate::from_sat_per_kwu(0);
|
||||
pub const ZERO: FeeRate = FeeRate::from_sat_per_mvb(0);
|
||||
|
||||
/// Minimum possible value (0 sat/kwu).
|
||||
/// The minimum possible value.
|
||||
///
|
||||
/// Equivalent to [`ZERO`](Self::ZERO), may better express intent in some contexts.
|
||||
pub const MIN: FeeRate = FeeRate::ZERO;
|
||||
|
||||
/// Maximum possible value.
|
||||
pub const MAX: FeeRate = FeeRate::from_sat_per_kwu(u64::MAX);
|
||||
/// The maximum possible value.
|
||||
pub const MAX: FeeRate = FeeRate::from_sat_per_mvb(u64::MAX);
|
||||
|
||||
/// Minimum fee rate required to broadcast a transaction.
|
||||
/// The minimum fee rate required to broadcast a transaction.
|
||||
///
|
||||
/// The value matches the default Bitcoin Core policy at the time of library release.
|
||||
pub const BROADCAST_MIN: FeeRate = FeeRate::from_sat_per_vb_u32(1);
|
||||
|
||||
/// Fee rate used to compute dust amount.
|
||||
/// The fee rate used to compute dust amount.
|
||||
pub const DUST: FeeRate = FeeRate::from_sat_per_vb_u32(3);
|
||||
|
||||
/// Constructs a new [`FeeRate`] from satoshis per 1000 weight units.
|
||||
pub const fn from_sat_per_kwu(sat_kwu: u64) -> Option<Self> {
|
||||
// No `map()` in const context.
|
||||
match sat_kwu.checked_mul(4_000) {
|
||||
Some(fee_rate) => Some(FeeRate::from_sat_per_mvb(fee_rate)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new [`FeeRate`] from satoshis per virtual bytes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`None`] on arithmetic overflow.
|
||||
pub fn from_sat_per_vb(sat_vb: u64) -> Option<Self> {
|
||||
// 1 vb == 4 wu
|
||||
// 1 sat/vb == 1/4 sat/wu
|
||||
// sat/vb * 1000 / 4 == sat/kwu
|
||||
Some(FeeRate::from_sat_per_kwu(sat_vb.checked_mul(1000 / 4)?))
|
||||
pub const fn from_sat_per_vb(sat_vb: u64) -> Option<Self> {
|
||||
// No `map()` in const context.
|
||||
match sat_vb.checked_mul(1_000_000) {
|
||||
Some(fee_rate) => Some(FeeRate::from_sat_per_mvb(fee_rate)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new [`FeeRate`] from satoshis per virtual bytes.
|
||||
pub const fn from_sat_per_vb_u32(sat_vb: u32) -> Self {
|
||||
let sat_vb = sat_vb as u64; // No `Into` in const context.
|
||||
FeeRate::from_sat_per_kwu(sat_vb * (1000 / 4))
|
||||
FeeRate::from_sat_per_mvb(sat_vb * 1_000_000)
|
||||
}
|
||||
|
||||
/// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes).
|
||||
pub const fn from_sat_per_kvb(sat_kvb: u64) -> Self { FeeRate::from_sat_per_kwu(sat_kvb / 4) }
|
||||
pub const fn from_sat_per_kvb(sat_kvb: u64) -> Option<Self> {
|
||||
// No `map()` in const context.
|
||||
match sat_kvb.checked_mul(1_000) {
|
||||
Some(fee_rate) => Some(FeeRate::from_sat_per_mvb(fee_rate)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts to sat/kwu rounding down.
|
||||
pub const fn to_sat_per_kwu_floor(self) -> u64 { self.to_sat_per_mvb() / 4_000 }
|
||||
|
||||
/// Converts to sat/kwu rounding up.
|
||||
pub const fn to_sat_per_kwu_ceil(self) -> u64 { (self.to_sat_per_mvb() + 3_999) / 4_000 }
|
||||
|
||||
/// Converts to sat/vB rounding down.
|
||||
pub const fn to_sat_per_vb_floor(self) -> u64 { self.to_sat_per_kwu() / (1000 / 4) }
|
||||
pub const fn to_sat_per_vb_floor(self) -> u64 { self.to_sat_per_mvb() / 1_000_000 }
|
||||
|
||||
/// Converts to sat/vB rounding up.
|
||||
pub const fn to_sat_per_vb_ceil(self) -> u64 {
|
||||
(self.to_sat_per_kwu() + (1000 / 4 - 1)) / (1000 / 4)
|
||||
}
|
||||
pub const fn to_sat_per_vb_ceil(self) -> u64 { (self.to_sat_per_mvb() + 999_999) / 1_000_000 }
|
||||
|
||||
/// Converts to sat/kvb rounding down.
|
||||
pub const fn to_sat_per_kvb_floor(self) -> u64 { self.to_sat_per_mvb() / 1_000 }
|
||||
|
||||
/// Converts to sat/kvb rounding up.
|
||||
pub const fn to_sat_per_kvb_ceil(self) -> u64 { (self.to_sat_per_mvb() + 999) / 1_000 }
|
||||
|
||||
/// Checked multiplication.
|
||||
///
|
||||
|
@ -89,8 +113,8 @@ impl FeeRate {
|
|||
#[must_use]
|
||||
pub const fn checked_mul(self, rhs: u64) -> Option<Self> {
|
||||
// No `map()` in const context.
|
||||
match self.to_sat_per_kwu().checked_mul(rhs) {
|
||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
||||
match self.to_sat_per_mvb().checked_mul(rhs) {
|
||||
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -101,8 +125,8 @@ impl FeeRate {
|
|||
#[must_use]
|
||||
pub const fn checked_div(self, rhs: u64) -> Option<Self> {
|
||||
// No `map()` in const context.
|
||||
match self.to_sat_per_kwu().checked_div(rhs) {
|
||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
||||
match self.to_sat_per_mvb().checked_div(rhs) {
|
||||
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -113,8 +137,8 @@ impl FeeRate {
|
|||
#[must_use]
|
||||
pub const fn checked_add(self, rhs: FeeRate) -> Option<Self> {
|
||||
// No `map()` in const context.
|
||||
match self.to_sat_per_kwu().checked_add(rhs.to_sat_per_kwu()) {
|
||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
||||
match self.to_sat_per_mvb().checked_add(rhs.to_sat_per_mvb()) {
|
||||
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -125,8 +149,8 @@ impl FeeRate {
|
|||
#[must_use]
|
||||
pub const fn checked_sub(self, rhs: FeeRate) -> Option<Self> {
|
||||
// No `map()` in const context.
|
||||
match self.to_sat_per_kwu().checked_sub(rhs.to_sat_per_kwu()) {
|
||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
||||
match self.to_sat_per_mvb().checked_sub(rhs.to_sat_per_mvb()) {
|
||||
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
@ -136,19 +160,19 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Add<FeeRate> for FeeRate {
|
||||
type Output = FeeRate;
|
||||
|
||||
fn add(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_kwu(self.to_sat_per_kwu() + rhs.to_sat_per_kwu()) }
|
||||
fn add(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_mvb(self.to_sat_per_mvb() + rhs.to_sat_per_mvb()) }
|
||||
}
|
||||
|
||||
impl ops::Sub<FeeRate> for FeeRate {
|
||||
type Output = FeeRate;
|
||||
|
||||
fn sub(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_kwu(self.to_sat_per_kwu() - rhs.to_sat_per_kwu()) }
|
||||
fn sub(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_mvb(self.to_sat_per_mvb() - rhs.to_sat_per_mvb()) }
|
||||
}
|
||||
|
||||
impl ops::Div<NonZeroU64> for FeeRate {
|
||||
type Output = FeeRate;
|
||||
|
||||
fn div(self, rhs: NonZeroU64) -> Self::Output{ Self::from_sat_per_kwu(self.to_sat_per_kwu() / rhs.get()) }
|
||||
fn div(self, rhs: NonZeroU64) -> Self::Output{ Self::from_sat_per_mvb(self.to_sat_per_mvb() / rhs.get()) }
|
||||
}
|
||||
}
|
||||
crate::internal_macros::impl_add_assign!(FeeRate);
|
||||
|
@ -159,7 +183,7 @@ impl core::iter::Sum for FeeRate {
|
|||
where
|
||||
I: Iterator<Item = Self>,
|
||||
{
|
||||
FeeRate::from_sat_per_kwu(iter.map(FeeRate::to_sat_per_kwu).sum())
|
||||
FeeRate::from_sat_per_mvb(iter.map(FeeRate::to_sat_per_mvb).sum())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +192,7 @@ impl<'a> core::iter::Sum<&'a FeeRate> for FeeRate {
|
|||
where
|
||||
I: Iterator<Item = &'a FeeRate>,
|
||||
{
|
||||
FeeRate::from_sat_per_kwu(iter.map(|f| FeeRate::to_sat_per_kwu(*f)).sum())
|
||||
FeeRate::from_sat_per_mvb(iter.map(|f| FeeRate::to_sat_per_mvb(*f)).sum())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +205,7 @@ impl<'a> Arbitrary<'a> for FeeRate {
|
|||
1 => Ok(FeeRate::BROADCAST_MIN),
|
||||
2 => Ok(FeeRate::DUST),
|
||||
3 => Ok(FeeRate::MAX),
|
||||
_ => Ok(FeeRate::from_sat_per_kwu(u64::arbitrary(u)?)),
|
||||
_ => Ok(FeeRate::from_sat_per_mvb(u64::arbitrary(u)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,18 +219,18 @@ mod tests {
|
|||
#[test]
|
||||
#[allow(clippy::op_ref)]
|
||||
fn feerate_div_nonzero() {
|
||||
let rate = FeeRate::from_sat_per_kwu(200);
|
||||
let rate = FeeRate::from_sat_per_kwu(200).unwrap();
|
||||
let divisor = NonZeroU64::new(2).unwrap();
|
||||
assert_eq!(rate / divisor, FeeRate::from_sat_per_kwu(100));
|
||||
assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100));
|
||||
assert_eq!(rate / divisor, FeeRate::from_sat_per_kwu(100).unwrap());
|
||||
assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::op_ref)]
|
||||
fn addition() {
|
||||
let one = FeeRate::from_sat_per_kwu(1);
|
||||
let two = FeeRate::from_sat_per_kwu(2);
|
||||
let three = FeeRate::from_sat_per_kwu(3);
|
||||
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||
|
||||
assert!(one + two == three);
|
||||
assert!(&one + two == three);
|
||||
|
@ -217,9 +241,9 @@ mod tests {
|
|||
#[test]
|
||||
#[allow(clippy::op_ref)]
|
||||
fn subtract() {
|
||||
let three = FeeRate::from_sat_per_kwu(3);
|
||||
let seven = FeeRate::from_sat_per_kwu(7);
|
||||
let ten = FeeRate::from_sat_per_kwu(10);
|
||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||
let seven = FeeRate::from_sat_per_kwu(7).unwrap();
|
||||
let ten = FeeRate::from_sat_per_kwu(10).unwrap();
|
||||
|
||||
assert_eq!(ten - seven, three);
|
||||
assert_eq!(&ten - seven, three);
|
||||
|
@ -229,43 +253,44 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn add_assign() {
|
||||
let mut f = FeeRate::from_sat_per_kwu(1);
|
||||
f += FeeRate::from_sat_per_kwu(2);
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3));
|
||||
let mut f = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||
f += FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap());
|
||||
|
||||
let mut f = FeeRate::from_sat_per_kwu(1);
|
||||
f += &FeeRate::from_sat_per_kwu(2);
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3));
|
||||
let mut f = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||
f += &FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_assign() {
|
||||
let mut f = FeeRate::from_sat_per_kwu(3);
|
||||
f -= FeeRate::from_sat_per_kwu(2);
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1));
|
||||
let mut f = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||
f -= FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap());
|
||||
|
||||
let mut f = FeeRate::from_sat_per_kwu(3);
|
||||
f -= &FeeRate::from_sat_per_kwu(2);
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1));
|
||||
let mut f = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||
f -= &FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_add() {
|
||||
let one = FeeRate::from_sat_per_kwu(1);
|
||||
let two = FeeRate::from_sat_per_kwu(2);
|
||||
let three = FeeRate::from_sat_per_kwu(3);
|
||||
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||
|
||||
assert_eq!(one.checked_add(two).unwrap(), three);
|
||||
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(u64::MAX).checked_add(one);
|
||||
assert!(FeeRate::from_sat_per_kvb(u64::MAX).is_none()); // sanity check.
|
||||
let fee_rate = FeeRate::from_sat_per_mvb(u64::MAX).checked_add(one);
|
||||
assert!(fee_rate.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_sub() {
|
||||
let one = FeeRate::from_sat_per_kwu(1);
|
||||
let two = FeeRate::from_sat_per_kwu(2);
|
||||
let three = FeeRate::from_sat_per_kwu(3);
|
||||
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
||||
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||
assert_eq!(three.checked_sub(two).unwrap(), one);
|
||||
|
||||
let fee_rate = FeeRate::ZERO.checked_sub(one);
|
||||
|
@ -274,23 +299,23 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn fee_rate_const() {
|
||||
assert_eq!(FeeRate::ZERO.to_sat_per_kwu(), 0);
|
||||
assert_eq!(FeeRate::MIN.to_sat_per_kwu(), u64::MIN);
|
||||
assert_eq!(FeeRate::MAX.to_sat_per_kwu(), u64::MAX);
|
||||
assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu(), 250);
|
||||
assert_eq!(FeeRate::DUST.to_sat_per_kwu(), 750);
|
||||
assert_eq!(FeeRate::ZERO.to_sat_per_kwu_floor(), 0);
|
||||
assert_eq!(FeeRate::MIN.to_sat_per_kwu_floor(), u64::MIN);
|
||||
assert_eq!(FeeRate::MAX.to_sat_per_kwu_floor(), u64::MAX / 4_000);
|
||||
assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu_floor(), 250);
|
||||
assert_eq!(FeeRate::DUST.to_sat_per_kwu_floor(), 750);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_rate_from_sat_per_vb() {
|
||||
let fee_rate = FeeRate::from_sat_per_vb(10).expect("expected feerate in sat/kwu");
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500));
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_rate_from_sat_per_kvb() {
|
||||
let fee_rate = FeeRate::from_sat_per_kvb(11);
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2));
|
||||
let fee_rate = FeeRate::from_sat_per_kvb(11).unwrap();
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(11_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -302,7 +327,7 @@ mod tests {
|
|||
#[test]
|
||||
fn from_sat_per_vb_u32() {
|
||||
let fee_rate = FeeRate::from_sat_per_vb_u32(10);
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500));
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2500).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -311,29 +336,40 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn raw_feerate() {
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(749);
|
||||
assert_eq!(fee_rate.to_sat_per_kwu(), 749);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(749).unwrap();
|
||||
assert_eq!(fee_rate.to_sat_per_kwu_floor(), 749);
|
||||
assert_eq!(fee_rate.to_sat_per_vb_floor(), 2);
|
||||
assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_mul() {
|
||||
let fee_rate =
|
||||
FeeRate::from_sat_per_kwu(10).checked_mul(10).expect("expected feerate in sat/kwu");
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(100));
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
||||
.unwrap()
|
||||
.checked_mul(10)
|
||||
.expect("expected feerate in sat/kwu");
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(100).unwrap());
|
||||
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10).checked_mul(u64::MAX);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap().checked_mul(u64::MAX);
|
||||
assert!(fee_rate.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_div() {
|
||||
let fee_rate =
|
||||
FeeRate::from_sat_per_kwu(10).checked_div(10).expect("expected feerate in sat/kwu");
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
||||
.unwrap()
|
||||
.checked_div(10)
|
||||
.expect("expected feerate in sat/kwu");
|
||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1).unwrap());
|
||||
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10).checked_div(0);
|
||||
let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap().checked_div(0);
|
||||
assert!(fee_rate.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mvb() {
|
||||
let fee_rate = FeeRate::from_sat_per_mvb(1_234_567);
|
||||
let got = fee_rate.to_sat_per_mvb();
|
||||
assert_eq!(got, 1_234_567);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
//!
|
||||
//! #[derive(Serialize, Deserialize)]
|
||||
//! pub struct Foo {
|
||||
//! #[serde(with = "fee_rate::serde::as_sat_per_kwu")]
|
||||
//! #[serde(with = "fee_rate::serde::as_sat_per_kwu_floor")]
|
||||
//! pub fee_rate: FeeRate,
|
||||
//! }
|
||||
//! ```
|
||||
|
@ -26,7 +26,7 @@
|
|||
use core::convert::Infallible;
|
||||
use core::fmt;
|
||||
|
||||
pub mod as_sat_per_kwu {
|
||||
pub mod as_sat_per_kwu_floor {
|
||||
//! Serialize and deserialize [`FeeRate`] denominated in satoshis per 1000 weight units.
|
||||
//!
|
||||
//! Use with `#[serde(with = "fee_rate::serde::as_sat_per_kwu")]`.
|
||||
|
@ -36,11 +36,12 @@ pub mod as_sat_per_kwu {
|
|||
use crate::FeeRate;
|
||||
|
||||
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
||||
u64::serialize(&f.to_sat_per_kwu(), s)
|
||||
u64::serialize(&f.to_sat_per_kwu_floor(), s)
|
||||
}
|
||||
|
||||
pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
|
||||
Ok(FeeRate::from_sat_per_kwu(u64::deserialize(d)?))
|
||||
FeeRate::from_sat_per_kwu(u64::deserialize(d)?)
|
||||
.ok_or_else(|| serde::de::Error::custom("overflowed sats/kwu"))
|
||||
}
|
||||
|
||||
pub mod opt {
|
||||
|
@ -57,7 +58,7 @@ pub mod as_sat_per_kwu {
|
|||
#[allow(clippy::ref_option)] // API forced by serde.
|
||||
pub fn serialize<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> {
|
||||
match *f {
|
||||
Some(f) => s.serialize_some(&f.to_sat_per_kwu()),
|
||||
Some(f) => s.serialize_some(&f.to_sat_per_kwu_floor()),
|
||||
None => s.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,23 +90,6 @@ pub enum NumOpResult<T> {
|
|||
impl<T> NumOpResult<T> {
|
||||
/// Maps a `NumOpResult<T>` to `NumOpResult<U>` by applying a function to a
|
||||
/// contained [`NumOpResult::Valid`] value, leaving a [`NumOpResult::Error`] value untouched.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use bitcoin_units::{FeeRate, Amount, Weight, SignedAmount};
|
||||
///
|
||||
/// let fee_rate = FeeRate::from_sat_per_vb(1).unwrap();
|
||||
/// let weight = Weight::from_wu(1000);
|
||||
/// let amount = Amount::from_sat_u32(1_000_000);
|
||||
///
|
||||
/// let amount_after_fee = fee_rate
|
||||
/// .to_fee(weight) // (1 sat/ 4 wu) * (1000 wu) = 250 sat fee
|
||||
/// .map(|fee| fee.to_signed())
|
||||
/// .and_then(|fee| amount.to_signed() - fee);
|
||||
///
|
||||
/// assert_eq!(amount_after_fee.unwrap(), SignedAmount::from_sat_i32(999_750))
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> NumOpResult<U> {
|
||||
match self {
|
||||
|
|
|
@ -37,13 +37,13 @@ struct Serde {
|
|||
vb_floor: FeeRate,
|
||||
#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil")]
|
||||
vb_ceil: FeeRate,
|
||||
#[serde(with = "fee_rate::serde::as_sat_per_kwu")]
|
||||
#[serde(with = "fee_rate::serde::as_sat_per_kwu_floor")]
|
||||
kwu: FeeRate,
|
||||
#[serde(with = "fee_rate::serde::as_sat_per_vb_floor::opt")]
|
||||
opt_vb_floor: Option<FeeRate>,
|
||||
#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil::opt")]
|
||||
opt_vb_ceil: Option<FeeRate>,
|
||||
#[serde(with = "fee_rate::serde::as_sat_per_kwu::opt")]
|
||||
#[serde(with = "fee_rate::serde::as_sat_per_kwu_floor::opt")]
|
||||
opt_kwu: Option<FeeRate>,
|
||||
|
||||
a: BlockHeight,
|
||||
|
|
Loading…
Reference in New Issue