mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-03-23 06:36:47 -04:00
385 lines
13 KiB
Rust
385 lines
13 KiB
Rust
![]() |
#![warn(
|
||
|
unused_extern_crates,
|
||
|
missing_debug_implementations,
|
||
|
missing_copy_implementations,
|
||
|
rust_2018_idioms,
|
||
|
clippy::cast_possible_truncation,
|
||
|
clippy::cast_sign_loss,
|
||
|
clippy::fallible_impl_from,
|
||
|
clippy::cast_precision_loss,
|
||
|
clippy::cast_possible_wrap,
|
||
|
clippy::dbg_macro
|
||
|
)]
|
||
|
#![cfg_attr(not(test), warn(clippy::unwrap_used))]
|
||
|
#![forbid(unsafe_code)]
|
||
|
#![allow(non_snake_case)]
|
||
|
|
||
|
pub mod alice;
|
||
|
pub mod bitcoin;
|
||
|
pub mod bob;
|
||
|
pub mod monero;
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use crate::{
|
||
|
alice, bitcoin,
|
||
|
bitcoin::{Amount, TX_FEE},
|
||
|
bob, monero,
|
||
|
};
|
||
|
use bitcoin_harness::Bitcoind;
|
||
|
use monero_harness::Monero;
|
||
|
use rand::rngs::OsRng;
|
||
|
use testcontainers::clients::Cli;
|
||
|
|
||
|
const TEN_XMR: u64 = 10_000_000_000_000;
|
||
|
|
||
|
pub async fn init_bitcoind(tc_client: &Cli) -> Bitcoind<'_> {
|
||
|
let bitcoind = Bitcoind::new(tc_client, "0.19.1").expect("failed to create bitcoind");
|
||
|
let _ = bitcoind.init(5).await;
|
||
|
|
||
|
bitcoind
|
||
|
}
|
||
|
|
||
|
#[tokio::test]
|
||
|
async fn happy_path() {
|
||
|
let cli = Cli::default();
|
||
|
let monero = Monero::new(&cli);
|
||
|
let bitcoind = init_bitcoind(&cli).await;
|
||
|
|
||
|
// must be bigger than our hardcoded fee of 10_000
|
||
|
let btc_amount = bitcoin::Amount::from_sat(10_000_000);
|
||
|
let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000);
|
||
|
|
||
|
let fund_alice = TEN_XMR;
|
||
|
let fund_bob = 0;
|
||
|
monero.init(fund_alice, fund_bob).await.unwrap();
|
||
|
|
||
|
let alice_monero_wallet = monero::AliceWallet(&monero);
|
||
|
let bob_monero_wallet = monero::BobWallet(&monero);
|
||
|
|
||
|
let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
||
|
let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
||
|
|
||
|
let alice_initial_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap();
|
||
|
let bob_initial_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
||
|
|
||
|
let redeem_address = alice_btc_wallet.new_address().await.unwrap();
|
||
|
let punish_address = redeem_address.clone();
|
||
|
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
||
|
|
||
|
let refund_timelock = 1;
|
||
|
let punish_timelock = 1;
|
||
|
|
||
|
let alice_state0 = alice::State0::new(
|
||
|
&mut OsRng,
|
||
|
btc_amount,
|
||
|
xmr_amount,
|
||
|
refund_timelock,
|
||
|
punish_timelock,
|
||
|
redeem_address,
|
||
|
punish_address,
|
||
|
);
|
||
|
let bob_state0 = bob::State0::new(
|
||
|
&mut OsRng,
|
||
|
btc_amount,
|
||
|
xmr_amount,
|
||
|
refund_timelock,
|
||
|
punish_timelock,
|
||
|
refund_address.clone(),
|
||
|
);
|
||
|
|
||
|
let alice_message0 = alice_state0.next_message(&mut OsRng);
|
||
|
let bob_message0 = bob_state0.next_message(&mut OsRng);
|
||
|
|
||
|
let alice_state1 = alice_state0.receive(bob_message0).unwrap();
|
||
|
let bob_state1 = bob_state0
|
||
|
.receive(&bob_btc_wallet, alice_message0)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let bob_message1 = bob_state1.next_message();
|
||
|
let alice_state2 = alice_state1.receive(bob_message1);
|
||
|
let alice_message1 = alice_state2.next_message();
|
||
|
let bob_state2 = bob_state1.receive(alice_message1).unwrap();
|
||
|
|
||
|
let bob_message2 = bob_state2.next_message();
|
||
|
let alice_state3 = alice_state2.receive(bob_message2).unwrap();
|
||
|
|
||
|
let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap();
|
||
|
let lock_txid = bob_state2b.tx_lock_id();
|
||
|
|
||
|
let alice_state4 = alice_state3
|
||
|
.watch_for_lock_btc(&alice_btc_wallet)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let (alice_state4b, lock_tx_monero_fee) =
|
||
|
alice_state4.lock_xmr(&alice_monero_wallet).await.unwrap();
|
||
|
|
||
|
let alice_message2 = alice_state4b.next_message();
|
||
|
|
||
|
let bob_state3 = bob_state2b
|
||
|
.watch_for_lock_xmr(&bob_monero_wallet, alice_message2)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let bob_message3 = bob_state3.next_message();
|
||
|
let alice_state5 = alice_state4b.receive(bob_message3);
|
||
|
|
||
|
alice_state5.redeem_btc(&alice_btc_wallet).await.unwrap();
|
||
|
let bob_state4 = bob_state3
|
||
|
.watch_for_redeem_btc(&bob_btc_wallet)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
bob_state4.claim_xmr(&bob_monero_wallet).await.unwrap();
|
||
|
|
||
|
let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
||
|
let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
||
|
|
||
|
let lock_tx_bitcoin_fee = bob_btc_wallet.transaction_fee(lock_txid).await.unwrap();
|
||
|
|
||
|
assert_eq!(
|
||
|
alice_final_btc_balance,
|
||
|
alice_initial_btc_balance + btc_amount - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
||
|
);
|
||
|
assert_eq!(
|
||
|
bob_final_btc_balance,
|
||
|
bob_initial_btc_balance - btc_amount - lock_tx_bitcoin_fee
|
||
|
);
|
||
|
|
||
|
let alice_final_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap();
|
||
|
bob_monero_wallet
|
||
|
.0
|
||
|
.wait_for_bob_wallet_block_height()
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let bob_final_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
||
|
|
||
|
assert_eq!(
|
||
|
alice_final_xmr_balance,
|
||
|
alice_initial_xmr_balance - u64::from(xmr_amount) - u64::from(lock_tx_monero_fee)
|
||
|
);
|
||
|
assert_eq!(
|
||
|
bob_final_xmr_balance,
|
||
|
bob_initial_xmr_balance + u64::from(xmr_amount)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[tokio::test]
|
||
|
async fn both_refund() {
|
||
|
let cli = Cli::default();
|
||
|
let monero = Monero::new(&cli);
|
||
|
let bitcoind = init_bitcoind(&cli).await;
|
||
|
|
||
|
// must be bigger than our hardcoded fee of 10_000
|
||
|
let btc_amount = bitcoin::Amount::from_sat(10_000_000);
|
||
|
let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000);
|
||
|
|
||
|
let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let fund_alice = TEN_XMR;
|
||
|
let fund_bob = 0;
|
||
|
|
||
|
monero.init(fund_alice, fund_bob).await.unwrap();
|
||
|
let alice_monero_wallet = monero::AliceWallet(&monero);
|
||
|
let bob_monero_wallet = monero::BobWallet(&monero);
|
||
|
|
||
|
let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
||
|
let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
||
|
|
||
|
let bob_initial_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
||
|
|
||
|
let redeem_address = alice_btc_wallet.new_address().await.unwrap();
|
||
|
let punish_address = redeem_address.clone();
|
||
|
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
||
|
|
||
|
let refund_timelock = 1;
|
||
|
let punish_timelock = 1;
|
||
|
|
||
|
let alice_state0 = alice::State0::new(
|
||
|
&mut OsRng,
|
||
|
btc_amount,
|
||
|
xmr_amount,
|
||
|
refund_timelock,
|
||
|
punish_timelock,
|
||
|
redeem_address,
|
||
|
punish_address,
|
||
|
);
|
||
|
let bob_state0 = bob::State0::new(
|
||
|
&mut OsRng,
|
||
|
btc_amount,
|
||
|
xmr_amount,
|
||
|
refund_timelock,
|
||
|
punish_timelock,
|
||
|
refund_address.clone(),
|
||
|
);
|
||
|
|
||
|
let alice_message0 = alice_state0.next_message(&mut OsRng);
|
||
|
let bob_message0 = bob_state0.next_message(&mut OsRng);
|
||
|
|
||
|
let alice_state1 = alice_state0.receive(bob_message0).unwrap();
|
||
|
let bob_state1 = bob_state0
|
||
|
.receive(&bob_btc_wallet, alice_message0)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let bob_message1 = bob_state1.next_message();
|
||
|
let alice_state2 = alice_state1.receive(bob_message1);
|
||
|
let alice_message1 = alice_state2.next_message();
|
||
|
let bob_state2 = bob_state1.receive(alice_message1).unwrap();
|
||
|
|
||
|
let bob_message2 = bob_state2.next_message();
|
||
|
let alice_state3 = alice_state2.receive(bob_message2).unwrap();
|
||
|
|
||
|
let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap();
|
||
|
|
||
|
let alice_state4 = alice_state3
|
||
|
.watch_for_lock_btc(&alice_btc_wallet)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let (alice_state4b, _lock_tx_monero_fee) =
|
||
|
alice_state4.lock_xmr(&alice_monero_wallet).await.unwrap();
|
||
|
|
||
|
bob_state2b.refund_btc(&bob_btc_wallet).await.unwrap();
|
||
|
|
||
|
alice_state4b
|
||
|
.refund_xmr(&alice_btc_wallet, &alice_monero_wallet)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
||
|
let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
||
|
|
||
|
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
||
|
// to TX_FEE
|
||
|
let lock_tx_bitcoin_fee = bob_btc_wallet
|
||
|
.transaction_fee(bob_state2b.tx_lock_id())
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
assert_eq!(alice_final_btc_balance, alice_initial_btc_balance);
|
||
|
assert_eq!(
|
||
|
bob_final_btc_balance,
|
||
|
// The 2 * TX_FEE corresponds to tx_refund and tx_cancel.
|
||
|
bob_initial_btc_balance - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee
|
||
|
);
|
||
|
|
||
|
alice_monero_wallet
|
||
|
.0
|
||
|
.wait_for_alice_wallet_block_height()
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let alice_final_xmr_balance = alice_monero_wallet.0.get_balance_alice().await.unwrap();
|
||
|
let bob_final_xmr_balance = bob_monero_wallet.0.get_balance_bob().await.unwrap();
|
||
|
|
||
|
// Because we create a new wallet when claiming Monero, we can only assert on
|
||
|
// this new wallet owning all of `xmr_amount` after refund
|
||
|
assert_eq!(alice_final_xmr_balance, u64::from(xmr_amount));
|
||
|
assert_eq!(bob_final_xmr_balance, bob_initial_xmr_balance);
|
||
|
}
|
||
|
|
||
|
#[tokio::test]
|
||
|
async fn alice_punishes() {
|
||
|
let cli = Cli::default();
|
||
|
let bitcoind = init_bitcoind(&cli).await;
|
||
|
|
||
|
// must be bigger than our hardcoded fee of 10_000
|
||
|
let btc_amount = bitcoin::Amount::from_sat(10_000_000);
|
||
|
let xmr_amount = monero::Amount::from_piconero(1_000_000_000_000);
|
||
|
|
||
|
let alice_btc_wallet = bitcoin::Wallet::new("alice", &bitcoind.node_url)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
let bob_btc_wallet = bitcoin::make_wallet("bob", &bitcoind, btc_amount)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let alice_initial_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
||
|
let bob_initial_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
||
|
|
||
|
let redeem_address = alice_btc_wallet.new_address().await.unwrap();
|
||
|
let punish_address = redeem_address.clone();
|
||
|
let refund_address = bob_btc_wallet.new_address().await.unwrap();
|
||
|
|
||
|
let refund_timelock = 1;
|
||
|
let punish_timelock = 1;
|
||
|
|
||
|
let alice_state0 = alice::State0::new(
|
||
|
&mut OsRng,
|
||
|
btc_amount,
|
||
|
xmr_amount,
|
||
|
refund_timelock,
|
||
|
punish_timelock,
|
||
|
redeem_address,
|
||
|
punish_address,
|
||
|
);
|
||
|
let bob_state0 = bob::State0::new(
|
||
|
&mut OsRng,
|
||
|
btc_amount,
|
||
|
xmr_amount,
|
||
|
refund_timelock,
|
||
|
punish_timelock,
|
||
|
refund_address.clone(),
|
||
|
);
|
||
|
|
||
|
let alice_message0 = alice_state0.next_message(&mut OsRng);
|
||
|
let bob_message0 = bob_state0.next_message(&mut OsRng);
|
||
|
|
||
|
let alice_state1 = alice_state0.receive(bob_message0).unwrap();
|
||
|
let bob_state1 = bob_state0
|
||
|
.receive(&bob_btc_wallet, alice_message0)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
let bob_message1 = bob_state1.next_message();
|
||
|
let alice_state2 = alice_state1.receive(bob_message1);
|
||
|
let alice_message1 = alice_state2.next_message();
|
||
|
let bob_state2 = bob_state1.receive(alice_message1).unwrap();
|
||
|
|
||
|
let bob_message2 = bob_state2.next_message();
|
||
|
let alice_state3 = alice_state2.receive(bob_message2).unwrap();
|
||
|
|
||
|
let bob_state2b = bob_state2.lock_btc(&bob_btc_wallet).await.unwrap();
|
||
|
|
||
|
let alice_state4 = alice_state3
|
||
|
.watch_for_lock_btc(&alice_btc_wallet)
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
alice_state4.punish(&alice_btc_wallet).await.unwrap();
|
||
|
|
||
|
let alice_final_btc_balance = alice_btc_wallet.balance().await.unwrap();
|
||
|
let bob_final_btc_balance = bob_btc_wallet.balance().await.unwrap();
|
||
|
|
||
|
// lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
|
||
|
// to TX_FEE
|
||
|
let lock_tx_bitcoin_fee = bob_btc_wallet
|
||
|
.transaction_fee(bob_state2b.tx_lock_id())
|
||
|
.await
|
||
|
.unwrap();
|
||
|
|
||
|
assert_eq!(
|
||
|
alice_final_btc_balance,
|
||
|
alice_initial_btc_balance + btc_amount - Amount::from_sat(2 * TX_FEE)
|
||
|
);
|
||
|
assert_eq!(
|
||
|
bob_final_btc_balance,
|
||
|
bob_initial_btc_balance - btc_amount - lock_tx_bitcoin_fee
|
||
|
);
|
||
|
}
|
||
|
}
|