@@ -2,7 +2,7 @@ use std::path::{PathBuf};
2
2
use std:: str:: FromStr ;
3
3
use protocol:: bitcoin:: { Amount , FeeRate } ;
4
4
use protocol:: constants:: RENEWAL_INTERVAL ;
5
- use protocol:: Covenant ;
5
+ use protocol:: { Covenant } ;
6
6
use protocol:: script:: SpaceScript ;
7
7
use spaced:: rpc:: { BidParams , ExecuteParams , OpenParams , RegisterParams , RpcClient , RpcWalletRequest , RpcWalletTxBuilder , TransferSpacesParams } ;
8
8
use spaced:: wallets:: { AddressKind , WalletResponse } ;
@@ -13,8 +13,8 @@ const ALICE: &str = "wallet_99";
13
13
const BOB : & str = "wallet_98" ;
14
14
const EVE : & str = "wallet_93" ;
15
15
16
- const TEST_SPACE : & str = "@example123" ;
17
- const TEST_INITIAL_BID : u64 = 5000 ;
16
+ const TEST_SPACE : & str = "@example123" ;
17
+ const TEST_INITIAL_BID : u64 = 5000 ;
18
18
19
19
20
20
/// alice opens [TEST_SPACE] for auction
@@ -54,6 +54,7 @@ async fn it_should_open_a_space_for_auction(rig: &TestRig) -> anyhow::Result<()>
54
54
/// Bob outbids alice by 1 sat
55
55
async fn it_should_allow_outbidding ( rig : & TestRig ) -> anyhow:: Result < ( ) > {
56
56
// Bob outbids alice
57
+ rig. wait_until_synced ( ) . await ?;
57
58
rig. wait_until_wallet_synced ( BOB ) . await ?;
58
59
rig. wait_until_wallet_synced ( ALICE ) . await ?;
59
60
let bobs_spaces = rig. spaced . client . wallet_list_spaces ( BOB ) . await ?;
@@ -67,7 +68,7 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
67
68
name: TEST_SPACE . to_string( ) ,
68
69
amount: TEST_INITIAL_BID + 1 ,
69
70
} ) ,
70
- ] , false ) . await . expect ( "send request" ) ;
71
+ ] , false ) . await . expect ( "send request" ) ;
71
72
72
73
println ! ( "{}" , serde_json:: to_string_pretty( & result) . unwrap( ) ) ;
73
74
rig. mine_blocks ( 1 , None ) . await ?;
@@ -82,7 +83,7 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
82
83
assert_eq ! ( alices_spaces. len( ) - 1 , alice_spaces_updated. len( ) , "alice must have one less space" ) ;
83
84
assert_eq ! ( bobs_spaces. len( ) + 1 , bob_spaces_updated. len( ) , "bob must have a new space" ) ;
84
85
assert_eq ! ( alices_balance_updated. balance, alices_balance. balance +
85
- Amount :: from_sat( TEST_INITIAL_BID + 662 ) , "alice must be refunded this exact amount" ) ;
86
+ Amount :: from_sat( TEST_INITIAL_BID + 662 ) , "alice must be refunded this exact amount" ) ;
86
87
87
88
let fullspaceout = rig. spaced . client . get_space ( TEST_SPACE ) . await ?;
88
89
let fullspaceout = fullspaceout. expect ( "a fullspace out" ) ;
@@ -91,7 +92,7 @@ async fn it_should_allow_outbidding(rig: &TestRig) -> anyhow::Result<()> {
91
92
match space. covenant {
92
93
Covenant :: Bid { total_burned, burn_increment, claim_height, .. } => {
93
94
assert ! ( claim_height. is_none( ) , "none for pre-auctions" ) ;
94
- assert_eq ! ( total_burned, Amount :: from_sat( TEST_INITIAL_BID + 1 ) , "total burned" ) ;
95
+ assert_eq ! ( total_burned, Amount :: from_sat( TEST_INITIAL_BID + 1 ) , "total burned" ) ;
95
96
assert_eq ! ( burn_increment, Amount :: from_sat( 1 ) , "burn increment only 1 sat" ) ;
96
97
}
97
98
_ => panic ! ( "expected a bid covenant" )
@@ -124,18 +125,45 @@ async fn it_should_only_accept_forced_zero_value_bid_increments_and_revoke(rig:
124
125
amount: last_bid. to_sat( ) ,
125
126
} ) ,
126
127
] ,
127
- false ) . await . is_err( ) , "shouldn't be able to bid with same value unless forced" ) ;
128
+ false ) . await . is_err( ) , "shouldn't be able to bid with same value unless forced" ) ;
128
129
129
- // force
130
- let result = wallet_do (
131
- rig, EVE ,
132
- vec ! [
133
- RpcWalletRequest :: Bid ( BidParams {
134
- name: TEST_SPACE . to_string( ) ,
135
- amount: last_bid. to_sat( ) ,
136
- } ) ,
137
- ] ,
138
- true ) . await . expect ( "send request" ) ;
130
+ // force only
131
+ assert ! ( rig. spaced. client. wallet_send_request(
132
+ EVE ,
133
+ RpcWalletTxBuilder {
134
+ bidouts: None ,
135
+ requests: vec![
136
+ RpcWalletRequest :: Bid ( BidParams {
137
+ name: TEST_SPACE . to_string( ) ,
138
+ amount: last_bid. to_sat( ) ,
139
+ } ) ,
140
+ ] ,
141
+ fee_rate: Some ( FeeRate :: from_sat_per_vb( 1 ) . expect( "fee" ) ) ,
142
+ dust: None ,
143
+ force: true ,
144
+ confirmed_only: false ,
145
+ skip_tx_check: false ,
146
+ } ,
147
+ ) . await . is_err( ) , "should require skip tx check" ) ;
148
+
149
+ // force & skip tx check
150
+ let result = rig. spaced . client . wallet_send_request (
151
+ EVE ,
152
+ RpcWalletTxBuilder {
153
+ bidouts : None ,
154
+ requests : vec ! [
155
+ RpcWalletRequest :: Bid ( BidParams {
156
+ name: TEST_SPACE . to_string( ) ,
157
+ amount: last_bid. to_sat( ) ,
158
+ } ) ,
159
+ ] ,
160
+ fee_rate : Some ( FeeRate :: from_sat_per_vb ( 1 ) . expect ( "fee" ) ) ,
161
+ dust : None ,
162
+ force : true ,
163
+ confirmed_only : false ,
164
+ skip_tx_check : true ,
165
+ } ,
166
+ ) . await ?;
139
167
140
168
println ! ( "{}" , serde_json:: to_string_pretty( & result) . unwrap( ) ) ;
141
169
rig. mine_blocks ( 1 , None ) . await ?;
@@ -149,7 +177,7 @@ async fn it_should_only_accept_forced_zero_value_bid_increments_and_revoke(rig:
149
177
150
178
assert_eq ! ( bob_spaces. len( ) - 1 , bob_spaces_updated. len( ) , "bob must have one less space" ) ;
151
179
assert_eq ! ( bob_balance_updated. balance, bob_balance. balance +
152
- Amount :: from_sat( last_bid. to_sat( ) + 662 ) , "alice must be refunded this exact amount" ) ;
180
+ Amount :: from_sat( last_bid. to_sat( ) + 662 ) , "alice must be refunded this exact amount" ) ;
153
181
assert_eq ! ( eve_spaces_updated. len( ) , eve_spaces. len( ) , "eve must have the same number of spaces" ) ;
154
182
155
183
let fullspaceout = rig. spaced . client . get_space ( TEST_SPACE ) . await ?;
@@ -283,7 +311,7 @@ async fn it_should_allow_applying_script_in_batch(rig: &TestRig) -> anyhow::Resu
283
311
all_spaces_2. iter ( ) . for_each ( |s| {
284
312
let space = s. space . as_ref ( ) . expect ( "space" ) ;
285
313
match & space. covenant {
286
- Covenant :: Transfer { expire_height, data} => {
314
+ Covenant :: Transfer { expire_height, data } => {
287
315
count += 1 ;
288
316
assert_eq ! ( * expire_height, expected_expire_height, "must refresh expire height" ) ;
289
317
assert ! ( data. is_some( ) , "must be data set" ) ;
@@ -298,6 +326,150 @@ async fn it_should_allow_applying_script_in_batch(rig: &TestRig) -> anyhow::Resu
298
326
Ok ( ( ) )
299
327
}
300
328
329
+
330
+
331
+ // Alice places an unconfirmed bid on @test2.
332
+ // Bob attempts to replace it but fails due to a lack of confirmed bid & funding utxos.
333
+ // Eve, with confirmed bid outputs/funds, successfully replaces the bid.
334
+ async fn it_should_replace_mempool_bids ( rig : & TestRig ) -> anyhow:: Result < ( ) > {
335
+ // create some confirmed bid outs for Eve
336
+ rig. spaced . client . wallet_send_request (
337
+ EVE ,
338
+ RpcWalletTxBuilder {
339
+ bidouts : Some ( 2 ) ,
340
+ requests : vec ! [ ] ,
341
+ fee_rate : Some ( FeeRate :: from_sat_per_vb ( 2 ) . expect ( "fee" ) ) ,
342
+ dust : None ,
343
+ force : false ,
344
+ confirmed_only : false ,
345
+ skip_tx_check : false ,
346
+ } ,
347
+ ) . await ?;
348
+ rig. mine_blocks ( 1 , None ) . await ?;
349
+
350
+ rig. wait_until_wallet_synced ( ALICE ) . await ?;
351
+ rig. wait_until_wallet_synced ( BOB ) . await ?;
352
+ rig. wait_until_wallet_synced ( EVE ) . await ?;
353
+
354
+ let response = wallet_do ( rig, ALICE , vec ! [
355
+ RpcWalletRequest :: Bid ( BidParams {
356
+ name: "@test2" . to_string( ) ,
357
+ amount: 1000 ,
358
+ } ) ] , false ) . await ?;
359
+
360
+ let response = serde_json:: to_string_pretty ( & response) . unwrap ( ) ;
361
+ println ! ( "{}" , response) ;
362
+
363
+ let response = wallet_do ( rig, BOB , vec ! [
364
+ RpcWalletRequest :: Bid ( BidParams {
365
+ name: "@test2" . to_string( ) ,
366
+ amount: 1000 ,
367
+ } ) ] , false ) . await ?;
368
+
369
+ let response = serde_json:: to_string_pretty ( & response) . unwrap ( ) ;
370
+
371
+ println ! ( "{}" , response) ;
372
+
373
+ assert ! ( response. contains( "hint" ) , "should have a hint about replacement errors" ) ;
374
+
375
+ let replacement = rig. spaced . client . wallet_send_request (
376
+ BOB ,
377
+ RpcWalletTxBuilder {
378
+ bidouts : None ,
379
+ requests : vec ! [
380
+ RpcWalletRequest :: Bid ( BidParams {
381
+ name: "@test2" . to_string( ) ,
382
+ amount: 1000 ,
383
+ } ) ] ,
384
+ fee_rate : Some ( FeeRate :: from_sat_per_vb ( 2 ) . expect ( "fee" ) ) ,
385
+ dust : None ,
386
+ force : false ,
387
+ confirmed_only : false ,
388
+ skip_tx_check : false ,
389
+ } ,
390
+ ) . await ?;
391
+
392
+ let response = serde_json:: to_string_pretty ( & replacement) . unwrap ( ) ;
393
+ println ! ( "{}" , response) ;
394
+
395
+ assert ! ( response. contains( "hint" ) , "should have a hint about confirmed only" ) ;
396
+ assert ! ( response. contains( "replacement-adds-unconfirmed" ) , "expected a replacement-adds-unconfirmed in the message" ) ;
397
+
398
+ // now let Eve try a replacement since she has confirmed outputs
399
+ let replacement = rig. spaced . client . wallet_send_request (
400
+ EVE ,
401
+ RpcWalletTxBuilder {
402
+ bidouts : None ,
403
+ requests : vec ! [
404
+ RpcWalletRequest :: Bid ( BidParams {
405
+ name: "@test2" . to_string( ) ,
406
+ amount: 1000 ,
407
+ } ) ] ,
408
+ fee_rate : Some ( FeeRate :: from_sat_per_vb ( 2 ) . expect ( "fee" ) ) ,
409
+ dust : None ,
410
+ force : false ,
411
+ confirmed_only : false ,
412
+ skip_tx_check : false ,
413
+ } ,
414
+ ) . await ?;
415
+
416
+ let response = serde_json:: to_string_pretty ( & replacement) . unwrap ( ) ;
417
+ println ! ( "{}" , response) ;
418
+
419
+ for tx_res in replacement. result {
420
+ assert ! ( tx_res. error. is_none( ) , "Eve should have no problem replacing" )
421
+ }
422
+
423
+ // Alice won't be able to build off other transactions from the double spent bid
424
+ // even when Eve bid gets confirmed. Wallet must remove double spent tx.
425
+ rig. mine_blocks ( 1 , None ) . await ?;
426
+ rig. wait_until_wallet_synced ( ALICE ) . await ?;
427
+ let txs = rig. spaced . client . wallet_list_transactions (
428
+ ALICE ,
429
+ 1000 , 0
430
+ ) . await ?;
431
+ let unconfirmed : Vec < _ > = txs. iter ( ) . filter ( |tx| !tx. confirmed ) . collect ( ) ;
432
+ assert_eq ! ( unconfirmed. len( ) , 0 , "there should be no stuck unconfirmed transactions" ) ;
433
+ Ok ( ( ) )
434
+ }
435
+
436
+ async fn it_should_maintain_locktime_when_fee_bumping ( rig : & TestRig ) -> anyhow:: Result < ( ) > {
437
+ rig. wait_until_wallet_synced ( ALICE ) . await ?;
438
+
439
+ let response = rig. spaced . client . wallet_send_request (
440
+ ALICE ,
441
+ RpcWalletTxBuilder {
442
+ bidouts : Some ( 2 ) ,
443
+ requests : vec ! [ ] ,
444
+ fee_rate : Some ( FeeRate :: from_sat_per_vb ( 1 ) . expect ( "fee" ) ) ,
445
+ dust : None ,
446
+ force : false ,
447
+ confirmed_only : false ,
448
+ skip_tx_check : false
449
+ } ,
450
+ ) . await ?;
451
+
452
+ println ! ( "{}" , serde_json:: to_string_pretty( & response) . unwrap( ) ) ;
453
+
454
+ let txid = response. result [ 0 ] . txid ;
455
+ for tx_res in response. result {
456
+ assert ! ( tx_res. error. is_none( ) , "should not be error" ) ;
457
+ }
458
+
459
+ let tx = rig. get_raw_transaction ( & txid) . await ?;
460
+
461
+ let bump = rig. spaced . client . wallet_bump_fee (
462
+ ALICE , txid, FeeRate :: from_sat_per_vb ( 4 ) . expect ( "fee" ) , false
463
+ ) . await ?;
464
+ assert_eq ! ( bump. len( ) , 1 , "should only be 1 tx" ) ;
465
+ assert ! ( bump[ 0 ] . error. is_none( ) , "should be no errors" ) ;
466
+
467
+ let replacement = rig. get_raw_transaction ( & bump[ 0 ] . txid ) . await ?;
468
+
469
+ assert_eq ! ( tx. lock_time, replacement. lock_time, "locktimes must not change" ) ;
470
+ Ok ( ( ) )
471
+ }
472
+
301
473
#[ tokio:: test]
302
474
async fn run_auction_tests ( ) -> anyhow:: Result < ( ) > {
303
475
let rig = TestRig :: new_with_regtest_preset ( ) . await ?;
@@ -315,10 +487,10 @@ async fn run_auction_tests() -> anyhow::Result<()> {
315
487
it_should_allow_outbidding ( & rig) . await ?;
316
488
it_should_only_accept_forced_zero_value_bid_increments_and_revoke ( & rig) . await ?;
317
489
it_should_allow_claim_on_or_after_claim_height ( & rig) . await ?;
318
-
319
- // batch tests
320
490
it_should_allow_batch_transfers_refreshing_expire_height ( & rig) . await ?;
321
491
it_should_allow_applying_script_in_batch ( & rig) . await ?;
492
+ it_should_replace_mempool_bids ( & rig) . await ?;
493
+ it_should_maintain_locktime_when_fee_bumping ( & rig) . await ?;
322
494
323
495
Ok ( ( ) )
324
496
}
@@ -333,6 +505,7 @@ async fn wallet_do(rig: &TestRig, wallet: &str, requests: Vec<RpcWalletRequest>,
333
505
dust : None ,
334
506
force,
335
507
confirmed_only : false ,
508
+ skip_tx_check : false ,
336
509
} ,
337
510
) . await ?;
338
511
Ok ( res)
0 commit comments