diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs index fcb5a6f7b..28f2ac3a4 100644 --- a/crates/bdk/src/error.rs +++ b/crates/bdk/src/error.rs @@ -138,7 +138,7 @@ impl fmt::Display for Error { Self::FeeRateTooLow { required } => write!( f, "Fee rate too low: required {} sat/vbyte", - required.as_sat_per_vb() + required.to_sat_per_vb_ceil() ), Self::FeeTooLow { required } => write!(f, "Fee to low: required {} sat", required), Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"), diff --git a/crates/bdk/src/psbt/mod.rs b/crates/bdk/src/psbt/mod.rs index bc6ce8589..f36f70cb4 100644 --- a/crates/bdk/src/psbt/mod.rs +++ b/crates/bdk/src/psbt/mod.rs @@ -71,9 +71,11 @@ impl PsbtUtils for Psbt { fn fee_rate(&self) -> Option { let fee_amount = self.fee_amount(); - fee_amount.map(|fee| { + fee_amount.and_then(|fee| { let weight = self.clone().extract_tx().weight(); - FeeRate::from_wu(fee, weight) + Some(FeeRate::from_sat_per_kwu( + fee.checked_mul(1000)? / weight.to_wu(), + )) }) } } diff --git a/crates/bdk/src/types.rs b/crates/bdk/src/types.rs index 2a88cc374..37547274d 100644 --- a/crates/bdk/src/types.rs +++ b/crates/bdk/src/types.rs @@ -11,11 +11,10 @@ use alloc::boxed::Box; use core::convert::AsRef; -use core::ops::Sub; use bdk_chain::ConfirmationTime; use bitcoin::blockdata::transaction::{OutPoint, TxOut}; -use bitcoin::{psbt, Weight}; +use bitcoin::psbt; use serde::{Deserialize, Serialize}; @@ -47,102 +46,7 @@ impl AsRef<[u8]> for KeychainKind { } } -/// Fee rate -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] -// Internally stored as satoshi/vbyte -pub struct FeeRate(f32); - -impl FeeRate { - /// Create a new instance checking the value provided - /// - /// ## Panics - /// - /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative. - fn new_checked(value: f32) -> Self { - assert!(value.is_normal() || value == 0.0); - assert!(value.is_sign_positive()); - - FeeRate(value) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in sats/kwu - pub fn from_sat_per_kwu(sat_per_kwu: f32) -> Self { - FeeRate::new_checked(sat_per_kwu / 250.0_f32) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in sats/kvb - pub fn from_sat_per_kvb(sat_per_kvb: f32) -> Self { - FeeRate::new_checked(sat_per_kvb / 1000.0_f32) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in btc/kvbytes - /// - /// ## Panics - /// - /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative. - pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self { - FeeRate::new_checked(btc_per_kvb * 1e5) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in satoshi/vbyte - /// - /// ## Panics - /// - /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative. - pub fn from_sat_per_vb(sat_per_vb: f32) -> Self { - FeeRate::new_checked(sat_per_vb) - } - - /// Create a new [`FeeRate`] with the default min relay fee value - pub const fn default_min_relay_fee() -> Self { - FeeRate(1.0) - } - - /// Calculate fee rate from `fee` and weight units (`wu`). - pub fn from_wu(fee: u64, wu: Weight) -> FeeRate { - Self::from_vb(fee, wu.to_vbytes_ceil() as usize) - } - - /// Calculate fee rate from `fee` and `vbytes`. - pub fn from_vb(fee: u64, vbytes: usize) -> FeeRate { - let rate = fee as f32 / vbytes as f32; - Self::from_sat_per_vb(rate) - } - - /// Return the value as satoshi/vbyte - pub fn as_sat_per_vb(&self) -> f32 { - self.0 - } - - /// Return the value as satoshi/kwu - pub fn sat_per_kwu(&self) -> f32 { - self.0 * 250.0_f32 - } - - /// Calculate absolute fee in Satoshis using size in weight units. - pub fn fee_wu(&self, wu: Weight) -> u64 { - self.fee_vb(wu.to_vbytes_ceil() as usize) - } - - /// Calculate absolute fee in Satoshis using size in virtual bytes. - pub fn fee_vb(&self, vbytes: usize) -> u64 { - (self.as_sat_per_vb() * vbytes as f32).ceil() as u64 - } -} - -impl Default for FeeRate { - fn default() -> Self { - FeeRate::default_min_relay_fee() - } -} - -impl Sub for FeeRate { - type Output = Self; - - fn sub(self, other: FeeRate) -> Self::Output { - FeeRate(self.0 - other.0) - } -} +pub use bitcoin::FeeRate; /// Trait implemented by types that can be used to measure weight units. pub trait Vbytes { @@ -233,73 +137,3 @@ impl Utxo { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_store_feerate_in_const() { - const _MIN_RELAY: FeeRate = FeeRate::default_min_relay_fee(); - } - - #[test] - #[should_panic] - fn test_invalid_feerate_neg_zero() { - let _ = FeeRate::from_sat_per_vb(-0.0); - } - - #[test] - #[should_panic] - fn test_invalid_feerate_neg_value() { - let _ = FeeRate::from_sat_per_vb(-5.0); - } - - #[test] - #[should_panic] - fn test_invalid_feerate_nan() { - let _ = FeeRate::from_sat_per_vb(f32::NAN); - } - - #[test] - #[should_panic] - fn test_invalid_feerate_inf() { - let _ = FeeRate::from_sat_per_vb(f32::INFINITY); - } - - #[test] - fn test_valid_feerate_pos_zero() { - let _ = FeeRate::from_sat_per_vb(0.0); - } - - #[test] - fn test_fee_from_btc_per_kvb() { - let fee = FeeRate::from_btc_per_kvb(1e-5); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_from_sat_per_vbyte() { - let fee = FeeRate::from_sat_per_vb(1.0); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_default_min_relay_fee() { - let fee = FeeRate::default_min_relay_fee(); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_from_sat_per_kvb() { - let fee = FeeRate::from_sat_per_kvb(1000.0); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_from_sat_per_kwu() { - let fee = FeeRate::from_sat_per_kwu(250.0); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - assert_eq!(fee.sat_per_kwu(), 250.0); - } -} diff --git a/crates/bdk/src/wallet/coin_selection.rs b/crates/bdk/src/wallet/coin_selection.rs index c3e84af2b..8634eb460 100644 --- a/crates/bdk/src/wallet/coin_selection.rs +++ b/crates/bdk/src/wallet/coin_selection.rs @@ -58,7 +58,7 @@ //! }, //! ) //! .collect::>(); -//! let additional_fees = fee_rate.fee_wu(additional_weight); +//! let additional_fees = (fee_rate * additional_weight).to_sat(); //! let amount_needed_with_fees = additional_fees + target_amount; //! if selected_amount < amount_needed_with_fees { //! return Err(bdk::Error::InsufficientFunds { @@ -276,7 +276,7 @@ impl CoinSelectionAlgorithm for OldestFirstCoinSelection { pub fn decide_change(remaining_amount: u64, fee_rate: FeeRate, drain_script: &Script) -> Excess { // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) let drain_output_len = serialize(drain_script).len() + 8usize; - let change_fee = fee_rate.fee_vb(drain_output_len); + let change_fee = (fee_rate * Weight::from_wu((drain_output_len * 4) as u64)).to_sat(); let drain_val = remaining_amount.saturating_sub(change_fee); if drain_val.is_dust(drain_script) { @@ -307,9 +307,11 @@ fn select_sorted_utxos( (&mut selected_amount, &mut fee_amount), |(selected_amount, fee_amount), (must_use, weighted_utxo)| { if must_use || **selected_amount < target_amount + **fee_amount { - **fee_amount += fee_rate.fee_wu(Weight::from_wu( - (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, - )); + **fee_amount += (fee_rate + * Weight::from_wu( + (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, + )) + .to_sat(); **selected_amount += weighted_utxo.utxo.txout().value; log::debug!( @@ -357,9 +359,9 @@ struct OutputGroup { impl OutputGroup { fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self { - let fee = fee_rate.fee_wu(Weight::from_wu( - (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, - )); + let fee = (fee_rate + * Weight::from_wu((TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64)) + .to_sat(); let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64; OutputGroup { weighted_utxo, @@ -426,7 +428,7 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection { .iter() .fold(0, |acc, x| acc + x.effective_value); - let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = self.size_of_change * fee_rate.to_sat_per_vb_ceil(); // `curr_value` and `curr_available_value` are both the sum of *effective_values* of // the UTXOs. For the optional UTXOs (curr_available_value) we filter out UTXOs with @@ -489,7 +491,7 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection { curr_value, curr_available_value, target_amount, - cost_of_change, + cost_of_change as f32, drain_script, fee_rate, ) @@ -840,7 +842,7 @@ mod test { .coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -861,7 +863,7 @@ mod test { .coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -882,7 +884,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -904,7 +906,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -922,7 +924,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1000.0), + FeeRate::from_sat_per_vb_unchecked(1000), target_amount, &drain_script, ) @@ -939,7 +941,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -960,7 +962,7 @@ mod test { .coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -981,7 +983,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1003,7 +1005,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1022,7 +1024,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1000.0), + FeeRate::from_sat_per_vb_unchecked(1000), target_amount, &drain_script, ) @@ -1043,7 +1045,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1064,7 +1066,7 @@ mod test { .coin_select( utxos.clone(), utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1085,7 +1087,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1122,7 +1124,7 @@ mod test { .coin_select( required, optional, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1144,7 +1146,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1162,7 +1164,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1000.0), + FeeRate::from_sat_per_vb_unchecked(1000), target_amount, &drain_script, ) @@ -1179,7 +1181,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1205,7 +1207,7 @@ mod test { .coin_select( vec![], optional_utxos, - FeeRate::from_sat_per_vb(0.0), + FeeRate::from_sat_per_vb_unchecked(0), target_amount, &drain_script, ) @@ -1217,7 +1219,7 @@ mod test { #[test] #[should_panic(expected = "BnBNoExactMatch")] fn test_bnb_function_no_exact_match() { - let fee_rate = FeeRate::from_sat_per_vb(10.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(10); let utxos: Vec = get_test_utxos() .into_iter() .map(|u| OutputGroup::new(u, fee_rate)) @@ -1226,7 +1228,7 @@ mod test { let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value); let size_of_change = 31; - let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = size_of_change * fee_rate.to_sat_per_vb_ceil(); let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; @@ -1237,7 +1239,7 @@ mod test { 0, curr_available_value, target_amount as i64, - cost_of_change, + cost_of_change as f32, &drain_script, fee_rate, ) @@ -1247,7 +1249,7 @@ mod test { #[test] #[should_panic(expected = "BnBTotalTriesExceeded")] fn test_bnb_function_tries_exceeded() { - let fee_rate = FeeRate::from_sat_per_vb(10.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(10); let utxos: Vec = generate_same_value_utxos(100_000, 100_000) .into_iter() .map(|u| OutputGroup::new(u, fee_rate)) @@ -1256,7 +1258,7 @@ mod test { let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value); let size_of_change = 31; - let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = size_of_change * fee_rate.to_sat_per_vb_ceil(); let target_amount = 20_000 + FEE_AMOUNT; let drain_script = ScriptBuf::default(); @@ -1268,7 +1270,7 @@ mod test { 0, curr_available_value, target_amount as i64, - cost_of_change, + cost_of_change as f32, &drain_script, fee_rate, ) @@ -1278,9 +1280,9 @@ mod test { // The match won't be exact but still in the range #[test] fn test_bnb_function_almost_exact_match_with_fees() { - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let size_of_change = 31; - let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = size_of_change * fee_rate.to_sat_per_vb_ceil(); let utxos: Vec<_> = generate_same_value_utxos(50_000, 10) .into_iter() @@ -1293,7 +1295,7 @@ mod test { // 2*(value of 1 utxo) - 2*(1 utxo fees with 1.0sat/vbyte fee rate) - // cost_of_change + 5. - let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as i64 + 5; + let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change as i64 + 5; let drain_script = ScriptBuf::default(); @@ -1304,7 +1306,7 @@ mod test { curr_value, curr_available_value, target_amount, - cost_of_change, + cost_of_change as f32, &drain_script, fee_rate, ) @@ -1318,7 +1320,7 @@ mod test { fn test_bnb_function_exact_match_more_utxos() { let seed = [0; 32]; let mut rng: StdRng = SeedableRng::from_seed(seed); - let fee_rate = FeeRate::from_sat_per_vb(0.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(0); for _ in 0..200 { let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40) @@ -1360,7 +1362,7 @@ mod test { let mut utxos = generate_random_utxos(&mut rng, 300); let target_amount = sum_random_utxos(&mut rng, &mut utxos) + FEE_AMOUNT; - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let utxos: Vec = utxos .into_iter() .map(|u| OutputGroup::new(u, fee_rate)) @@ -1389,7 +1391,7 @@ mod test { let selection = BranchAndBoundCoinSelection::default().coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(10.0), + FeeRate::from_sat_per_vb_unchecked(10), 500_000, &drain_script, ); @@ -1415,7 +1417,7 @@ mod test { let selection = BranchAndBoundCoinSelection::default().coin_select( required, optional, - FeeRate::from_sat_per_vb(10.0), + FeeRate::from_sat_per_vb_unchecked(10), 500_000, &drain_script, ); @@ -1437,7 +1439,7 @@ mod test { let selection = BranchAndBoundCoinSelection::default().coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(10_000.0), + FeeRate::from_sat_per_vb_unchecked(10_000), 500_000, &drain_script, ); diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 9ee72b4b6..26f14b707 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -574,7 +574,7 @@ impl Wallet { pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result { self.calculate_fee(tx).map(|fee| { let weight = tx.weight(); - FeeRate::from_wu(fee, weight) + FeeRate::from_sat_per_kwu(fee.saturating_mul(1000) / weight.to_wu()) }) } @@ -1004,7 +1004,7 @@ impl Wallet { let (fee_rate, mut fee_amount) = match params .fee_policy .as_ref() - .unwrap_or(&FeePolicy::FeeRate(FeeRate::default())) + .unwrap_or(&FeePolicy::FeeRate(FeeRate::BROADCAST_MIN)) { //FIXME: see https://github.com/bitcoindevkit/bdk/issues/256 FeePolicy::FeeAmount(fee) => { @@ -1015,11 +1015,13 @@ impl Wallet { }); } } - (FeeRate::from_sat_per_vb(0.0), *fee) + (FeeRate::ZERO, *fee) } FeePolicy::FeeRate(rate) => { if let Some(previous_fee) = params.bumping_fee { - let required_feerate = FeeRate::from_sat_per_vb(previous_fee.rate + 1.0); + let required_feerate = FeeRate::from_sat_per_kwu( + ((previous_fee.rate + 1.0) as f64 * 250.0).ceil() as u64, + ); if *rate < required_feerate { return Err(Error::FeeRateTooLow { required: required_feerate, @@ -1069,7 +1071,7 @@ impl Wallet { outgoing += value; } - fee_amount += fee_rate.fee_wu(tx.weight()); + fee_amount += (fee_rate * tx.weight()).to_sat(); // Segwit transactions' header is 2WU larger than legacy txs' header, // as they contain a witness marker (1WU) and a witness flag (1WU) (see BIP144). @@ -1080,7 +1082,7 @@ impl Wallet { // end up with a transaction with a slightly higher fee rate than the requested one. // If, instead, we undershoot, we may end up with a feerate lower than the requested one // - we might come up with non broadcastable txs! - fee_amount += fee_rate.fee_wu(Weight::from_wu(2)); + fee_amount += (fee_rate * Weight::from_wu(2)).to_sat(); if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed && internal_descriptor.is_none() @@ -1223,7 +1225,7 @@ impl Wallet { /// let mut psbt = { /// let mut builder = wallet.build_fee_bump(tx.txid())?; /// builder - /// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)); + /// .fee_rate(bdk::FeeRate::from_sat_per_vb_unchecked(5)); /// builder.finish()? /// }; /// @@ -1350,7 +1352,7 @@ impl Wallet { utxos: original_utxos, bumping_fee: Some(tx_builder::PreviousFee { absolute: fee, - rate: fee_rate.as_sat_per_vb(), + rate: fee_rate.to_sat_per_vb_ceil() as f32, }), ..Default::default() }; diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs index 37e85a124..bcbf0bca1 100644 --- a/crates/bdk/src/wallet/tx_builder.rs +++ b/crates/bdk/src/wallet/tx_builder.rs @@ -27,7 +27,7 @@ //! // Create a transaction with one output to `to_address` of 50_000 satoshi //! .add_recipient(to_address.script_pubkey(), 50_000) //! // With a custom fee rate of 5.0 satoshi/vbyte -//! .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)) +//! .fee_rate(bdk::FeeRate::from_sat_per_vb_unchecked(5)) //! // Only spend non-change outputs //! .do_not_spend_change() //! // Turn on RBF signaling @@ -163,7 +163,7 @@ pub(crate) enum FeePolicy { impl Default for FeePolicy { fn default() -> Self { - FeePolicy::FeeRate(FeeRate::default_min_relay_fee()) + FeePolicy::FeeRate(FeeRate::BROADCAST_MIN) } } @@ -652,7 +652,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { /// .drain_wallet() /// // Send the excess (which is all the coins minus the fee) to this address. /// .drain_to(to_address.script_pubkey()) - /// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)) + /// .fee_rate(bdk::FeeRate::from_sat_per_vb_unchecked(5)) /// .enable_rbf(); /// let psbt = tx_builder.finish()?; /// # Ok::<(), bdk::Error>(()) diff --git a/crates/bdk/tests/psbt.rs b/crates/bdk/tests/psbt.rs index 602c37dbd..79a1b53b4 100644 --- a/crates/bdk/tests/psbt.rs +++ b/crates/bdk/tests/psbt.rs @@ -88,7 +88,7 @@ fn test_psbt_fee_rate_with_witness_utxo() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(expected_fee_rate as u64)); let mut psbt = builder.finish().unwrap(); let fee_amount = psbt.fee_amount(); assert!(fee_amount.is_some()); @@ -99,8 +99,8 @@ fn test_psbt_fee_rate_with_witness_utxo() { assert!(finalized); let finalized_fee_rate = psbt.fee_rate().unwrap(); - assert!(finalized_fee_rate.as_sat_per_vb() >= expected_fee_rate); - assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb()); + assert!(finalized_fee_rate.to_sat_per_vb_ceil() as f32 >= expected_fee_rate); + assert!(finalized_fee_rate.to_sat_per_vb_ceil() <= unfinalized_fee_rate.to_sat_per_vb_ceil()); } #[test] @@ -113,7 +113,7 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() { let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(expected_fee_rate as u64)); let mut psbt = builder.finish().unwrap(); let fee_amount = psbt.fee_amount(); assert!(fee_amount.is_some()); @@ -123,8 +123,8 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() { assert!(finalized); let finalized_fee_rate = psbt.fee_rate().unwrap(); - assert!(finalized_fee_rate.as_sat_per_vb() >= expected_fee_rate); - assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb()); + assert!(finalized_fee_rate.to_sat_per_vb_ceil() as f32 >= expected_fee_rate); + assert!(finalized_fee_rate.to_sat_per_vb_ceil() < unfinalized_fee_rate.to_sat_per_vb_ceil()); } #[test] @@ -137,7 +137,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let addr = wpkh_wallet.get_address(New); let mut builder = wpkh_wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(expected_fee_rate as u64)); let mut wpkh_psbt = builder.finish().unwrap(); wpkh_psbt.inputs[0].witness_utxo = None; @@ -149,7 +149,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let addr = pkh_wallet.get_address(New); let mut builder = pkh_wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(expected_fee_rate as u64)); let mut pkh_psbt = builder.finish().unwrap(); pkh_psbt.inputs[0].non_witness_utxo = None; diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index aad8c2db2..20b39cce1 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -12,8 +12,8 @@ use bitcoin::hashes::Hash; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::ScriptBuf; use bitcoin::{ - absolute, script::PushBytesBuf, taproot::TapNodeHash, Address, OutPoint, Sequence, Transaction, - TxIn, TxOut, Weight, + absolute, script::PushBytesBuf, taproot::TapNodeHash, Address, Amount, OutPoint, Sequence, + Transaction, TxIn, TxOut, Weight, }; use bitcoin::{psbt, Network}; use bitcoin::{BlockHash, Txid}; @@ -135,8 +135,8 @@ fn test_get_funded_wallet_tx_fee_rate() { // sats are the transaction fee. // tx weight = 452 bytes, as vbytes = (452+3)/4 = 113 - // fee rate (sats per vbyte) = fee / vbytes = 1000 / 113 = 8.8495575221 rounded to 8.849558 - assert_eq!(tx_fee_rate.as_sat_per_vb(), 8.849558); + // fee rate (sats per vbyte) = fee / vbytes = 1000 / 113 = 8.8495575221 rounded up to 9 + assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); } macro_rules! assert_fee_rate { @@ -171,11 +171,11 @@ macro_rules! assert_fee_rate { assert_eq!(fee_amount, $fees); - let tx_fee_rate = FeeRate::from_wu($fees, tx.weight()); + let tx_fee_rate = Amount::from_sat($fees) / tx.weight(); let fee_rate = $fee_rate; if !dust_change { - assert!(tx_fee_rate >= fee_rate && (tx_fee_rate - fee_rate).as_sat_per_vb().abs() < 0.5, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate); + assert!(tx_fee_rate >= fee_rate && tx_fee_rate.to_sat_per_vb_ceil() - fee_rate.to_sat_per_vb_ceil() == 0, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate); } else { assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {:?}, the tx has {:?}", fee_rate, tx_fee_rate); } @@ -523,7 +523,7 @@ fn test_create_tx_default_fee_rate() { let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::default(), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::BROADCAST_MIN, @add_signature); } #[test] @@ -533,11 +533,11 @@ fn test_create_tx_custom_fee_rate() { let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), 25_000) - .fee_rate(FeeRate::from_sat_per_vb(5.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); } #[test] @@ -629,7 +629,7 @@ fn test_create_tx_drain_to_dust_amount() { builder .drain_to(addr.script_pubkey()) .drain_wallet() - .fee_rate(FeeRate::from_sat_per_vb(453.0)); + .fee_rate(FeeRate::from_sat_per_kwu(123250)); builder.finish().unwrap(); } @@ -1376,7 +1376,7 @@ fn test_bump_fee_low_fee_rate() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(1.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(1)); builder.finish().unwrap(); } @@ -1446,7 +1446,9 @@ fn test_bump_fee_reduce_change() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf(); + builder + .fee_rate(FeeRate::from_sat_per_vb_unchecked(3)) + .enable_rbf(); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1477,7 +1479,7 @@ fn test_bump_fee_reduce_change() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(3), @add_signature); let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_absolute(200); @@ -1542,7 +1544,7 @@ fn test_bump_fee_reduce_single_recipient() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder - .fee_rate(FeeRate::from_sat_per_vb(2.5)) + .fee_rate(FeeRate::from_sat_per_vb_unchecked(3)) .allow_shrinking(addr.script_pubkey()) .unwrap(); let psbt = builder.finish().unwrap(); @@ -1556,7 +1558,7 @@ fn test_bump_fee_reduce_single_recipient() { assert_eq!(tx.output.len(), 1); assert_eq!(tx.output[0].value + fee.unwrap_or(0), sent_received.0); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_kwu(751), @add_signature); } #[test] @@ -1651,7 +1653,7 @@ fn test_bump_fee_drain_wallet() { .drain_wallet() .allow_shrinking(addr.script_pubkey()) .unwrap() - .fee_rate(FeeRate::from_sat_per_vb(5.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.extract_tx()); @@ -1714,7 +1716,7 @@ fn test_bump_fee_remove_output_manually_selected_only() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder .manually_selected_only() - .fee_rate(FeeRate::from_sat_per_vb(255.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(255)); builder.finish().unwrap(); } @@ -1755,7 +1757,7 @@ fn test_bump_fee_add_input() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(50.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1782,7 +1784,7 @@ fn test_bump_fee_add_input() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); } #[test] @@ -1865,7 +1867,7 @@ fn test_bump_fee_no_change_add_input_and_change() { // now bump the fees without using `allow_shrinking`. the wallet should add an // extra input and a change output, and leave the original output untouched let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(50.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1897,7 +1899,7 @@ fn test_bump_fee_no_change_add_input_and_change() { 75_000 - original_send_all_amount - fee.unwrap_or(0) ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); } #[test] @@ -1941,8 +1943,8 @@ fn test_bump_fee_add_input_change_dust() { original_tx_weight + Weight::from_wu(160) + Weight::from_wu(112) - Weight::from_wu(124); // two inputs (50k, 25k) and one output (45k) - epsilon // We use epsilon here to avoid asking for a slightly too high feerate - let fee_abs = 50_000 + 25_000 - 45_000 - 10; - builder.fee_rate(FeeRate::from_wu(fee_abs, new_tx_weight)); + let fee_abs: u64 = 50_000 + 25_000 - 45_000 - 10; + builder.fee_rate(Amount::from_sat(fee_abs) / new_tx_weight); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1965,7 +1967,7 @@ fn test_bump_fee_add_input_change_dust() { 45_000 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(140.0), @dust_change, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(140), @dust_change, @add_signature); } #[test] @@ -1996,7 +1998,7 @@ fn test_bump_fee_force_add_input() { builder .add_utxo(incoming_op) .unwrap() - .fee_rate(FeeRate::from_sat_per_vb(5.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -2024,7 +2026,7 @@ fn test_bump_fee_force_add_input() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); } #[test] @@ -2120,7 +2122,7 @@ fn test_bump_fee_unconfirmed_inputs_only() { .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(25.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(25)); builder.finish().unwrap(); } @@ -2155,7 +2157,7 @@ fn test_bump_fee_unconfirmed_input() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder - .fee_rate(FeeRate::from_sat_per_vb(15.0)) + .fee_rate(FeeRate::from_sat_per_vb_unchecked(15)) .allow_shrinking(addr.script_pubkey()) .unwrap(); builder.finish().unwrap(); @@ -2175,7 +2177,7 @@ fn test_fee_amount_negative_drain_val() { let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") .unwrap() .assume_checked(); - let fee_rate = FeeRate::from_sat_per_vb(2.01); + let fee_rate = FeeRate::from_sat_per_kwu(522); let incoming_op = receive_output_in_latest_block(&mut wallet, 8859); let mut builder = wallet.build_tx(); @@ -2188,7 +2190,7 @@ fn test_fee_amount_negative_drain_val() { let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_eq!(psbt.inputs.len(), 1); + assert_eq!(psbt.inputs.len(), 2); assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate, @add_signature); } @@ -3369,7 +3371,7 @@ fn test_fee_rate_sign_no_grinding_high_r() { // alright. let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_address(New); - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_kwu(251); let mut builder = wallet.build_tx(); let mut data = PushBytesBuf::try_from(vec![0]).unwrap(); builder @@ -3435,7 +3437,7 @@ fn test_fee_rate_sign_grinding_low_r() { // signature is 70 bytes. let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_address(New); - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_kwu(251); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey())