mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-29 11:47:28 -04:00
Network protocol tests for spot_price behaviour
Each test spawns swarm for Alice and Bob that only contains the spot_price behaviours and uses a memory transport. Tests cover happy path (i.e. expected price is returned) and error scenarios. Implementation of `TestRate` on `LatestRate` allows testing rate fetch error and quote calculation error behaviour. Thanks to @thomaseizinger for ramping up the test framework for comit-rs in the past!
This commit is contained in:
parent
03a0dc73cd
commit
89b3d07eba
5 changed files with 552 additions and 3 deletions
|
@ -10,3 +10,6 @@ pub mod swarm;
|
||||||
pub mod tor_transport;
|
pub mod tor_transport;
|
||||||
pub mod transfer_proof;
|
pub mod transfer_proof;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test"))]
|
||||||
|
pub mod test;
|
||||||
|
|
162
swap/src/network/test.rs
Normal file
162
swap/src/network/test.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use futures::future;
|
||||||
|
use libp2p::core::muxing::StreamMuxerBox;
|
||||||
|
use libp2p::core::transport::memory::MemoryTransport;
|
||||||
|
use libp2p::core::upgrade::{SelectUpgrade, Version};
|
||||||
|
use libp2p::core::{Executor, Multiaddr};
|
||||||
|
use libp2p::mplex::MplexConfig;
|
||||||
|
use libp2p::noise::{self, NoiseConfig, X25519Spec};
|
||||||
|
use libp2p::swarm::{
|
||||||
|
IntoProtocolsHandler, NetworkBehaviour, ProtocolsHandler, SwarmBuilder, SwarmEvent,
|
||||||
|
};
|
||||||
|
use libp2p::{identity, yamux, PeerId, Swarm, Transport};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
|
/// An adaptor struct for libp2p that spawns futures into the current
|
||||||
|
/// thread-local runtime.
|
||||||
|
struct GlobalSpawnTokioExecutor;
|
||||||
|
|
||||||
|
impl Executor for GlobalSpawnTokioExecutor {
|
||||||
|
fn exec(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>) {
|
||||||
|
let _ = tokio::spawn(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Actor<B: NetworkBehaviour> {
|
||||||
|
pub swarm: Swarm<B>,
|
||||||
|
pub addr: Multiaddr,
|
||||||
|
pub peer_id: PeerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_connected_swarm_pair<B, F>(behaviour_fn: F) -> (Actor<B>, Actor<B>)
|
||||||
|
where
|
||||||
|
B: NetworkBehaviour,
|
||||||
|
F: Fn(PeerId, identity::Keypair) -> B + Clone,
|
||||||
|
<<<B as NetworkBehaviour>::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::InEvent: Clone,
|
||||||
|
<B as NetworkBehaviour>::OutEvent: Debug{
|
||||||
|
let (swarm, addr, peer_id) = new_swarm(behaviour_fn.clone());
|
||||||
|
let mut alice = Actor {
|
||||||
|
swarm,
|
||||||
|
addr,
|
||||||
|
peer_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (swarm, addr, peer_id) = new_swarm(behaviour_fn);
|
||||||
|
let mut bob = Actor {
|
||||||
|
swarm,
|
||||||
|
addr,
|
||||||
|
peer_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
connect(&mut alice.swarm, &mut bob.swarm).await;
|
||||||
|
|
||||||
|
(alice, bob)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_swarm<B: NetworkBehaviour, F: Fn(PeerId, identity::Keypair) -> B>(
|
||||||
|
behaviour_fn: F,
|
||||||
|
) -> (Swarm<B>, Multiaddr, PeerId)
|
||||||
|
where
|
||||||
|
B: NetworkBehaviour,
|
||||||
|
{
|
||||||
|
let id_keys = identity::Keypair::generate_ed25519();
|
||||||
|
let peer_id = PeerId::from(id_keys.public());
|
||||||
|
|
||||||
|
let dh_keys = noise::Keypair::<X25519Spec>::new()
|
||||||
|
.into_authentic(&id_keys)
|
||||||
|
.expect("failed to create dh_keys");
|
||||||
|
let noise = NoiseConfig::xx(dh_keys).into_authenticated();
|
||||||
|
|
||||||
|
let transport = MemoryTransport::default()
|
||||||
|
.upgrade(Version::V1)
|
||||||
|
.authenticate(noise)
|
||||||
|
.multiplex(SelectUpgrade::new(
|
||||||
|
yamux::YamuxConfig::default(),
|
||||||
|
MplexConfig::new(),
|
||||||
|
))
|
||||||
|
.timeout(Duration::from_secs(5))
|
||||||
|
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
|
||||||
|
.boxed();
|
||||||
|
|
||||||
|
let mut swarm: Swarm<B> = SwarmBuilder::new(transport, behaviour_fn(peer_id, id_keys), peer_id)
|
||||||
|
.executor(Box::new(GlobalSpawnTokioExecutor))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let address_port = rand::random::<u64>();
|
||||||
|
let addr = format!("/memory/{}", address_port)
|
||||||
|
.parse::<Multiaddr>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Swarm::listen_on(&mut swarm, addr.clone()).unwrap();
|
||||||
|
|
||||||
|
(swarm, addr, peer_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn await_events_or_timeout<A, B>(
|
||||||
|
alice_event: impl Future<Output = A>,
|
||||||
|
bob_event: impl Future<Output = B>,
|
||||||
|
) -> (A, B) {
|
||||||
|
time::timeout(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
future::join(alice_event, bob_event),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("network behaviours to emit an event within 10 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connects two swarms with each other.
|
||||||
|
///
|
||||||
|
/// This assumes the transport that is in use can be used by Bob to connect to
|
||||||
|
/// the listen address that is emitted by Alice. In other words, they have to be
|
||||||
|
/// on the same network. The memory transport used by the above `new_swarm`
|
||||||
|
/// function fulfills this.
|
||||||
|
///
|
||||||
|
/// We also assume that the swarms don't emit any behaviour events during the
|
||||||
|
/// connection phase. Any event emitted is considered a bug from this functions
|
||||||
|
/// PoV because they would be lost.
|
||||||
|
pub async fn connect<BA, BB>(alice: &mut Swarm<BA>, bob: &mut Swarm<BB>)
|
||||||
|
where
|
||||||
|
BA: NetworkBehaviour,
|
||||||
|
BB: NetworkBehaviour,
|
||||||
|
<BA as NetworkBehaviour>::OutEvent: Debug,
|
||||||
|
<BB as NetworkBehaviour>::OutEvent: Debug,
|
||||||
|
{
|
||||||
|
let mut alice_connected = false;
|
||||||
|
let mut bob_connected = false;
|
||||||
|
|
||||||
|
while !alice_connected && !bob_connected {
|
||||||
|
let (alice_event, bob_event) = future::join(alice.next_event(), bob.next_event()).await;
|
||||||
|
|
||||||
|
match alice_event {
|
||||||
|
SwarmEvent::ConnectionEstablished { .. } => {
|
||||||
|
alice_connected = true;
|
||||||
|
}
|
||||||
|
SwarmEvent::NewListenAddr(addr) => {
|
||||||
|
bob.dial_addr(addr).unwrap();
|
||||||
|
}
|
||||||
|
SwarmEvent::Behaviour(event) => {
|
||||||
|
panic!(
|
||||||
|
"alice unexpectedly emitted a behaviour event during connection: {:?}",
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
match bob_event {
|
||||||
|
SwarmEvent::ConnectionEstablished { .. } => {
|
||||||
|
bob_connected = true;
|
||||||
|
}
|
||||||
|
SwarmEvent::Behaviour(event) => {
|
||||||
|
panic!(
|
||||||
|
"bob unexpectedly emitted a behaviour event during connection: {:?}",
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ use std::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum OutEvent {
|
pub enum OutEvent {
|
||||||
ExecutionSetupParams {
|
ExecutionSetupParams {
|
||||||
peer: PeerId,
|
peer: PeerId,
|
||||||
|
@ -244,3 +245,386 @@ impl Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::asb::Rate;
|
||||||
|
use crate::monero;
|
||||||
|
use crate::network::test::{await_events_or_timeout, connect, new_swarm};
|
||||||
|
use crate::protocol::{alice, bob};
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use libp2p::Swarm;
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
impl Default for AliceBehaviourValues {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
balance: monero::Amount::from_monero(1.0).unwrap(),
|
||||||
|
lock_fee: monero::Amount::ZERO,
|
||||||
|
max_buy: bitcoin::Amount::from_btc(0.01).unwrap(),
|
||||||
|
rate: TestRate::default(), // 0.01
|
||||||
|
resume_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_alice_has_sufficient_balance_then_returns_price() {
|
||||||
|
let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_alice_has_insufficient_balance_then_returns_error() {
|
||||||
|
let mut test = SpotPriceTest::setup(
|
||||||
|
AliceBehaviourValues::default().with_balance(monero::Amount::ZERO),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_error(
|
||||||
|
alice::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
|
||||||
|
bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_alice_has_insufficient_balance_after_balance_update_then_returns_error() {
|
||||||
|
let mut test = SpotPriceTest::setup(AliceBehaviourValues::default()).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
let expected_xmr = monero::Amount::from_monero(1.0).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_price((btc_to_swap, expected_xmr), expected_xmr)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
test.alice_swarm
|
||||||
|
.behaviour_mut()
|
||||||
|
.update_balance(monero::Amount::ZERO);
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_error(
|
||||||
|
alice::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
|
||||||
|
bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_alice_has_insufficient_balance_because_of_lock_fee_then_returns_error() {
|
||||||
|
let mut test = SpotPriceTest::setup(
|
||||||
|
AliceBehaviourValues::default().with_lock_fee(monero::Amount::from_piconero(1)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_error(
|
||||||
|
alice::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
|
||||||
|
bob::spot_price::Error::BalanceTooLow { buy: btc_to_swap },
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_max_buy_exceeded_then_returns_error() {
|
||||||
|
let max_buy = bitcoin::Amount::from_btc(0.001).unwrap();
|
||||||
|
|
||||||
|
let mut test =
|
||||||
|
SpotPriceTest::setup(AliceBehaviourValues::default().with_max_buy(max_buy)).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_error(
|
||||||
|
alice::spot_price::Error::MaxBuyAmountExceeded {
|
||||||
|
buy: btc_to_swap,
|
||||||
|
max: max_buy,
|
||||||
|
},
|
||||||
|
bob::spot_price::Error::MaxBuyAmountExceeded {
|
||||||
|
buy: btc_to_swap,
|
||||||
|
max: max_buy,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_alice_in_resume_only_mode_then_returns_error() {
|
||||||
|
let mut test =
|
||||||
|
SpotPriceTest::setup(AliceBehaviourValues::default().with_resume_only(true)).await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_error(
|
||||||
|
alice::spot_price::Error::ResumeOnlyMode,
|
||||||
|
bob::spot_price::Error::NoSwapsAccepted,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_rate_fetch_problem_then_returns_error() {
|
||||||
|
let mut test =
|
||||||
|
SpotPriceTest::setup(AliceBehaviourValues::default().with_rate(TestRate::error_rate()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_error(
|
||||||
|
alice::spot_price::Error::LatestRateFetchFailed(Box::new(TestRateError {})),
|
||||||
|
bob::spot_price::Error::Other,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn given_rate_calculation_problem_then_returns_error() {
|
||||||
|
let mut test = SpotPriceTest::setup(
|
||||||
|
AliceBehaviourValues::default().with_rate(TestRate::from_rate_and_spread(0.0, 0)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let btc_to_swap = bitcoin::Amount::from_btc(0.01).unwrap();
|
||||||
|
|
||||||
|
let request = spot_price::Request { btc: btc_to_swap };
|
||||||
|
|
||||||
|
test.send_request(request);
|
||||||
|
test.assert_error(
|
||||||
|
alice::spot_price::Error::SellQuoteCalculationFailed(anyhow!(
|
||||||
|
"Error text irrelevant, won't be checked here"
|
||||||
|
)),
|
||||||
|
bob::spot_price::Error::Other,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpotPriceTest {
|
||||||
|
alice_swarm: Swarm<alice::spot_price::Behaviour<TestRate>>,
|
||||||
|
bob_swarm: Swarm<spot_price::Behaviour>,
|
||||||
|
|
||||||
|
alice_peer_id: PeerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpotPriceTest {
|
||||||
|
pub async fn setup(values: AliceBehaviourValues) -> Self {
|
||||||
|
let (mut alice_swarm, _, alice_peer_id) = new_swarm(|_, _| {
|
||||||
|
Behaviour::new(
|
||||||
|
values.balance,
|
||||||
|
values.lock_fee,
|
||||||
|
values.max_buy,
|
||||||
|
values.rate.clone(),
|
||||||
|
values.resume_only,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let (mut bob_swarm, ..) = new_swarm(|_, _| bob::spot_price::bob());
|
||||||
|
|
||||||
|
connect(&mut alice_swarm, &mut bob_swarm).await;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
alice_swarm,
|
||||||
|
bob_swarm,
|
||||||
|
alice_peer_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_request(&mut self, spot_price_request: spot_price::Request) {
|
||||||
|
self.bob_swarm
|
||||||
|
.behaviour_mut()
|
||||||
|
.send_request(&self.alice_peer_id, spot_price_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn assert_price(
|
||||||
|
&mut self,
|
||||||
|
alice_assert: (bitcoin::Amount, monero::Amount),
|
||||||
|
bob_assert: monero::Amount,
|
||||||
|
) {
|
||||||
|
match await_events_or_timeout(self.alice_swarm.next(), self.bob_swarm.next()).await {
|
||||||
|
(
|
||||||
|
alice::spot_price::OutEvent::ExecutionSetupParams { btc, xmr, .. },
|
||||||
|
spot_price::OutEvent::Message { message, .. },
|
||||||
|
) => {
|
||||||
|
assert_eq!(alice_assert, (btc, xmr));
|
||||||
|
|
||||||
|
let response = match message {
|
||||||
|
RequestResponseMessage::Response { response, .. } => response,
|
||||||
|
_ => panic!("Unexpected message {:?} for Bob", message),
|
||||||
|
};
|
||||||
|
|
||||||
|
match response {
|
||||||
|
spot_price::Response::Xmr(xmr) => {
|
||||||
|
assert_eq!(bob_assert, xmr)
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected response {:?} for Bob", response),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(alice_event, bob_event) => panic!(
|
||||||
|
"Received unexpected event, alice emitted {:?} and bob emitted {:?}",
|
||||||
|
alice_event, bob_event
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn assert_error(
|
||||||
|
&mut self,
|
||||||
|
alice_assert: alice::spot_price::Error,
|
||||||
|
bob_assert: bob::spot_price::Error,
|
||||||
|
) {
|
||||||
|
match await_events_or_timeout(self.alice_swarm.next(), self.bob_swarm.next()).await {
|
||||||
|
(
|
||||||
|
alice::spot_price::OutEvent::Error { error, .. },
|
||||||
|
spot_price::OutEvent::Message { message, .. },
|
||||||
|
) => {
|
||||||
|
// TODO: Somehow make PartialEq work on Alice's spot_price::Error
|
||||||
|
match (alice_assert, error) {
|
||||||
|
(
|
||||||
|
alice::spot_price::Error::BalanceTooLow { .. },
|
||||||
|
alice::spot_price::Error::BalanceTooLow { .. },
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
alice::spot_price::Error::MaxBuyAmountExceeded { .. },
|
||||||
|
alice::spot_price::Error::MaxBuyAmountExceeded { .. },
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
alice::spot_price::Error::LatestRateFetchFailed(_),
|
||||||
|
alice::spot_price::Error::LatestRateFetchFailed(_),
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
alice::spot_price::Error::SellQuoteCalculationFailed(_),
|
||||||
|
alice::spot_price::Error::SellQuoteCalculationFailed(_),
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
alice::spot_price::Error::ResumeOnlyMode,
|
||||||
|
alice::spot_price::Error::ResumeOnlyMode,
|
||||||
|
) => {}
|
||||||
|
(alice_assert, error) => {
|
||||||
|
panic!("Expected: {:?} Actual: {:?}", alice_assert, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = match message {
|
||||||
|
RequestResponseMessage::Response { response, .. } => response,
|
||||||
|
_ => panic!("Unexpected message {:?} for Bob", message),
|
||||||
|
};
|
||||||
|
|
||||||
|
match response {
|
||||||
|
spot_price::Response::Error(error) => {
|
||||||
|
assert_eq!(bob_assert, error.into())
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected response {:?} for Bob", response),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(alice_event, bob_event) => panic!(
|
||||||
|
"Received unexpected event, alice emitted {:?} and bob emitted {:?}",
|
||||||
|
alice_event, bob_event
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AliceBehaviourValues {
|
||||||
|
pub balance: monero::Amount,
|
||||||
|
pub lock_fee: monero::Amount,
|
||||||
|
pub max_buy: bitcoin::Amount,
|
||||||
|
pub rate: TestRate, // 0.01
|
||||||
|
pub resume_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AliceBehaviourValues {
|
||||||
|
pub fn with_balance(mut self, balance: monero::Amount) -> AliceBehaviourValues {
|
||||||
|
self.balance = balance;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_lock_fee(mut self, lock_fee: monero::Amount) -> AliceBehaviourValues {
|
||||||
|
self.lock_fee = lock_fee;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_max_buy(mut self, max_buy: bitcoin::Amount) -> AliceBehaviourValues {
|
||||||
|
self.max_buy = max_buy;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_resume_only(mut self, resume_only: bool) -> AliceBehaviourValues {
|
||||||
|
self.resume_only = resume_only;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_rate(mut self, rate: TestRate) -> AliceBehaviourValues {
|
||||||
|
self.rate = rate;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum TestRate {
|
||||||
|
Rate(Rate),
|
||||||
|
Err(TestRateError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestRate {
|
||||||
|
pub const RATE: f64 = 0.01;
|
||||||
|
|
||||||
|
pub fn from_rate_and_spread(rate: f64, spread: u64) -> Self {
|
||||||
|
let ask = bitcoin::Amount::from_btc(rate).expect("Static value should never fail");
|
||||||
|
let spread = Decimal::from(spread);
|
||||||
|
Self::Rate(Rate::new(ask, spread))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_rate() -> Self {
|
||||||
|
Self::Err(TestRateError {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TestRate {
|
||||||
|
fn default() -> Self {
|
||||||
|
TestRate::from_rate_and_spread(Self::RATE, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
|
#[error("Could not fetch rate")]
|
||||||
|
pub struct TestRateError {}
|
||||||
|
|
||||||
|
impl LatestRate for TestRate {
|
||||||
|
type Error = TestRateError;
|
||||||
|
|
||||||
|
fn latest_rate(&mut self) -> Result<Rate, Self::Error> {
|
||||||
|
match self {
|
||||||
|
TestRate::Rate(rate) => Ok(*rate),
|
||||||
|
TestRate::Err(error) => Err(error.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ pub mod cancel;
|
||||||
pub mod event_loop;
|
pub mod event_loop;
|
||||||
mod execution_setup;
|
mod execution_setup;
|
||||||
pub mod refund;
|
pub mod refund;
|
||||||
mod spot_price;
|
pub mod spot_price;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod swap;
|
pub mod swap;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use libp2p::request_response::{ProtocolSupport, RequestResponseConfig};
|
||||||
use libp2p::PeerId;
|
use libp2p::PeerId;
|
||||||
|
|
||||||
const PROTOCOL: &str = spot_price::PROTOCOL;
|
const PROTOCOL: &str = spot_price::PROTOCOL;
|
||||||
type SpotPriceOutEvent = spot_price::OutEvent;
|
pub type SpotPriceOutEvent = spot_price::OutEvent;
|
||||||
|
|
||||||
/// Constructs a new instance of the `spot-price` behaviour to be used by Bob.
|
/// Constructs a new instance of the `spot-price` behaviour to be used by Bob.
|
||||||
///
|
///
|
||||||
|
@ -37,7 +37,7 @@ impl From<(PeerId, spot_price::Message)> for OutEvent {
|
||||||
|
|
||||||
crate::impl_from_rr_event!(SpotPriceOutEvent, OutEvent, PROTOCOL);
|
crate::impl_from_rr_event!(SpotPriceOutEvent, OutEvent, PROTOCOL);
|
||||||
|
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
#[derive(Clone, Debug, thiserror::Error, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Seller currently does not accept incoming swap requests, please try again later")]
|
#[error("Seller currently does not accept incoming swap requests, please try again later")]
|
||||||
NoSwapsAccepted,
|
NoSwapsAccepted,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue