|
2 | 2 |
|
3 | 3 | mod common;
|
4 | 4 | use bdk_coin_select::metrics::{Changeless, LowestFee};
|
5 |
| -use bdk_coin_select::ChangePolicy; |
6 | 5 | use bdk_coin_select::{BnbMetric, Candidate, CoinSelector};
|
| 6 | +use bdk_coin_select::{ChangePolicy, Drain, DrainWeights, FeeRate, Target}; |
7 | 7 | use proptest::prelude::*;
|
8 | 8 |
|
9 | 9 | proptest! {
|
@@ -160,3 +160,85 @@ fn combined_changeless_metric() {
|
160 | 160 |
|
161 | 161 | assert!(combined_rounds >= rounds);
|
162 | 162 | }
|
| 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