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`].
|
/// To use the default Bitcoin Core value, use [`minimal_non_dust`].
|
||||||
///
|
///
|
||||||
/// [`minimal_non_dust`]: Script::minimal_non_dust
|
/// [`minimal_non_dust`]: Script::minimal_non_dust
|
||||||
fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> Option<Amount> {
|
fn minimal_non_dust_custom(&self, dust_relay: FeeRate) -> Option<Amount> {
|
||||||
self.minimal_non_dust_internal(dust_relay_fee.to_sat_per_kwu() * 4)
|
self.minimal_non_dust_internal(dust_relay.to_sat_per_kvb_ceil())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counts the sigops for this Script using accurate counting.
|
/// Counts the sigops for this Script using accurate counting.
|
||||||
|
@ -407,10 +407,10 @@ mod sealed {
|
||||||
|
|
||||||
crate::internal_macros::define_extension_trait! {
|
crate::internal_macros::define_extension_trait! {
|
||||||
pub(crate) trait ScriptExtPriv impl for Script {
|
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
|
// 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.
|
// 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() {
|
.checked_mul(if self.is_op_return() {
|
||||||
0
|
0
|
||||||
} else if self.is_witness_program() {
|
} else if self.is_witness_program() {
|
||||||
|
|
|
@ -18,7 +18,6 @@ use hashes::sha256d;
|
||||||
use internals::{compact_size, write_err, ToU64};
|
use internals::{compact_size, write_err, ToU64};
|
||||||
use io::{BufRead, Write};
|
use io::{BufRead, Write};
|
||||||
use primitives::Sequence;
|
use primitives::Sequence;
|
||||||
use units::NumOpResult;
|
|
||||||
|
|
||||||
use super::Weight;
|
use super::Weight;
|
||||||
use crate::consensus::{self, encode, Decodable, Encodable};
|
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.
|
/// * `fee_rate` - the fee rate of the transaction being created.
|
||||||
/// * `input_weight_prediction` - the predicted input weight.
|
/// * `input_weight_prediction` - the predicted input weight.
|
||||||
///
|
/// * `value` - The value of the output we are spending.
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// This will return [`NumOpResult::Error`] if the fee calculation (fee_rate * weight) overflows.
|
|
||||||
/// Otherwise, [`NumOpResult::Valid`] will wrap the successful calculation.
|
|
||||||
pub fn effective_value(
|
pub fn effective_value(
|
||||||
fee_rate: FeeRate,
|
fee_rate: FeeRate,
|
||||||
input_weight_prediction: InputWeightPrediction,
|
input_weight_prediction: InputWeightPrediction,
|
||||||
value: Amount,
|
value: Amount,
|
||||||
) -> NumOpResult<SignedAmount> {
|
) -> SignedAmount {
|
||||||
let weight = input_weight_prediction.total_weight();
|
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.
|
/// Predicts the weight of a to-be-constructed transaction.
|
||||||
|
@ -1691,11 +1689,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn effective_value_happy_path() {
|
fn effective_value_happy_path() {
|
||||||
let value = "1 cBTC".parse::<Amount>().unwrap();
|
let value = "1 cBTC".parse::<Amount>().unwrap();
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(10);
|
let fee_rate = FeeRate::from_sat_per_kwu(10).unwrap();
|
||||||
let effective_value =
|
let effective_value = effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value);
|
||||||
effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value).unwrap();
|
|
||||||
|
|
||||||
// 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_fee = "3 sats".parse::<SignedAmount>().unwrap();
|
||||||
let expected_effective_value = (value.to_signed() - expected_fee).unwrap();
|
let expected_effective_value = (value.to_signed() - expected_fee).unwrap();
|
||||||
assert_eq!(effective_value, expected_effective_value);
|
assert_eq!(effective_value, expected_effective_value);
|
||||||
|
@ -1705,7 +1702,8 @@ mod tests {
|
||||||
fn effective_value_fee_rate_does_not_overflow() {
|
fn effective_value_fee_rate_does_not_overflow() {
|
||||||
let eff_value =
|
let eff_value =
|
||||||
effective_value(FeeRate::MAX, InputWeightPrediction::P2WPKH_MAX, Amount::ZERO);
|
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]
|
#[test]
|
||||||
|
|
|
@ -1178,8 +1178,11 @@ impl fmt::Display for ExtractTxError {
|
||||||
use ExtractTxError::*;
|
use ExtractTxError::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
AbsurdFeeRate { fee_rate, .. } =>
|
AbsurdFeeRate { fee_rate, .. } => write!(
|
||||||
write!(f, "an absurdly high fee rate of {} sat/kwu", fee_rate.to_sat_per_kwu()),
|
f,
|
||||||
|
"an absurdly high fee rate of {} sat/kwu",
|
||||||
|
fee_rate.to_sat_per_kwu_floor()
|
||||||
|
),
|
||||||
MissingInputValue { .. } => write!(
|
MissingInputValue { .. } => write!(
|
||||||
f,
|
f,
|
||||||
"one of the inputs lacked value information (witness_utxo or non_witness_utxo)"
|
"one of the inputs lacked value information (witness_utxo or non_witness_utxo)"
|
||||||
|
@ -1353,9 +1356,15 @@ mod tests {
|
||||||
use crate::Sequence;
|
use crate::Sequence;
|
||||||
|
|
||||||
/// Fee rate in sat/kwu for a high-fee PSBT with an input=5_000_000_000_000, output=1000
|
/// 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);
|
const ABSURD_FEE_RATE: FeeRate = match FeeRate::from_sat_per_kwu(15_060_240_960_843) {
|
||||||
/// Fee rate which is just below absurd threshold (1 sat/kwu less)
|
Some(fee_rate) => fee_rate,
|
||||||
const JUST_BELOW_ABSURD_FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(15_060_240_960_842);
|
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]
|
#[track_caller]
|
||||||
pub fn hex_psbt(s: &str) -> Result<Psbt, crate::psbt::error::Error> {
|
pub fn hex_psbt(s: &str) -> Result<Psbt, crate::psbt::error::Error> {
|
||||||
|
@ -1472,7 +1481,7 @@ mod tests {
|
||||||
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
|
ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate,
|
||||||
_ => panic!(""),
|
_ => 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
|
// 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.count_sigops_legacy();
|
||||||
let _ = script.minimal_non_dust();
|
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 _ = script.minimal_non_dust_custom(fee_rate);
|
||||||
|
|
||||||
let mut b = script::Builder::new();
|
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 weight = Weight::from_kwu(1).unwrap();
|
||||||
let fee_rate = sat(1).checked_div_by_weight_ceil(weight).unwrap();
|
let fee_rate = sat(1).checked_div_by_weight_ceil(weight).unwrap();
|
||||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
// 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 weight = Weight::from_wu(381);
|
||||||
let fee_rate = sat(329).checked_div_by_weight_ceil(weight).unwrap();
|
let fee_rate = sat(329).checked_div_by_weight_ceil(weight).unwrap();
|
||||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||||
// round up to 864
|
// 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);
|
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_ceil(Weight::ZERO);
|
||||||
assert!(fee_rate.is_none());
|
assert!(fee_rate.is_none());
|
||||||
|
@ -288,13 +288,13 @@ fn amount_checked_div_by_weight_floor() {
|
||||||
let weight = Weight::from_kwu(1).unwrap();
|
let weight = Weight::from_kwu(1).unwrap();
|
||||||
let fee_rate = sat(1).checked_div_by_weight_floor(weight).unwrap();
|
let fee_rate = sat(1).checked_div_by_weight_floor(weight).unwrap();
|
||||||
// 1 sats / 1,000 wu = 1 sats/kwu
|
// 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 weight = Weight::from_wu(381);
|
||||||
let fee_rate = sat(329).checked_div_by_weight_floor(weight).unwrap();
|
let fee_rate = sat(329).checked_div_by_weight_floor(weight).unwrap();
|
||||||
// 329 sats / 381 wu = 863.5 sats/kwu
|
// 329 sats / 381 wu = 863.5 sats/kwu
|
||||||
// round down to 863
|
// 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);
|
let fee_rate = Amount::ONE_SAT.checked_div_by_weight_floor(Weight::ZERO);
|
||||||
assert!(fee_rate.is_none());
|
assert!(fee_rate.is_none());
|
||||||
|
@ -304,7 +304,7 @@ fn amount_checked_div_by_weight_floor() {
|
||||||
#[test]
|
#[test]
|
||||||
fn amount_checked_div_by_fee_rate() {
|
fn amount_checked_div_by_fee_rate() {
|
||||||
let amount = sat(1000);
|
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
|
// Test floor division
|
||||||
let weight = amount.checked_div_by_fee_rate_floor(fee_rate).unwrap();
|
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
|
// Test truncation behavior
|
||||||
let amount = sat(1000);
|
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 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();
|
let ceil_weight = amount.checked_div_by_fee_rate_ceil(fee_rate).unwrap();
|
||||||
assert_eq!(floor_weight, Weight::from_wu(333_333));
|
assert_eq!(floor_weight, Weight::from_wu(333_333));
|
||||||
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
||||||
|
|
||||||
// Test division by zero
|
// 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_floor(zero_fee_rate).is_none());
|
||||||
assert!(amount.checked_div_by_fee_rate_ceil(zero_fee_rate).is_none());
|
assert!(amount.checked_div_by_fee_rate_ceil(zero_fee_rate).is_none());
|
||||||
|
|
||||||
// Test with maximum amount
|
// Test with maximum amount
|
||||||
let max_amount = Amount::MAX;
|
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();
|
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
|
// 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));
|
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.
|
/// Returns [`None`] if overflow occurred.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option<FeeRate> {
|
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) {
|
match self.to_sat().checked_mul(1_000) {
|
||||||
Some(res) => match res.checked_div(weight.to_wu()) {
|
Some(sats) => {
|
||||||
Some(fee_rate) => Some(FeeRate::from_sat_per_kwu(fee_rate)),
|
let fee_rate = sats / wu;
|
||||||
None => None,
|
FeeRate::from_sat_per_kwu(fee_rate)
|
||||||
},
|
}
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,20 +56,22 @@ impl Amount {
|
||||||
/// let amount = Amount::from_sat(10)?;
|
/// let amount = Amount::from_sat(10)?;
|
||||||
/// let weight = Weight::from_wu(300);
|
/// let weight = Weight::from_wu(300);
|
||||||
/// let fee_rate = amount.checked_div_by_weight_ceil(weight);
|
/// 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>(())
|
/// # Ok::<_, amount::OutOfRangeError>(())
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
|
pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
|
||||||
let wu = weight.to_wu();
|
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(sats) = self.to_sat().checked_mul(1_000) {
|
||||||
if let Some(wu_minus_one) = wu.checked_sub(1) {
|
// No need to used checked arithmetic because wu is non-zero.
|
||||||
if let Some(sats_plus_wu_minus_one) = sats.checked_add(wu_minus_one) {
|
if let Some(bump) = sats.checked_add(wu - 1) {
|
||||||
if let Some(fee_rate) = sats_plus_wu_minus_one.checked_div(wu) {
|
let fee_rate = bump / wu;
|
||||||
return Some(FeeRate::from_sat_per_kwu(fee_rate));
|
return FeeRate::from_sat_per_kwu(fee_rate);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
@ -79,14 +86,13 @@ impl Amount {
|
||||||
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
|
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> {
|
pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> {
|
||||||
match self.to_sat().checked_mul(1000) {
|
if let Some(msats) = self.to_sat().checked_mul(1000) {
|
||||||
Some(amount_msats) => match amount_msats.checked_div(fee_rate.to_sat_per_kwu()) {
|
if let Some(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) {
|
||||||
Some(wu) => Some(Weight::from_wu(wu)),
|
return Some(Weight::from_wu(wu));
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Checked fee rate ceiling division.
|
/// Checked fee rate ceiling division.
|
||||||
///
|
///
|
||||||
|
@ -96,30 +102,39 @@ impl Amount {
|
||||||
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
|
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> {
|
pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> {
|
||||||
let rate = fee_rate.to_sat_per_kwu();
|
// Use ceil because result is used as the divisor.
|
||||||
match self.to_sat().checked_mul(1000) {
|
let rate = fee_rate.to_sat_per_kwu_ceil();
|
||||||
Some(amount_msats) => match rate.checked_sub(1) {
|
if rate == 0 {
|
||||||
Some(rate_minus_one) => match amount_msats.checked_add(rate_minus_one) {
|
return None;
|
||||||
Some(rounded_msats) => match rounded_msats.checked_div(rate) {
|
|
||||||
Some(wu) => Some(Weight::from_wu(wu)),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
None => 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 {
|
impl FeeRate {
|
||||||
/// Calculates the fee by multiplying this fee rate by weight, in weight units, returning
|
/// Calculates the fee by multiplying this fee rate by weight.
|
||||||
/// [`NumOpResult::Error`] if an overflow occurred.
|
|
||||||
///
|
///
|
||||||
/// This is equivalent to `Self::checked_mul_by_weight()`.
|
/// Computes the absolute fee amount for a given [`Weight`] at this fee rate. When the resulting
|
||||||
pub const fn to_fee(self, weight: Weight) -> NumOpResult<Amount> {
|
/// fee is a non-integer amount, the amount is rounded up, ensuring that the transaction fee is
|
||||||
self.checked_mul_by_weight(weight)
|
/// 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`]
|
/// 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()`.
|
/// This is equivalent to `Self::checked_mul_by_weight()`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[deprecated(since = "TBD", note = "use `to_fee()` instead")]
|
#[deprecated(since = "TBD", note = "use `to_fee()` instead")]
|
||||||
pub fn fee_wu(self, weight: Weight) -> Option<Amount> {
|
pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.checked_mul_by_weight(weight) }
|
||||||
self.checked_mul_by_weight(weight).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the fee by multiplying this fee rate by weight, in virtual bytes, returning [`None`]
|
/// Calculates the fee by multiplying this fee rate by weight, in virtual bytes, returning [`None`]
|
||||||
/// if an overflow occurred.
|
/// if an overflow occurred.
|
||||||
|
@ -139,9 +152,7 @@ impl FeeRate {
|
||||||
/// `Self::fee_wu(weight)`.
|
/// `Self::fee_wu(weight)`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[deprecated(since = "TBD", note = "use Weight::from_vb and then `to_fee()` instead")]
|
#[deprecated(since = "TBD", note = "use Weight::from_vb and then `to_fee()` instead")]
|
||||||
pub fn fee_vb(self, vb: u64) -> Option<Amount> {
|
pub fn fee_vb(self, vb: u64) -> Option<Amount> { Weight::from_vb(vb).map(|w| self.to_fee(w)) }
|
||||||
Weight::from_vb(vb).and_then(|w| self.to_fee(w).ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checked weight multiplication.
|
/// 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
|
/// 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.
|
/// enough instead of falling short if rounded down.
|
||||||
///
|
///
|
||||||
/// Returns [`NumOpResult::Error`] if overflow occurred.
|
/// Returns [`None`] if overflow occurred.
|
||||||
pub const fn checked_mul_by_weight(self, weight: Weight) -> NumOpResult<Amount> {
|
pub const fn checked_mul_by_weight(self, weight: Weight) -> Option<Amount> {
|
||||||
if let Some(fee) = self.to_sat_per_kwu().checked_mul(weight.to_wu()) {
|
let wu = weight.to_wu();
|
||||||
if let Some(round_up) = fee.checked_add(999) {
|
if let Some(fee_kwu) = self.to_sat_per_kwu_floor().checked_mul(wu) {
|
||||||
if let Ok(ret) = Amount::from_sat(round_up / 1_000) {
|
// Bump by 999 to do ceil division using kwu.
|
||||||
return NumOpResult::Valid(ret);
|
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 {
|
impl ops::Mul<FeeRate> for Weight {
|
||||||
type Output = NumOpResult<Amount>;
|
type Output = NumOpResult<Amount>;
|
||||||
fn mul(self, rhs: FeeRate) -> Self::Output {
|
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> {
|
impl ops::Mul<FeeRate> for NumOpResult<Weight> {
|
||||||
|
@ -203,7 +220,10 @@ crate::internal_macros::impl_op_for_references! {
|
||||||
impl ops::Mul<Weight> for FeeRate {
|
impl ops::Mul<Weight> for FeeRate {
|
||||||
type Output = NumOpResult<Amount>;
|
type Output = NumOpResult<Amount>;
|
||||||
fn mul(self, rhs: Weight) -> Self::Output {
|
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> {
|
impl ops::Mul<Weight> for NumOpResult<FeeRate> {
|
||||||
|
@ -328,7 +348,7 @@ impl Weight {
|
||||||
/// enough instead of falling short if rounded down.
|
/// enough instead of falling short if rounded down.
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if overflow occurred.
|
/// 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)
|
fee_rate.checked_mul_by_weight(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,17 +360,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_div_by_weight() {
|
fn fee_rate_div_by_weight() {
|
||||||
let fee_rate = (Amount::from_sat_u32(329) / Weight::from_wu(381)).unwrap();
|
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]
|
#[test]
|
||||||
fn fee_wu() {
|
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 fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
|
||||||
let weight = Weight::from_vb(3).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]
|
#[test]
|
||||||
|
@ -362,8 +379,8 @@ mod tests {
|
||||||
.expect("expected Amount");
|
.expect("expected Amount");
|
||||||
assert_eq!(Amount::from_sat_u32(100), fee);
|
assert_eq!(Amount::from_sat_u32(100), fee);
|
||||||
|
|
||||||
let fee = FeeRate::from_sat_per_kwu(10).checked_mul_by_weight(Weight::MAX);
|
let fee = FeeRate::from_sat_per_kwu(10).unwrap().checked_mul_by_weight(Weight::MAX);
|
||||||
assert!(fee.is_error());
|
assert!(fee.is_none());
|
||||||
|
|
||||||
let weight = Weight::from_vb(3).unwrap();
|
let weight = Weight::from_vb(3).unwrap();
|
||||||
let fee_rate = FeeRate::from_sat_per_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);
|
assert_eq!(Amount::from_sat_u32(9), fee);
|
||||||
|
|
||||||
let weight = Weight::from_wu(381);
|
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();
|
let fee = weight.checked_mul_by_fee_rate(fee_rate).unwrap();
|
||||||
// 381 * 0.864 yields 329.18.
|
// 381 * 0.864 yields 329.18.
|
||||||
// The result is then rounded up to 330.
|
// The result is then rounded up to 330.
|
||||||
|
@ -398,7 +415,7 @@ mod tests {
|
||||||
fn amount_div_by_fee_rate() {
|
fn amount_div_by_fee_rate() {
|
||||||
// Test exact division
|
// Test exact division
|
||||||
let amount = Amount::from_sat_u32(1000);
|
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();
|
let weight = (amount / fee_rate).unwrap();
|
||||||
assert_eq!(weight, Weight::from_wu(500_000));
|
assert_eq!(weight, Weight::from_wu(500_000));
|
||||||
|
|
||||||
|
@ -412,7 +429,7 @@ mod tests {
|
||||||
|
|
||||||
// Test truncation behavior
|
// Test truncation behavior
|
||||||
let amount = Amount::from_sat_u32(1000);
|
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();
|
let weight = (amount / fee_rate).unwrap();
|
||||||
// 1000 * 1000 = 1,000,000 msats
|
// 1000 * 1000 = 1,000,000 msats
|
||||||
// 1,000,000 / 3 = 333,333.33... wu
|
// 1,000,000 / 3 = 333,333.33... wu
|
||||||
|
@ -424,7 +441,7 @@ mod tests {
|
||||||
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
assert_eq!(ceil_weight, Weight::from_wu(333_334));
|
||||||
|
|
||||||
// Test that division by zero returns None
|
// 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_floor(zero_rate).is_none());
|
||||||
assert!(amount.checked_div_by_fee_rate_ceil(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 {
|
mod encapsulate {
|
||||||
/// Fee rate.
|
/// Fee rate.
|
||||||
///
|
///
|
||||||
/// This is an integer newtype representing fee rate in `sat/kwu`. It provides protection
|
/// This is an integer newtype representing fee rate. It provides protection
|
||||||
/// against mixing up the types as well as basic formatting features.
|
/// against mixing up the types, conversion functions, and basic formatting.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct FeeRate(u64);
|
pub struct FeeRate(u64);
|
||||||
|
|
||||||
impl FeeRate {
|
impl FeeRate {
|
||||||
/// Constructs a new [`FeeRate`] from satoshis per 1000 weight units.
|
/// Constructs a new [`FeeRate`] from satoshis per 1,000,000 virtual bytes.
|
||||||
pub const fn from_sat_per_kwu(sat_kwu: u64) -> Self { FeeRate(sat_kwu) }
|
pub(crate) const fn from_sat_per_mvb(sat_mvb: u64) -> Self { Self(sat_mvb) }
|
||||||
|
|
||||||
/// Returns raw fee rate.
|
/// Converts to sat/MvB.
|
||||||
///
|
pub(crate) const fn to_sat_per_mvb(self) -> u64 { self.0 }
|
||||||
/// Can be used instead of `into()` to avoid inference issues.
|
|
||||||
pub const fn to_sat_per_kwu(self) -> u64 { self.0 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use encapsulate::FeeRate;
|
pub use encapsulate::FeeRate;
|
||||||
|
|
||||||
impl FeeRate {
|
impl FeeRate {
|
||||||
/// 0 sat/kwu.
|
/// The zero fee rate.
|
||||||
///
|
///
|
||||||
/// Equivalent to [`MIN`](Self::MIN), may better express intent in some contexts.
|
/// 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.
|
/// Equivalent to [`ZERO`](Self::ZERO), may better express intent in some contexts.
|
||||||
pub const MIN: FeeRate = FeeRate::ZERO;
|
pub const MIN: FeeRate = FeeRate::ZERO;
|
||||||
|
|
||||||
/// Maximum possible value.
|
/// The maximum possible value.
|
||||||
pub const MAX: FeeRate = FeeRate::from_sat_per_kwu(u64::MAX);
|
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.
|
/// 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);
|
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);
|
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.
|
/// Constructs a new [`FeeRate`] from satoshis per virtual bytes.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns [`None`] on arithmetic overflow.
|
/// Returns [`None`] on arithmetic overflow.
|
||||||
pub fn from_sat_per_vb(sat_vb: u64) -> Option<Self> {
|
pub const fn from_sat_per_vb(sat_vb: u64) -> Option<Self> {
|
||||||
// 1 vb == 4 wu
|
// No `map()` in const context.
|
||||||
// 1 sat/vb == 1/4 sat/wu
|
match sat_vb.checked_mul(1_000_000) {
|
||||||
// sat/vb * 1000 / 4 == sat/kwu
|
Some(fee_rate) => Some(FeeRate::from_sat_per_mvb(fee_rate)),
|
||||||
Some(FeeRate::from_sat_per_kwu(sat_vb.checked_mul(1000 / 4)?))
|
None => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new [`FeeRate`] from satoshis per virtual bytes.
|
/// Constructs a new [`FeeRate`] from satoshis per virtual bytes.
|
||||||
pub const fn from_sat_per_vb_u32(sat_vb: u32) -> Self {
|
pub const fn from_sat_per_vb_u32(sat_vb: u32) -> Self {
|
||||||
let sat_vb = sat_vb as u64; // No `Into` in const context.
|
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).
|
/// 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.
|
/// 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.
|
/// Converts to sat/vB rounding up.
|
||||||
pub const fn to_sat_per_vb_ceil(self) -> u64 {
|
pub const fn to_sat_per_vb_ceil(self) -> u64 { (self.to_sat_per_mvb() + 999_999) / 1_000_000 }
|
||||||
(self.to_sat_per_kwu() + (1000 / 4 - 1)) / (1000 / 4)
|
|
||||||
}
|
/// 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.
|
/// Checked multiplication.
|
||||||
///
|
///
|
||||||
|
@ -89,8 +113,8 @@ impl FeeRate {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_mul(self, rhs: u64) -> Option<Self> {
|
pub const fn checked_mul(self, rhs: u64) -> Option<Self> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat_per_kwu().checked_mul(rhs) {
|
match self.to_sat_per_mvb().checked_mul(rhs) {
|
||||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,8 +125,8 @@ impl FeeRate {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_div(self, rhs: u64) -> Option<Self> {
|
pub const fn checked_div(self, rhs: u64) -> Option<Self> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat_per_kwu().checked_div(rhs) {
|
match self.to_sat_per_mvb().checked_div(rhs) {
|
||||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,8 +137,8 @@ impl FeeRate {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_add(self, rhs: FeeRate) -> Option<Self> {
|
pub const fn checked_add(self, rhs: FeeRate) -> Option<Self> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat_per_kwu().checked_add(rhs.to_sat_per_kwu()) {
|
match self.to_sat_per_mvb().checked_add(rhs.to_sat_per_mvb()) {
|
||||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,8 +149,8 @@ impl FeeRate {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn checked_sub(self, rhs: FeeRate) -> Option<Self> {
|
pub const fn checked_sub(self, rhs: FeeRate) -> Option<Self> {
|
||||||
// No `map()` in const context.
|
// No `map()` in const context.
|
||||||
match self.to_sat_per_kwu().checked_sub(rhs.to_sat_per_kwu()) {
|
match self.to_sat_per_mvb().checked_sub(rhs.to_sat_per_mvb()) {
|
||||||
Some(res) => Some(Self::from_sat_per_kwu(res)),
|
Some(res) => Some(Self::from_sat_per_mvb(res)),
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,19 +160,19 @@ crate::internal_macros::impl_op_for_references! {
|
||||||
impl ops::Add<FeeRate> for FeeRate {
|
impl ops::Add<FeeRate> for FeeRate {
|
||||||
type Output = 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 {
|
impl ops::Sub<FeeRate> for FeeRate {
|
||||||
type Output = 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 {
|
impl ops::Div<NonZeroU64> for FeeRate {
|
||||||
type Output = 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);
|
crate::internal_macros::impl_add_assign!(FeeRate);
|
||||||
|
@ -159,7 +183,7 @@ impl core::iter::Sum for FeeRate {
|
||||||
where
|
where
|
||||||
I: Iterator<Item = Self>,
|
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
|
where
|
||||||
I: Iterator<Item = &'a FeeRate>,
|
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),
|
1 => Ok(FeeRate::BROADCAST_MIN),
|
||||||
2 => Ok(FeeRate::DUST),
|
2 => Ok(FeeRate::DUST),
|
||||||
3 => Ok(FeeRate::MAX),
|
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]
|
#[test]
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn feerate_div_nonzero() {
|
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();
|
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).unwrap());
|
||||||
assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100));
|
assert_eq!(&rate / &divisor, FeeRate::from_sat_per_kwu(100).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn addition() {
|
fn addition() {
|
||||||
let one = FeeRate::from_sat_per_kwu(1);
|
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||||
let two = FeeRate::from_sat_per_kwu(2);
|
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
||||||
let three = FeeRate::from_sat_per_kwu(3);
|
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||||
|
|
||||||
assert!(one + two == three);
|
assert!(one + two == three);
|
||||||
assert!(&one + two == three);
|
assert!(&one + two == three);
|
||||||
|
@ -217,9 +241,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::op_ref)]
|
#[allow(clippy::op_ref)]
|
||||||
fn subtract() {
|
fn subtract() {
|
||||||
let three = FeeRate::from_sat_per_kwu(3);
|
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||||
let seven = FeeRate::from_sat_per_kwu(7);
|
let seven = FeeRate::from_sat_per_kwu(7).unwrap();
|
||||||
let ten = FeeRate::from_sat_per_kwu(10);
|
let ten = FeeRate::from_sat_per_kwu(10).unwrap();
|
||||||
|
|
||||||
assert_eq!(ten - seven, three);
|
assert_eq!(ten - seven, three);
|
||||||
assert_eq!(&ten - seven, three);
|
assert_eq!(&ten - seven, three);
|
||||||
|
@ -229,43 +253,44 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_assign() {
|
fn add_assign() {
|
||||||
let mut f = FeeRate::from_sat_per_kwu(1);
|
let mut f = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||||
f += FeeRate::from_sat_per_kwu(2);
|
f += FeeRate::from_sat_per_kwu(2).unwrap();
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3));
|
assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap());
|
||||||
|
|
||||||
let mut f = FeeRate::from_sat_per_kwu(1);
|
let mut f = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||||
f += &FeeRate::from_sat_per_kwu(2);
|
f += &FeeRate::from_sat_per_kwu(2).unwrap();
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(3));
|
assert_eq!(f, FeeRate::from_sat_per_kwu(3).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sub_assign() {
|
fn sub_assign() {
|
||||||
let mut f = FeeRate::from_sat_per_kwu(3);
|
let mut f = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||||
f -= FeeRate::from_sat_per_kwu(2);
|
f -= FeeRate::from_sat_per_kwu(2).unwrap();
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1));
|
assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap());
|
||||||
|
|
||||||
let mut f = FeeRate::from_sat_per_kwu(3);
|
let mut f = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||||
f -= &FeeRate::from_sat_per_kwu(2);
|
f -= &FeeRate::from_sat_per_kwu(2).unwrap();
|
||||||
assert_eq!(f, FeeRate::from_sat_per_kwu(1));
|
assert_eq!(f, FeeRate::from_sat_per_kwu(1).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_add() {
|
fn checked_add() {
|
||||||
let one = FeeRate::from_sat_per_kwu(1);
|
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||||
let two = FeeRate::from_sat_per_kwu(2);
|
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
||||||
let three = FeeRate::from_sat_per_kwu(3);
|
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||||
|
|
||||||
assert_eq!(one.checked_add(two).unwrap(), three);
|
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());
|
assert!(fee_rate.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_sub() {
|
fn checked_sub() {
|
||||||
let one = FeeRate::from_sat_per_kwu(1);
|
let one = FeeRate::from_sat_per_kwu(1).unwrap();
|
||||||
let two = FeeRate::from_sat_per_kwu(2);
|
let two = FeeRate::from_sat_per_kwu(2).unwrap();
|
||||||
let three = FeeRate::from_sat_per_kwu(3);
|
let three = FeeRate::from_sat_per_kwu(3).unwrap();
|
||||||
assert_eq!(three.checked_sub(two).unwrap(), one);
|
assert_eq!(three.checked_sub(two).unwrap(), one);
|
||||||
|
|
||||||
let fee_rate = FeeRate::ZERO.checked_sub(one);
|
let fee_rate = FeeRate::ZERO.checked_sub(one);
|
||||||
|
@ -274,23 +299,23 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_const() {
|
fn fee_rate_const() {
|
||||||
assert_eq!(FeeRate::ZERO.to_sat_per_kwu(), 0);
|
assert_eq!(FeeRate::ZERO.to_sat_per_kwu_floor(), 0);
|
||||||
assert_eq!(FeeRate::MIN.to_sat_per_kwu(), u64::MIN);
|
assert_eq!(FeeRate::MIN.to_sat_per_kwu_floor(), u64::MIN);
|
||||||
assert_eq!(FeeRate::MAX.to_sat_per_kwu(), u64::MAX);
|
assert_eq!(FeeRate::MAX.to_sat_per_kwu_floor(), u64::MAX / 4_000);
|
||||||
assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu(), 250);
|
assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu_floor(), 250);
|
||||||
assert_eq!(FeeRate::DUST.to_sat_per_kwu(), 750);
|
assert_eq!(FeeRate::DUST.to_sat_per_kwu_floor(), 750);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fee_rate_from_sat_per_vb() {
|
fn fee_rate_from_sat_per_vb() {
|
||||||
let fee_rate = FeeRate::from_sat_per_vb(10).expect("expected feerate in sat/kwu");
|
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]
|
#[test]
|
||||||
fn fee_rate_from_sat_per_kvb() {
|
fn fee_rate_from_sat_per_kvb() {
|
||||||
let fee_rate = FeeRate::from_sat_per_kvb(11);
|
let fee_rate = FeeRate::from_sat_per_kvb(11).unwrap();
|
||||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(2));
|
assert_eq!(fee_rate, FeeRate::from_sat_per_mvb(11_000));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -302,7 +327,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn from_sat_per_vb_u32() {
|
fn from_sat_per_vb_u32() {
|
||||||
let fee_rate = FeeRate::from_sat_per_vb_u32(10);
|
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]
|
#[test]
|
||||||
|
@ -311,29 +336,40 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn raw_feerate() {
|
fn raw_feerate() {
|
||||||
let fee_rate = FeeRate::from_sat_per_kwu(749);
|
let fee_rate = FeeRate::from_sat_per_kwu(749).unwrap();
|
||||||
assert_eq!(fee_rate.to_sat_per_kwu(), 749);
|
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_floor(), 2);
|
||||||
assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3);
|
assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_mul() {
|
fn checked_mul() {
|
||||||
let fee_rate =
|
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
||||||
FeeRate::from_sat_per_kwu(10).checked_mul(10).expect("expected feerate in sat/kwu");
|
.unwrap()
|
||||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(100));
|
.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());
|
assert!(fee_rate.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checked_div() {
|
fn checked_div() {
|
||||||
let fee_rate =
|
let fee_rate = FeeRate::from_sat_per_kwu(10)
|
||||||
FeeRate::from_sat_per_kwu(10).checked_div(10).expect("expected feerate in sat/kwu");
|
.unwrap()
|
||||||
assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(1));
|
.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());
|
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)]
|
//! #[derive(Serialize, Deserialize)]
|
||||||
//! pub struct Foo {
|
//! 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,
|
//! pub fee_rate: FeeRate,
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
use core::convert::Infallible;
|
use core::convert::Infallible;
|
||||||
use core::fmt;
|
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.
|
//! Serialize and deserialize [`FeeRate`] denominated in satoshis per 1000 weight units.
|
||||||
//!
|
//!
|
||||||
//! Use with `#[serde(with = "fee_rate::serde::as_sat_per_kwu")]`.
|
//! Use with `#[serde(with = "fee_rate::serde::as_sat_per_kwu")]`.
|
||||||
|
@ -36,11 +36,12 @@ pub mod as_sat_per_kwu {
|
||||||
use crate::FeeRate;
|
use crate::FeeRate;
|
||||||
|
|
||||||
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
|
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> {
|
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 {
|
pub mod opt {
|
||||||
|
@ -57,7 +58,7 @@ pub mod as_sat_per_kwu {
|
||||||
#[allow(clippy::ref_option)] // API forced by serde.
|
#[allow(clippy::ref_option)] // API forced by serde.
|
||||||
pub fn serialize<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> {
|
pub fn serialize<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> {
|
||||||
match *f {
|
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(),
|
None => s.serialize_none(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,23 +90,6 @@ pub enum NumOpResult<T> {
|
||||||
impl<T> NumOpResult<T> {
|
impl<T> NumOpResult<T> {
|
||||||
/// Maps a `NumOpResult<T>` to `NumOpResult<U>` by applying a function to a
|
/// Maps a `NumOpResult<T>` to `NumOpResult<U>` by applying a function to a
|
||||||
/// contained [`NumOpResult::Valid`] value, leaving a [`NumOpResult::Error`] value untouched.
|
/// 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]
|
#[inline]
|
||||||
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> NumOpResult<U> {
|
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> NumOpResult<U> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -37,13 +37,13 @@ struct Serde {
|
||||||
vb_floor: FeeRate,
|
vb_floor: FeeRate,
|
||||||
#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil")]
|
#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil")]
|
||||||
vb_ceil: FeeRate,
|
vb_ceil: FeeRate,
|
||||||
#[serde(with = "fee_rate::serde::as_sat_per_kwu")]
|
#[serde(with = "fee_rate::serde::as_sat_per_kwu_floor")]
|
||||||
kwu: FeeRate,
|
kwu: FeeRate,
|
||||||
#[serde(with = "fee_rate::serde::as_sat_per_vb_floor::opt")]
|
#[serde(with = "fee_rate::serde::as_sat_per_vb_floor::opt")]
|
||||||
opt_vb_floor: Option<FeeRate>,
|
opt_vb_floor: Option<FeeRate>,
|
||||||
#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil::opt")]
|
#[serde(with = "fee_rate::serde::as_sat_per_vb_ceil::opt")]
|
||||||
opt_vb_ceil: Option<FeeRate>,
|
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>,
|
opt_kwu: Option<FeeRate>,
|
||||||
|
|
||||||
a: BlockHeight,
|
a: BlockHeight,
|
||||||
|
|
Loading…
Reference in New Issue