diff --git a/crates/bdk/src/psbt/mod.rs b/crates/bdk/src/psbt/mod.rs index 7a66989e98..9b3aea843d 100644 --- a/crates/bdk/src/psbt/mod.rs +++ b/crates/bdk/src/psbt/mod.rs @@ -26,7 +26,7 @@ pub trait PsbtUtils { /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in sats. /// If the PSBT is missing a TxOut for an input returns None. - fn fee_amount(&self) -> Option; + fn fee_amount(&self) -> Option; /// The transaction's fee rate. This value will only be accurate if calculated AFTER the /// `Psbt` is finalized and all witness/signature data is added to the @@ -49,18 +49,13 @@ impl PsbtUtils for Psbt { } } - fn fee_amount(&self) -> Option { + fn fee_amount(&self) -> Option { let tx = &self.unsigned_tx; let utxos: Option> = (0..tx.input.len()).map(|i| self.get_utxo_for(i)).collect(); utxos.map(|inputs| { - let input_amount: u64 = inputs.iter().map(|i| i.value.to_sat()).sum(); - let output_amount: u64 = self - .unsigned_tx - .output - .iter() - .map(|o| o.value.to_sat()) - .sum(); + let input_amount: Amount = inputs.iter().map(|i| i.value).sum(); + let output_amount: Amount = self.unsigned_tx.output.iter().map(|o| o.value).sum(); input_amount .checked_sub(output_amount) .expect("input amount must be greater than output amount") @@ -70,6 +65,6 @@ impl PsbtUtils for Psbt { fn fee_rate(&self) -> Option { let fee_amount = self.fee_amount(); let weight = self.clone().extract_tx().ok()?.weight(); - fee_amount.map(|fee| Amount::from_sat(fee) / weight) + fee_amount.map(|fee| fee / weight) } } diff --git a/crates/bdk/src/wallet/error.rs b/crates/bdk/src/wallet/error.rs index eaf811d6fc..abb6a3a37d 100644 --- a/crates/bdk/src/wallet/error.rs +++ b/crates/bdk/src/wallet/error.rs @@ -16,7 +16,7 @@ use crate::descriptor::DescriptorError; use crate::wallet::coin_selection; use crate::{descriptor, KeychainKind}; use alloc::string::String; -use bitcoin::{absolute, psbt, OutPoint, Sequence, Txid}; +use bitcoin::{absolute, psbt, Amount, OutPoint, Sequence, Txid}; use core::fmt; /// Errors returned by miniscript when updating inconsistent PSBTs @@ -78,8 +78,8 @@ pub enum CreateTxError { }, /// When bumping a tx the absolute fee requested is lower than replaced tx absolute fee FeeTooLow { - /// Required fee absolute value (satoshi) - required: u64, + /// Required fee absolute value [`Amount`] + required: Amount, }, /// When bumping a tx the fee rate requested is lower than required FeeRateTooLow { diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 3850bc30aa..d5cc79633d 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -890,7 +890,7 @@ impl Wallet { self.persist.stage(ChangeSet::from(additions)); } - /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction. + /// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase transaction. /// /// To calculate the fee for a [`Transaction`] with inputs not owned by this wallet you must /// manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function. @@ -917,7 +917,7 @@ impl Wallet { /// let fee = wallet.calculate_fee(tx).expect("fee"); /// ``` /// [`insert_txout`]: Self::insert_txout - pub fn calculate_fee(&self, tx: &Transaction) -> Result { + pub fn calculate_fee(&self, tx: &Transaction) -> Result { self.indexed_graph.graph().calculate_fee(tx) } @@ -949,8 +949,7 @@ impl Wallet { /// ``` /// [`insert_txout`]: Self::insert_txout pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result { - self.calculate_fee(tx) - .map(|fee| Amount::from_sat(fee) / tx.weight()) + self.calculate_fee(tx).map(|fee| fee / tx.weight()) } /// Compute the `tx`'s sent and received [`Amount`]s. @@ -1405,7 +1404,7 @@ impl Wallet { }); } } - (rate, 0) + (rate, Amount::ZERO) } }; @@ -1421,8 +1420,9 @@ impl Wallet { } // we keep it as a float while we accumulate it, and only round it at the end - let mut outgoing: u64 = 0; - let mut received: u64 = 0; + // (it's internally implemented as a float as Amount(u64)) + let mut outgoing = Amount::ZERO; + let mut received = Amount::ZERO; let recipients = params.recipients.iter().map(|(r, v)| (r, *v)); @@ -1435,7 +1435,7 @@ impl Wallet { } if self.is_mine(script_pubkey) { - received += value; + received += Amount::from_sat(value); } let new_out = TxOut { @@ -1445,10 +1445,10 @@ impl Wallet { tx.output.push(new_out); - outgoing += value; + outgoing += Amount::from_sat(value); } - fee_amount += (fee_rate * tx.weight()).to_sat(); + fee_amount += fee_rate * tx.weight(); if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed && internal_descriptor.is_none() @@ -1484,10 +1484,10 @@ impl Wallet { required_utxos, optional_utxos, fee_rate, - outgoing + fee_amount, + (outgoing + fee_amount).to_sat(), // FIXME: (@leonardo) I think this should be also updated to expect `Amount` &drain_script, )?; - fee_amount += coin_selection.fee_amount; + fee_amount += Amount::from_sat(coin_selection.fee_amount); let excess = &coin_selection.excess; tx.input = coin_selection @@ -1529,12 +1529,12 @@ impl Wallet { match excess { NoChange { remaining_amount, .. - } => fee_amount += remaining_amount, + } => fee_amount += Amount::from_sat(*remaining_amount), Change { amount, fee } => { if self.is_mine(&drain_script) { - received += amount; + received += Amount::from_sat(*amount); } - fee_amount += fee; + fee_amount += Amount::from_sat(*fee); // create drain output let drain_output = TxOut { diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs index 5c3e70d54e..37cc3a0ac5 100644 --- a/crates/bdk/src/wallet/tx_builder.rs +++ b/crates/bdk/src/wallet/tx_builder.rs @@ -159,14 +159,14 @@ pub(crate) struct TxParams { #[derive(Clone, Copy, Debug)] pub(crate) struct PreviousFee { - pub absolute: u64, + pub absolute: Amount, pub rate: FeeRate, } #[derive(Debug, Clone, Copy)] pub(crate) enum FeePolicy { FeeRate(FeeRate), - FeeAmount(u64), + FeeAmount(Amount), } impl Default for FeePolicy { @@ -204,15 +204,15 @@ impl<'a, Cs, Ctx> TxBuilder<'a, Cs, Ctx> { } /// Set an absolute fee - /// The fee_absolute method refers to the absolute transaction fee in satoshis (sats). - /// If anyone sets both the fee_absolute method and the fee_rate method, - /// the FeePolicy enum will be set by whichever method was called last, - /// as the FeeRate and FeeAmount are mutually exclusive. + /// The fee_absolute method refers to the absolute transaction fee in [`Amount`]. + /// If anyone sets both the `fee_absolute` method and the `fee_rate` method, + /// the `FeePolicy` enum will be set by whichever method was called last, + /// as the [`FeeRate`] and `FeeAmount` are mutually exclusive. /// /// Note that this is really a minimum absolute fee -- it's possible to /// overshoot it slightly since adding a change output to drain the remaining /// excess might not be viable. - pub fn fee_absolute(&mut self, fee_amount: u64) -> &mut Self { + pub fn fee_absolute(&mut self, fee_amount: Amount) -> &mut Self { self.params.fee_policy = Some(FeePolicy::FeeAmount(fee_amount)); self } diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index 42ec7d39ad..5ff55bac93 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -233,7 +233,7 @@ fn test_get_funded_wallet_tx_fees() { // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 // sats are the transaction fee. - assert_eq!(tx_fee, 1000) + assert_eq!(tx_fee, Amount::from_sat(1000)) } #[test] @@ -298,16 +298,16 @@ macro_rules! assert_fee_rate { let fee_amount = psbt .inputs .iter() - .fold(0, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value.to_sat()) + .fold(Amount::ZERO, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value) - psbt .unsigned_tx .output .iter() - .fold(0, |acc, o| acc + o.value.to_sat()); + .fold(Amount::ZERO, |acc, o| acc + o.value); assert_eq!(fee_amount, $fees); - let tx_fee_rate = (Amount::from_sat(fee_amount) / tx.weight()) + let tx_fee_rate = (fee_amount / tx.weight()) .to_sat_per_kwu(); let fee_rate = $fee_rate.to_sat_per_kwu(); let half_default = FeeRate::BROADCAST_MIN.checked_div(2) @@ -589,8 +589,8 @@ fn test_create_tx_drain_wallet_and_drain_to() { assert_eq!(psbt.unsigned_tx.output.len(), 1); assert_eq!( - psbt.unsigned_tx.output[0].value.to_sat(), - 50_000 - fee.unwrap_or(0) + psbt.unsigned_tx.output[0].value, + Amount::from_sat(50_000) - fee.unwrap_or(Amount::ZERO) ); } @@ -619,8 +619,11 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { .iter() .find(|x| x.script_pubkey == drain_addr.script_pubkey()) .unwrap(); - assert_eq!(main_output.value.to_sat(), 20_000,); - assert_eq!(drain_output.value.to_sat(), 30_000 - fee.unwrap_or(0)); + assert_eq!(main_output.value, Amount::from_sat(20_000)); + assert_eq!( + drain_output.value, + Amount::from_sat(30_000) - fee.unwrap_or(Amount::ZERO) + ); } #[test] @@ -638,8 +641,8 @@ fn test_create_tx_drain_to_and_utxos() { assert_eq!(psbt.unsigned_tx.output.len(), 1); assert_eq!( - psbt.unsigned_tx.output[0].value.to_sat(), - 50_000 - fee.unwrap_or(0) + psbt.unsigned_tx.output[0].value, + Amount::from_sat(50_000) - fee.unwrap_or(Amount::ZERO) ); } @@ -662,7 +665,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::BROADCAST_MIN, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::BROADCAST_MIN, @add_signature); } #[test] @@ -676,7 +679,7 @@ fn test_create_tx_custom_fee_rate() { let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); } #[test] @@ -687,15 +690,15 @@ fn test_create_tx_absolute_fee() { builder .drain_to(addr.script_pubkey()) .drain_wallet() - .fee_absolute(100); + .fee_absolute(Amount::from_sat(100)); let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_eq!(fee.unwrap_or(0), 100); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(100)); assert_eq!(psbt.unsigned_tx.output.len(), 1); assert_eq!( - psbt.unsigned_tx.output[0].value.to_sat(), - 50_000 - fee.unwrap_or(0) + psbt.unsigned_tx.output[0].value, + Amount::from_sat(50_000) - fee.unwrap_or(Amount::ZERO) ); } @@ -707,15 +710,15 @@ fn test_create_tx_absolute_zero_fee() { builder .drain_to(addr.script_pubkey()) .drain_wallet() - .fee_absolute(0); + .fee_absolute(Amount::from_sat(0)); let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_eq!(fee.unwrap_or(0), 0); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::ZERO); assert_eq!(psbt.unsigned_tx.output.len(), 1); assert_eq!( - psbt.unsigned_tx.output[0].value.to_sat(), - 50_000 - fee.unwrap_or(0) + psbt.unsigned_tx.output[0].value, + Amount::from_sat(50_000) - fee.unwrap_or(Amount::ZERO) ); } @@ -728,7 +731,7 @@ fn test_create_tx_absolute_high_fee() { builder .drain_to(addr.script_pubkey()) .drain_wallet() - .fee_absolute(60_000); + .fee_absolute(Amount::from_sat(60_000)); let _ = builder.finish().unwrap(); } @@ -746,10 +749,10 @@ fn test_create_tx_add_change() { let fee = check_fee!(wallet, psbt); assert_eq!(psbt.unsigned_tx.output.len(), 2); - assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 25_000); + assert_eq!(psbt.unsigned_tx.output[0].value, Amount::from_sat(25_000)); assert_eq!( - psbt.unsigned_tx.output[1].value.to_sat(), - 25_000 - fee.unwrap_or(0) + psbt.unsigned_tx.output[1].value, + Amount::from_sat(25_000) - fee.unwrap_or(Amount::ZERO) ); } @@ -764,7 +767,7 @@ fn test_create_tx_skip_change_dust() { assert_eq!(psbt.unsigned_tx.output.len(), 1); assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 49_800); - assert_eq!(fee.unwrap_or(0), 200); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(200)); } #[test] @@ -795,11 +798,11 @@ fn test_create_tx_ordering_respected() { assert_eq!(psbt.unsigned_tx.output.len(), 3); assert_eq!( - psbt.unsigned_tx.output[0].value.to_sat(), - 10_000 - fee.unwrap_or(0) + psbt.unsigned_tx.output[0].value, + Amount::from_sat(10_000) - fee.unwrap_or(Amount::ZERO) ); - assert_eq!(psbt.unsigned_tx.output[1].value.to_sat(), 10_000); - assert_eq!(psbt.unsigned_tx.output[2].value.to_sat(), 30_000); + assert_eq!(psbt.unsigned_tx.output[1].value, Amount::from_sat(10_000)); + assert_eq!(psbt.unsigned_tx.output[2].value, Amount::from_sat(30_000)); } #[test] @@ -1226,8 +1229,8 @@ fn test_add_foreign_utxo() { wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); assert_eq!( - (sent_received.0 - sent_received.1).to_sat(), - 10_000 + fee.unwrap_or(0), + (sent_received.0 - sent_received.1), + Amount::from_sat(10_000) + fee.unwrap_or(Amount::ZERO), "we should have only net spent ~10_000" ); @@ -1566,7 +1569,7 @@ fn test_bump_fee_low_abs() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(10); + builder.fee_absolute(Amount::from_sat(10)); builder.finish().unwrap(); } @@ -1588,7 +1591,7 @@ fn test_bump_fee_zero_abs() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(0); + builder.fee_absolute(Amount::from_sat(0)); builder.finish().unwrap(); } @@ -1623,10 +1626,10 @@ fn test_bump_fee_reduce_change() { assert_eq!(sent_received.0, original_sent_received.0); assert_eq!( - sent_received.1 + Amount::from_sat(fee.unwrap_or(0)), - original_sent_received.1 + Amount::from_sat(original_fee.unwrap_or(0)) + sent_received.1 + fee.unwrap_or(Amount::ZERO), + original_sent_received.1 + original_fee.unwrap_or(Amount::ZERO) ); - assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0)); + assert!(fee.unwrap_or(Amount::ZERO) > original_fee.unwrap_or(Amount::ZERO)); let tx = &psbt.unsigned_tx; assert_eq!(tx.output.len(), 2); @@ -1647,10 +1650,10 @@ fn test_bump_fee_reduce_change() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), feerate, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), feerate, @add_signature); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(200); + builder.fee_absolute(Amount::from_sat(200)); builder.enable_rbf(); let psbt = builder.finish().unwrap(); let sent_received = @@ -1659,14 +1662,14 @@ fn test_bump_fee_reduce_change() { assert_eq!(sent_received.0, original_sent_received.0); assert_eq!( - sent_received.1 + Amount::from_sat(fee.unwrap_or(0)), - original_sent_received.1 + Amount::from_sat(original_fee.unwrap_or(0)) + sent_received.1 + fee.unwrap_or(Amount::ZERO), + original_sent_received.1 + original_fee.unwrap_or(Amount::ZERO) ); assert!( - fee.unwrap_or(0) > original_fee.unwrap_or(0), + fee.unwrap_or(Amount::ZERO) > original_fee.unwrap_or(Amount::ZERO), "{} > {}", - fee.unwrap_or(0), - original_fee.unwrap_or(0) + fee.unwrap_or(Amount::ZERO), + original_fee.unwrap_or(Amount::ZERO) ); let tx = &psbt.unsigned_tx; @@ -1688,7 +1691,7 @@ fn test_bump_fee_reduce_change() { sent_received.1 ); - assert_eq!(fee.unwrap_or(0), 200); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(200)); } #[test] @@ -1723,16 +1726,16 @@ fn test_bump_fee_reduce_single_recipient() { let fee = check_fee!(wallet, psbt); assert_eq!(sent_received.0, original_sent_received.0); - assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0)); + assert!(fee.unwrap_or(Amount::ZERO) > original_fee.unwrap_or(Amount::ZERO)); let tx = &psbt.unsigned_tx; assert_eq!(tx.output.len(), 1); assert_eq!( - tx.output[0].value + Amount::from_sat(fee.unwrap_or(0)), + tx.output[0].value + fee.unwrap_or(Amount::ZERO), sent_received.0 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), feerate, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), feerate, @add_signature); } #[test] @@ -1759,22 +1762,22 @@ fn test_bump_fee_absolute_reduce_single_recipient() { builder .allow_shrinking(addr.script_pubkey()) .unwrap() - .fee_absolute(300); + .fee_absolute(Amount::from_sat(300)); let psbt = builder.finish().unwrap(); let tx = &psbt.unsigned_tx; let sent_received = wallet.sent_and_received(tx); let fee = check_fee!(wallet, psbt); assert_eq!(sent_received.0, original_sent_received.0); - assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0)); + assert!(fee.unwrap_or(Amount::ZERO) > original_fee.unwrap_or(Amount::ZERO)); assert_eq!(tx.output.len(), 1); assert_eq!( - tx.output[0].value + Amount::from_sat(fee.unwrap_or(0)), + tx.output[0].value + fee.unwrap_or(Amount::ZERO), sent_received.0 ); - assert_eq!(fee.unwrap_or(0), 300); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(300)); } #[test] @@ -1953,7 +1956,7 @@ fn test_bump_fee_add_input() { original_details.0 + Amount::from_sat(25_000) ); assert_eq!( - Amount::from_sat(fee.unwrap_or(0)) + sent_received.1, + fee.unwrap_or(Amount::ZERO) + sent_received.1, Amount::from_sat(30_000) ); @@ -1977,7 +1980,7 @@ fn test_bump_fee_add_input() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); } #[test] @@ -2000,7 +2003,7 @@ fn test_bump_fee_absolute_add_input() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(6_000); + builder.fee_absolute(Amount::from_sat(6_000)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); @@ -2011,7 +2014,7 @@ fn test_bump_fee_absolute_add_input() { original_sent_received.0 + Amount::from_sat(25_000) ); assert_eq!( - Amount::from_sat(fee.unwrap_or(0)) + sent_received.1, + fee.unwrap_or(Amount::ZERO) + sent_received.1, Amount::from_sat(30_000) ); @@ -2035,7 +2038,7 @@ fn test_bump_fee_absolute_add_input() { sent_received.1 ); - assert_eq!(fee.unwrap_or(0), 6_000); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(6_000)); } #[test] @@ -2074,15 +2077,14 @@ fn test_bump_fee_no_change_add_input_and_change() { wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); let fee = check_fee!(wallet, psbt); - let original_send_all_amount = - original_sent_received.0 - Amount::from_sat(original_fee.unwrap_or(0)); + let original_send_all_amount = original_sent_received.0 - original_fee.unwrap_or(Amount::ZERO); assert_eq!( sent_received.0, original_sent_received.0 + Amount::from_sat(50_000) ); assert_eq!( sent_received.1, - Amount::from_sat(75_000) - original_send_all_amount - Amount::from_sat(fee.unwrap_or(0)) + Amount::from_sat(75_000) - original_send_all_amount - fee.unwrap_or(Amount::ZERO) ); let tx = &psbt.unsigned_tx; @@ -2102,10 +2104,10 @@ fn test_bump_fee_no_change_add_input_and_change() { .find(|txout| txout.script_pubkey != addr.script_pubkey()) .unwrap() .value, - Amount::from_sat(75_000) - original_send_all_amount - Amount::from_sat(fee.unwrap_or(0)) + Amount::from_sat(75_000) - original_send_all_amount - fee.unwrap_or(Amount::ZERO) ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); } #[test] @@ -2159,14 +2161,14 @@ fn test_bump_fee_add_input_change_dust() { assert_eq!( original_sent_received.1, - Amount::from_sat(5_000 - original_fee.unwrap_or(0)) + Amount::from_sat(5_000) - original_fee.unwrap_or(Amount::ZERO) ); assert_eq!( sent_received.0, original_sent_received.0 + Amount::from_sat(25_000) ); - assert_eq!(fee.unwrap_or(0), 30_000); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(30_000)); assert_eq!(sent_received.1, Amount::ZERO); let tx = &psbt.unsigned_tx; @@ -2181,7 +2183,7 @@ fn test_bump_fee_add_input_change_dust() { Amount::from_sat(45_000) ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(140), @dust_change, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(140), @dust_change, @add_signature); } #[test] @@ -2223,7 +2225,7 @@ fn test_bump_fee_force_add_input() { original_sent_received.0 + Amount::from_sat(25_000) ); assert_eq!( - Amount::from_sat(fee.unwrap_or(0)) + sent_received.1, + fee.unwrap_or(Amount::ZERO) + sent_received.1, Amount::from_sat(30_000) ); @@ -2247,7 +2249,7 @@ fn test_bump_fee_force_add_input() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); } #[test] @@ -2277,7 +2279,10 @@ fn test_bump_fee_absolute_force_add_input() { // the new fee_rate is low enough that just reducing the change would be fine, but we force // the addition of an extra input with `add_utxo()` let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.add_utxo(incoming_op).unwrap().fee_absolute(250); + builder + .add_utxo(incoming_op) + .unwrap() + .fee_absolute(Amount::from_sat(250)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); @@ -2288,7 +2293,7 @@ fn test_bump_fee_absolute_force_add_input() { original_sent_received.0 + Amount::from_sat(25_000) ); assert_eq!( - Amount::from_sat(fee.unwrap_or(0)) + sent_received.1, + fee.unwrap_or(Amount::ZERO) + sent_received.1, Amount::from_sat(30_000) ); @@ -2312,7 +2317,7 @@ fn test_bump_fee_absolute_force_add_input() { sent_received.1 ); - assert_eq!(fee.unwrap_or(0), 250); + assert_eq!(fee.unwrap_or(Amount::ZERO), Amount::from_sat(250)); } #[test] @@ -2419,7 +2424,7 @@ fn test_fee_amount_negative_drain_val() { let fee = check_fee!(wallet, psbt); assert_eq!(psbt.inputs.len(), 1); - assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), fee_rate, @add_signature); } #[test] @@ -3256,7 +3261,7 @@ fn test_taproot_foreign_utxo() { assert_eq!( sent_received.0 - sent_received.1, - Amount::from_sat(10_000 + fee.unwrap_or(0)), + Amount::from_sat(10_000) + fee.unwrap_or(Amount::ZERO), "we should have only net spent ~10_000" ); @@ -3760,7 +3765,7 @@ fn test_fee_rate_sign_no_grinding_high_r() { ) .unwrap(); // ...and checking that everything is fine - assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), fee_rate); } #[test] @@ -3794,7 +3799,7 @@ fn test_fee_rate_sign_grinding_low_r() { let key = psbt.inputs[0].partial_sigs.keys().next().unwrap(); let sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len(); assert_eq!(sig_len, 70); - assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate); + assert_fee_rate!(psbt, fee.unwrap_or(Amount::ZERO), fee_rate); } #[test] diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index cf51485547..2eccf147b4 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -95,7 +95,7 @@ use crate::{ use alloc::collections::vec_deque::VecDeque; use alloc::sync::Arc; use alloc::vec::Vec; -use bitcoin::{Amount, OutPoint, Script, Transaction, TxOut, Txid}; +use bitcoin::{Amount, OutPoint, Script, SignedAmount, Transaction, TxOut, Txid}; use core::fmt::{self, Formatter}; use core::{ convert::Infallible, @@ -182,7 +182,7 @@ pub enum CalculateFeeError { /// Missing `TxOut` for one or more of the inputs of the tx MissingTxOut(Vec), /// When the transaction is invalid according to the graph it has a negative fee - NegativeFee(i64), + NegativeFee(SignedAmount), } impl fmt::Display for CalculateFeeError { @@ -307,7 +307,7 @@ impl TxGraph { }) } - /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction. + /// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase transaction. /// Returns `OK(_)` if we have all the [`TxOut`]s being spent by `tx` in the graph (either as /// the full transactions or individual txouts). /// @@ -318,20 +318,20 @@ impl TxGraph { /// Note `tx` does not have to be in the graph for this to work. /// /// [`insert_txout`]: Self::insert_txout - pub fn calculate_fee(&self, tx: &Transaction) -> Result { + pub fn calculate_fee(&self, tx: &Transaction) -> Result { if tx.is_coinbase() { - return Ok(0); + return Ok(Amount::ZERO); } let (inputs_sum, missing_outputs) = tx.input.iter().fold( - (0_i64, Vec::new()), + (SignedAmount::ZERO, Vec::new()), |(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) { None => { missing_outpoints.push(txin.previous_output); (sum, missing_outpoints) } Some(txout) => { - sum += txout.value.to_sat() as i64; + sum += txout.value.to_signed().expect("valid `SignedAmount`"); (sum, missing_outpoints) } }, @@ -343,14 +343,14 @@ impl TxGraph { let outputs_sum = tx .output .iter() - .map(|txout| txout.value.to_sat() as i64) - .sum::(); + .map(|txout| txout.value.to_signed().expect("valid `SignedAmount`")) + .sum::(); let fee = inputs_sum - outputs_sum; - if fee < 0 { + if fee < SignedAmount::ZERO { Err(CalculateFeeError::NegativeFee(fee)) } else { - Ok(fee as u64) + Ok(fee.to_unsigned().expect("valid `Amount`")) } } diff --git a/crates/chain/tests/test_tx_graph.rs b/crates/chain/tests/test_tx_graph.rs index 1c7a90f72a..502caa1d94 100644 --- a/crates/chain/tests/test_tx_graph.rs +++ b/crates/chain/tests/test_tx_graph.rs @@ -7,6 +7,7 @@ use bdk_chain::{ tx_graph::{ChangeSet, TxGraph}, Anchor, Append, BlockId, ChainOracle, ChainPosition, ConfirmationHeightAnchor, }; +use bitcoin::SignedAmount; use bitcoin::{ absolute, hashes::Hash, transaction, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, @@ -464,14 +465,14 @@ fn test_calculate_fee() { }], }; - assert_eq!(graph.calculate_fee(&tx), Ok(100)); + assert_eq!(graph.calculate_fee(&tx), Ok(Amount::from_sat(100))); tx.input.remove(2); // fee would be negative, should return CalculateFeeError::NegativeFee assert_eq!( graph.calculate_fee(&tx), - Err(CalculateFeeError::NegativeFee(-200)) + Err(CalculateFeeError::NegativeFee(SignedAmount::from_sat(-200))) ); // If we have an unknown outpoint, fee should return CalculateFeeError::MissingTxOut. @@ -503,7 +504,7 @@ fn test_calculate_fee_on_coinbase() { let graph = TxGraph::<()>::default(); - assert_eq!(graph.calculate_fee(&tx), Ok(0)); + assert_eq!(graph.calculate_fee(&tx), Ok(Amount::ZERO)); } // `test_walk_ancestors` uses the following transaction structure: diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index c6f7d6ce3d..2258c9d60b 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -92,7 +92,8 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { .fee .expect("Fee must exist") .abs() - .to_sat() as u64; + .to_unsigned() + .expect("valid `Amount`"); // Check that the calculated fee matches the fee from the transaction data. assert_eq!(fee, tx_fee); diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index 1d8d8d78dd..2e363f4e6d 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -92,7 +92,8 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { .fee .expect("Fee must exist") .abs() - .to_sat() as u64; + .to_unsigned() + .expect("valid `Amount`"); // Check that the calculated fee matches the fee from the transaction data. assert_eq!(fee, tx_fee);