Skip to content

Commit 7360052

Browse files
committed
Write lowest fee test that hits important branch
That proptest is not hitting for some reason.
1 parent 17cc8f2 commit 7360052

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

src/metrics/lowest_fee.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl BnbMetric for LowestFee {
6767

6868
if let Some((_, low_sats_per_wu_candidate)) = cs.unselected().next_back() {
6969
let ev = low_sats_per_wu_candidate.effective_value(self.target.feerate);
70-
if ev < 0.0 {
70+
if ev < -0.0 {
7171
// we can only reduce excess if ev is negative
7272
let value_per_negative_effective_value =
7373
low_sats_per_wu_candidate.value as f32 / ev.abs();
@@ -85,7 +85,7 @@ impl BnbMetric for LowestFee {
8585
.drain_weights
8686
.waste(self.target.feerate, self.long_term_feerate);
8787
let best_score_without_change = Ordf32(
88-
current_score.0 - cost_of_change + cost_of_getting_rid_of_change,
88+
current_score.0 + cost_of_getting_rid_of_change - cost_of_change,
8989
);
9090
if best_score_without_change < current_score {
9191
return Some(best_score_without_change);

tests/lowest_fee.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
mod common;
44
use bdk_coin_select::metrics::{Changeless, LowestFee};
5-
use bdk_coin_select::ChangePolicy;
65
use bdk_coin_select::{BnbMetric, Candidate, CoinSelector};
6+
use bdk_coin_select::{ChangePolicy, Drain, DrainWeights, FeeRate, Target};
77
use proptest::prelude::*;
88

99
proptest! {
@@ -160,3 +160,85 @@ fn combined_changeless_metric() {
160160

161161
assert!(combined_rounds >= rounds);
162162
}
163+
164+
/// This test considers the case where you could actually lower your long term fee by adding another input.
165+
#[test]
166+
fn adding_another_input_to_remove_change() {
167+
let target = Target {
168+
feerate: FeeRate::from_sat_per_vb(1.0),
169+
min_fee: 0,
170+
value: 99_870,
171+
};
172+
173+
let candidates = vec![
174+
Candidate {
175+
value: 100_000,
176+
weight: 100,
177+
input_count: 1,
178+
is_segwit: true,
179+
},
180+
Candidate {
181+
value: 50_000,
182+
weight: 100,
183+
input_count: 1,
184+
is_segwit: true,
185+
},
186+
// NOTE: this input has negative effective value
187+
Candidate {
188+
value: 10,
189+
weight: 100,
190+
input_count: 1,
191+
is_segwit: true,
192+
},
193+
];
194+
195+
let mut cs = CoinSelector::new(&candidates, 200);
196+
197+
let best_solution = {
198+
let mut cs = cs.clone();
199+
cs.select(0);
200+
cs.select(2);
201+
cs.excess(target, Drain::none());
202+
assert!(cs.is_target_met(target));
203+
cs
204+
};
205+
206+
let drain_weights = DrainWeights {
207+
output_weight: 100,
208+
spend_weight: 1_000,
209+
};
210+
211+
let excess_to_make_first_candidate_satisfy_but_have_change = {
212+
let mut cs = cs.clone();
213+
cs.select(0);
214+
assert!(cs.is_target_met(target));
215+
let with_change_excess = cs.excess(
216+
target,
217+
Drain {
218+
value: 0,
219+
weights: drain_weights,
220+
},
221+
);
222+
assert!(with_change_excess > 0);
223+
with_change_excess as u64
224+
};
225+
226+
let change_policy = ChangePolicy {
227+
min_value: excess_to_make_first_candidate_satisfy_but_have_change - 10,
228+
drain_weights,
229+
};
230+
231+
let mut metric = LowestFee {
232+
target,
233+
long_term_feerate: FeeRate::from_sat_per_vb(1.0),
234+
change_policy,
235+
};
236+
237+
let (score, _) = common::bnb_search(&mut cs, metric, 10).expect("finds solution");
238+
let best_solution_score = metric.score(&best_solution).expect("must be a solution");
239+
240+
assert_eq!(best_solution.drain(target, change_policy), Drain::none());
241+
242+
assert!(score <= best_solution_score);
243+
assert_eq!(cs.selected_indices(), best_solution.selected_indices());
244+
}

0 commit comments

Comments
 (0)