From b0cdf1747c056331f779cd43fceb3f088572cb7d Mon Sep 17 00:00:00 2001 From: codebestia Date: Wed, 24 Dec 2025 14:14:24 +0100 Subject: [PATCH 1/6] test: (integration) alice locks wrong xmr amount --- swap/tests/bob_xmr_validation.rs | 81 ++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 swap/tests/bob_xmr_validation.rs diff --git a/swap/tests/bob_xmr_validation.rs b/swap/tests/bob_xmr_validation.rs new file mode 100644 index 000000000..c61c32d63 --- /dev/null +++ b/swap/tests/bob_xmr_validation.rs @@ -0,0 +1,81 @@ +pub mod harness; + +use harness::FastCancelConfig; +use swap::asb::FixedRate; +use swap::protocol::alice::AliceState; +use swap::protocol::bob::BobState; +use swap::protocol::{alice, bob}; +use swap_core::monero::Amount; + +use crate::harness::SlowCancelConfig; + +#[tokio::test] +async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { + harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + // Run Bob until he gives up on the swap + let (bob_swap, bob_join_handle) = ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { + matches!(s, BobState::WaitingForCancelTimelockExpiration { .. }) + })); + + // Run until Alice detects the Bitcoin as having been locked + let alice_swap = ctx.alice_next_swap().await; + + let alice_state = alice::run_until( + alice_swap, + |s| matches!(s, AliceState::BtcLocked { .. }), + FixedRate::default(), + ) + .await?; + + // Resume Alice such that she locks the wrong amount of Monero + ctx.restart_alice().await; + let mut alice_swap = ctx.alice_next_swap().await; + + // Modify Alice such that she locks the wrong amount of Monero + alice_swap.state = alice_state.lower_by_one_piconero(); + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + // Run until Bob detects the wrong amount of Monero and gives up + // He gives up by waiting for the cancel timelock to expire + let bob_state = bob_swap.await??; + assert!(matches!(bob_state, BobState::WaitingForCancelTimelockExpiration { .. })); + + // Resume Bob and wait run until completion + let (bob_swap, _) = ctx + .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) + .await; + let bob_state = bob::run_until(bob_swap, |s| { + matches!(s, BobState::BtcRefunded(..)) + }) + .await?; + + // Assert that both Bob and Alice refunded + ctx.assert_bob_refunded(bob_state).await; + let alice_state = alice_swap.await??; + ctx.assert_alice_refunded(alice_state).await; + + Ok(()) + }) + .await; +} + +trait FakeModifyMoneroAmount { + /// Reduces the Monero amount by one piconero + fn lower_by_one_piconero(self) -> Self; +} + +impl FakeModifyMoneroAmount for AliceState { + fn lower_by_one_piconero(self) -> Self { + let AliceState::BtcLocked { mut state3 } = self else { + panic!("Expected BtcLocked state to be able to modify the Monero amount"); + }; + + let one_piconero = Amount::from_piconero(1); + + state3.xmr = state3.xmr.checked_sub(one_piconero).unwrap(); + + Self::BtcLocked { state3 } + } +} \ No newline at end of file From b9e3e2d18d90b249b68fb369e40e866f6012f986 Mon Sep 17 00:00:00 2001 From: codebestia Date: Wed, 24 Dec 2025 14:26:12 +0100 Subject: [PATCH 2/6] test: (integration) correct xmr amount bob redeems --- swap/tests/bob_xmr_validation.rs | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/swap/tests/bob_xmr_validation.rs b/swap/tests/bob_xmr_validation.rs index c61c32d63..9c2020cb5 100644 --- a/swap/tests/bob_xmr_validation.rs +++ b/swap/tests/bob_xmr_validation.rs @@ -61,6 +61,52 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { .await; } +#[tokio::test] +async fn given_correct_xmr_amount_bob_redeems_btc() { + harness::setup_test(FastCancelConfig, None, |mut ctx| async move { + // Run both parties through the happy path + let (bob_swap, bob_join_handle) = ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + + // Run Bob until Bitcoin is locked + let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { + matches!(s, BobState::BtcLocked { .. }) + })); + + // Run Alice until Monero is locked with correct ammount + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run_until( + alice_swap, + |s| matches!(s, AliceState::XmrLocked { .. }), + FixedRate::default(), + )); + + // Bob verifies the correct Monero amount and proceeds to redeem + let bob_state = bob_swap.await??; + assert!(matches!(bob_state, BobState::BtcLocked { .. })); + + // Resume Bob to complete the swap + let (bob_swap, _) = ctx + .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) + .await; + + let bob_state = bob::run_until(bob_swap, |s| { + matches!(s, BobState::XmrRedeemed { .. }) + }) + .await?; + + // Assert Bob successfully redeemed Monero + ctx.assert_bob_redeemed(bob_state).await; + + // Assert Alice successfully redeemed Bitcoin + let alice_state = alice_swap.await??; + ctx.assert_alice_redeemed(alice_state).await; + + Ok(()) + }) + .await; +} + trait FakeModifyMoneroAmount { /// Reduces the Monero amount by one piconero fn lower_by_one_piconero(self) -> Self; From 1919f16ec014e91ba40016a6d08534d9d22d8ed5 Mon Sep 17 00:00:00 2001 From: codebestia Date: Wed, 24 Dec 2025 14:33:11 +0100 Subject: [PATCH 3/6] test: (integration) alice locks significantly wrong amount --- swap/tests/bob_xmr_validation.rs | 57 +++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/swap/tests/bob_xmr_validation.rs b/swap/tests/bob_xmr_validation.rs index 9c2020cb5..f43a74cce 100644 --- a/swap/tests/bob_xmr_validation.rs +++ b/swap/tests/bob_xmr_validation.rs @@ -64,7 +64,6 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { #[tokio::test] async fn given_correct_xmr_amount_bob_redeems_btc() { harness::setup_test(FastCancelConfig, None, |mut ctx| async move { - // Run both parties through the happy path let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; @@ -107,9 +106,58 @@ async fn given_correct_xmr_amount_bob_redeems_btc() { .await; } +#[tokio::test] +async fn given_significantly_wrong_xmr_amount_bob_immediately_aborts() { + harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + let (bob_swap, bob_join_handle) = ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + + let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { + matches!(s, BobState::WaitingForCancelTimelockExpiration { .. }) + })); + + let alice_swap = ctx.alice_next_swap().await; + let alice_state = alice::run_until( + alice_swap, + |s| matches!(s, AliceState::BtcLocked { .. }), + FixedRate::default(), + ) + .await?; + + // Alice locks half the expected amount + ctx.restart_alice().await; + let mut alice_swap = ctx.alice_next_swap().await; + alice_swap.state = alice_state.halve_xmr_amount(); + + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + // Bob immediately detects the problem + let bob_state = bob_swap.await??; + assert!(matches!(bob_state, BobState::WaitingForCancelTimelockExpiration { .. })); + + let (bob_swap, _) = ctx + .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) + .await; + + let bob_state = bob::run_until(bob_swap, |s| { + matches!(s, BobState::BtcRefunded(..)) + }) + .await?; + + ctx.assert_bob_refunded(bob_state).await; + let alice_state = alice_swap.await??; + ctx.assert_alice_refunded(alice_state).await; + + Ok(()) + }) + .await; +} + trait FakeModifyMoneroAmount { /// Reduces the Monero amount by one piconero fn lower_by_one_piconero(self) -> Self; + // Halves the Monero amount + fn halve_xmr_amount(self) -> Self; } impl FakeModifyMoneroAmount for AliceState { @@ -124,4 +172,11 @@ impl FakeModifyMoneroAmount for AliceState { Self::BtcLocked { state3 } } + fn halve_xmr_amount(self) -> Self { + let AliceState::BtcLocked { mut state3 } = self else { + panic!("Expected BtcLocked state to be able to modify the Monero amount"); + }; + state3.xmr = Amount::from_piconero(state3.xmr.as_piconero() / 2); + Self::BtcLocked { state3 } + } } \ No newline at end of file From 545f211bcfa969516597b74ba07a225a7c14d86b Mon Sep 17 00:00:00 2001 From: codebestia Date: Wed, 24 Dec 2025 14:36:09 +0100 Subject: [PATCH 4/6] chore: format test file --- swap/tests/bob_xmr_validation.rs | 66 ++++++++++++++++---------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/swap/tests/bob_xmr_validation.rs b/swap/tests/bob_xmr_validation.rs index f43a74cce..a7dd06dbb 100644 --- a/swap/tests/bob_xmr_validation.rs +++ b/swap/tests/bob_xmr_validation.rs @@ -32,7 +32,7 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { // Resume Alice such that she locks the wrong amount of Monero ctx.restart_alice().await; let mut alice_swap = ctx.alice_next_swap().await; - + // Modify Alice such that she locks the wrong amount of Monero alice_swap.state = alice_state.lower_by_one_piconero(); let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); @@ -40,16 +40,17 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { // Run until Bob detects the wrong amount of Monero and gives up // He gives up by waiting for the cancel timelock to expire let bob_state = bob_swap.await??; - assert!(matches!(bob_state, BobState::WaitingForCancelTimelockExpiration { .. })); + assert!(matches!( + bob_state, + BobState::WaitingForCancelTimelockExpiration { .. } + )); // Resume Bob and wait run until completion let (bob_swap, _) = ctx .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) .await; - let bob_state = bob::run_until(bob_swap, |s| { - matches!(s, BobState::BtcRefunded(..)) - }) - .await?; + let bob_state = + bob::run_until(bob_swap, |s| matches!(s, BobState::BtcRefunded(..))).await?; // Assert that both Bob and Alice refunded ctx.assert_bob_refunded(bob_state).await; @@ -66,12 +67,12 @@ async fn given_correct_xmr_amount_bob_redeems_btc() { harness::setup_test(FastCancelConfig, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; - + // Run Bob until Bitcoin is locked let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { matches!(s, BobState::BtcLocked { .. }) })); - + // Run Alice until Monero is locked with correct ammount let alice_swap = ctx.alice_next_swap().await; let alice_swap = tokio::spawn(alice::run_until( @@ -79,28 +80,26 @@ async fn given_correct_xmr_amount_bob_redeems_btc() { |s| matches!(s, AliceState::XmrLocked { .. }), FixedRate::default(), )); - + // Bob verifies the correct Monero amount and proceeds to redeem let bob_state = bob_swap.await??; assert!(matches!(bob_state, BobState::BtcLocked { .. })); - + // Resume Bob to complete the swap let (bob_swap, _) = ctx .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) .await; - - let bob_state = bob::run_until(bob_swap, |s| { - matches!(s, BobState::XmrRedeemed { .. }) - }) - .await?; - + + let bob_state = + bob::run_until(bob_swap, |s| matches!(s, BobState::XmrRedeemed { .. })).await?; + // Assert Bob successfully redeemed Monero ctx.assert_bob_redeemed(bob_state).await; - + // Assert Alice successfully redeemed Bitcoin let alice_state = alice_swap.await??; ctx.assert_alice_redeemed(alice_state).await; - + Ok(()) }) .await; @@ -111,11 +110,11 @@ async fn given_significantly_wrong_xmr_amount_bob_immediately_aborts() { harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; - + let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { matches!(s, BobState::WaitingForCancelTimelockExpiration { .. }) })); - + let alice_swap = ctx.alice_next_swap().await; let alice_state = alice::run_until( alice_swap, @@ -123,31 +122,32 @@ async fn given_significantly_wrong_xmr_amount_bob_immediately_aborts() { FixedRate::default(), ) .await?; - + // Alice locks half the expected amount ctx.restart_alice().await; let mut alice_swap = ctx.alice_next_swap().await; alice_swap.state = alice_state.halve_xmr_amount(); - + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); - + // Bob immediately detects the problem let bob_state = bob_swap.await??; - assert!(matches!(bob_state, BobState::WaitingForCancelTimelockExpiration { .. })); - + assert!(matches!( + bob_state, + BobState::WaitingForCancelTimelockExpiration { .. } + )); + let (bob_swap, _) = ctx .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) .await; - - let bob_state = bob::run_until(bob_swap, |s| { - matches!(s, BobState::BtcRefunded(..)) - }) - .await?; - + + let bob_state = + bob::run_until(bob_swap, |s| matches!(s, BobState::BtcRefunded(..))).await?; + ctx.assert_bob_refunded(bob_state).await; let alice_state = alice_swap.await??; ctx.assert_alice_refunded(alice_state).await; - + Ok(()) }) .await; @@ -179,4 +179,4 @@ impl FakeModifyMoneroAmount for AliceState { state3.xmr = Amount::from_piconero(state3.xmr.as_piconero() / 2); Self::BtcLocked { state3 } } -} \ No newline at end of file +} From f9ad9d51318219ba711e97f5a6fe9e660fab32f3 Mon Sep 17 00:00:00 2001 From: codebestia Date: Thu, 25 Dec 2025 05:31:36 +0100 Subject: [PATCH 5/6] chore: update test suite --- swap/tests/bob_xmr_validation.rs | 68 ++++++++++++-------------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/swap/tests/bob_xmr_validation.rs b/swap/tests/bob_xmr_validation.rs index a7dd06dbb..8f67383d4 100644 --- a/swap/tests/bob_xmr_validation.rs +++ b/swap/tests/bob_xmr_validation.rs @@ -62,49 +62,6 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { .await; } -#[tokio::test] -async fn given_correct_xmr_amount_bob_redeems_btc() { - harness::setup_test(FastCancelConfig, None, |mut ctx| async move { - let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.id; - - // Run Bob until Bitcoin is locked - let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { - matches!(s, BobState::BtcLocked { .. }) - })); - - // Run Alice until Monero is locked with correct ammount - let alice_swap = ctx.alice_next_swap().await; - let alice_swap = tokio::spawn(alice::run_until( - alice_swap, - |s| matches!(s, AliceState::XmrLocked { .. }), - FixedRate::default(), - )); - - // Bob verifies the correct Monero amount and proceeds to redeem - let bob_state = bob_swap.await??; - assert!(matches!(bob_state, BobState::BtcLocked { .. })); - - // Resume Bob to complete the swap - let (bob_swap, _) = ctx - .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) - .await; - - let bob_state = - bob::run_until(bob_swap, |s| matches!(s, BobState::XmrRedeemed { .. })).await?; - - // Assert Bob successfully redeemed Monero - ctx.assert_bob_redeemed(bob_state).await; - - // Assert Alice successfully redeemed Bitcoin - let alice_state = alice_swap.await??; - ctx.assert_alice_redeemed(alice_state).await; - - Ok(()) - }) - .await; -} - #[tokio::test] async fn given_significantly_wrong_xmr_amount_bob_immediately_aborts() { harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { @@ -153,6 +110,31 @@ async fn given_significantly_wrong_xmr_amount_bob_immediately_aborts() { .await; } +#[tokio::test] +async fn given_correct_xmr_amount_bob_redeems_btc() { + harness::setup_test(FastCancelConfig, None, |mut ctx| async move { + let (bob_swap, _bob_join_handle) = ctx.bob_swap().await; + + // Run both to completion without restart + let bob_swap = tokio::spawn(bob::run(bob_swap)); + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + // Wait for both + let (bob_result, alice_result) = tokio::join!(bob_swap, alice_swap); + + let bob_state = bob_result??; + let alice_state = alice_result??; + + // Verify final states + assert!(matches!(bob_state, BobState::XmrRedeemed { .. })); + ctx.assert_alice_redeemed(alice_state).await; + + Ok(()) + }) + .await; +} + trait FakeModifyMoneroAmount { /// Reduces the Monero amount by one piconero fn lower_by_one_piconero(self) -> Self; From 2189f1723e91f2a241e8197bb0378c6501245e99 Mon Sep 17 00:00:00 2001 From: codebestia Date: Mon, 29 Dec 2025 01:41:30 +0100 Subject: [PATCH 6/6] test: improve test and update test workflow --- .github/workflows/ci.yml | 2 + swap/tests/bob_xmr_validation.rs | 88 ++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97649afc2..83421282b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,6 +182,8 @@ jobs: test_name: alice_broken_wallet_rpc_after_started_btc_early_refund - package: swap test_name: happy_path_alice_does_not_send_transfer_proof + - package: swap + test_name: bob_xmr_validation - package: monero-tests test_name: reserve_proof - package: monero-tests diff --git a/swap/tests/bob_xmr_validation.rs b/swap/tests/bob_xmr_validation.rs index 8f67383d4..7ab3d235b 100644 --- a/swap/tests/bob_xmr_validation.rs +++ b/swap/tests/bob_xmr_validation.rs @@ -1,6 +1,5 @@ pub mod harness; -use harness::FastCancelConfig; use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::bob::BobState; @@ -12,11 +11,11 @@ use crate::harness::SlowCancelConfig; #[tokio::test] async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { - // Run Bob until he gives up on the swap + // Run Bob until he finds a swap candidate let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { - matches!(s, BobState::WaitingForCancelTimelockExpiration { .. }) + matches!(s, BobState::XmrLockTransactionCandidate { .. }) })); // Run until Alice detects the Bitcoin as having been locked @@ -29,6 +28,7 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { ) .await?; + // Resume Alice such that she locks the wrong amount of Monero ctx.restart_alice().await; let mut alice_swap = ctx.alice_next_swap().await; @@ -37,16 +37,30 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { alice_swap.state = alice_state.lower_by_one_piconero(); let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); - // Run until Bob detects the wrong amount of Monero and gives up - // He gives up by waiting for the cancel timelock to expire + // Assert that Bob recognises Alice as a swap candidate let bob_state = bob_swap.await??; + assert!(matches!( + bob_state, + BobState::XmrLockTransactionCandidate { .. } + )); + + // Resume Bob and wait run until Bob detects the problem + let (bob_swap, bob_join_handle) = ctx + .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) + .await; + let bob_state = + bob::run_until(bob_swap, + |s| matches!(s, BobState::WaitingForCancelTimelockExpiration { .. }) + ).await?; + + // Assert that Bob detects the problem assert!(matches!( bob_state, BobState::WaitingForCancelTimelockExpiration { .. } )); - // Resume Bob and wait run until completion - let (bob_swap, _) = ctx + // Resume Bob and wait till completion + let (bob_swap, bob_join_handle) = ctx .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) .await; let bob_state = @@ -57,6 +71,7 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { let alice_state = alice_swap.await??; ctx.assert_alice_refunded(alice_state).await; + Ok(()) }) .await; @@ -65,14 +80,16 @@ async fn given_alice_locks_wrong_xmr_amount_bob_rejects() { #[tokio::test] async fn given_significantly_wrong_xmr_amount_bob_immediately_aborts() { harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + // Run Bob until he finds a swap candidate let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; - let bob_swap = tokio::spawn(bob::run_until(bob_swap, |s| { - matches!(s, BobState::WaitingForCancelTimelockExpiration { .. }) + matches!(s, BobState::XmrLockTransactionCandidate { .. }) })); + // Run until Alice detects the Bitcoin as having been locked let alice_swap = ctx.alice_next_swap().await; + let alice_state = alice::run_until( alice_swap, |s| matches!(s, AliceState::BtcLocked { .. }), @@ -80,55 +97,49 @@ async fn given_significantly_wrong_xmr_amount_bob_immediately_aborts() { ) .await?; - // Alice locks half the expected amount + + // Resume Alice such that she locks half the amount ctx.restart_alice().await; let mut alice_swap = ctx.alice_next_swap().await; - alice_swap.state = alice_state.halve_xmr_amount(); + // Modify Alice such that she locks half the amount + alice_swap.state = alice_state.lower_by_one_piconero(); let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); - // Bob immediately detects the problem + // Assert that Bob recognises Alice as a swap candidate let bob_state = bob_swap.await??; assert!(matches!( bob_state, - BobState::WaitingForCancelTimelockExpiration { .. } + BobState::XmrLockTransactionCandidate { .. } )); - let (bob_swap, _) = ctx + // Resume Bob and wait run until Bob detects the problem + let (bob_swap, bob_join_handle) = ctx .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) .await; + let bob_state = + bob::run_until(bob_swap, + |s| matches!(s, BobState::WaitingForCancelTimelockExpiration { .. }) + ).await?; + + // Assert that Bob detects the problem + assert!(matches!( + bob_state, + BobState::WaitingForCancelTimelockExpiration { .. } + )); + // Resume Bob and wait till completion + let (bob_swap, bob_join_handle) = ctx + .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) + .await; let bob_state = bob::run_until(bob_swap, |s| matches!(s, BobState::BtcRefunded(..))).await?; + // Assert that both Bob and Alice refunded ctx.assert_bob_refunded(bob_state).await; let alice_state = alice_swap.await??; ctx.assert_alice_refunded(alice_state).await; - Ok(()) - }) - .await; -} - -#[tokio::test] -async fn given_correct_xmr_amount_bob_redeems_btc() { - harness::setup_test(FastCancelConfig, None, |mut ctx| async move { - let (bob_swap, _bob_join_handle) = ctx.bob_swap().await; - - // Run both to completion without restart - let bob_swap = tokio::spawn(bob::run(bob_swap)); - let alice_swap = ctx.alice_next_swap().await; - let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); - - // Wait for both - let (bob_result, alice_result) = tokio::join!(bob_swap, alice_swap); - - let bob_state = bob_result??; - let alice_state = alice_result??; - - // Verify final states - assert!(matches!(bob_state, BobState::XmrRedeemed { .. })); - ctx.assert_alice_redeemed(alice_state).await; Ok(()) }) @@ -154,6 +165,7 @@ impl FakeModifyMoneroAmount for AliceState { Self::BtcLocked { state3 } } + fn halve_xmr_amount(self) -> Self { let AliceState::BtcLocked { mut state3 } = self else { panic!("Expected BtcLocked state to be able to modify the Monero amount");