Skip to content

Commit

Permalink
Fix checking balance on taker swap start #888 (#890)
Browse files Browse the repository at this point in the history
* WIP.

* WIP.

* WIP. Found and fixed the bug. TODO refactor.

* Finalize the tests and fix. Refactor.

* Remove pub for TakerSwapPreparedParams fields.

* Add MarketMakerIt::start_with_envs to avoid passing empty slice everywhere.
  • Loading branch information
artemii235 authored Apr 7, 2021
1 parent 335e5b5 commit d3e81c3
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 7 deletions.
21 changes: 20 additions & 1 deletion mm2src/common/for_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,25 @@ impl MarketMakerIt {
/// * `local` - Function to start the MarketMaker in a local thread, instead of spawning a process.
/// It's required to manually add 127.0.0.* IPs aliases on Mac to make it properly work.
/// cf. https://superuser.com/a/458877, https://superuser.com/a/635327
pub fn start(mut conf: Json, userpass: String, local: Option<LocalStart>) -> Result<MarketMakerIt, String> {
pub fn start(conf: Json, userpass: String, local: Option<LocalStart>) -> Result<MarketMakerIt, String> {
MarketMakerIt::start_with_envs(conf, userpass, local, &[])
}

/// Create a new temporary directory and start a new MarketMaker process there.
///
/// * `conf` - The command-line configuration passed to the MarketMaker.
/// Unique local IP address is injected as "myipaddr" unless this field is already present.
/// * `userpass` - RPC API key. We should probably extract it automatically from the MM log.
/// * `local` - Function to start the MarketMaker in a local thread, instead of spawning a process.
/// * `envs` - The enviroment variables passed to the process
/// It's required to manually add 127.0.0.* IPs aliases on Mac to make it properly work.
/// cf. https://superuser.com/a/458877, https://superuser.com/a/635327
pub fn start_with_envs(
mut conf: Json,
userpass: String,
local: Option<LocalStart>,
envs: &[(&str, &str)],
) -> Result<MarketMakerIt, String> {
let ip: IpAddr = if conf["myipaddr"].is_null() {
// Generate an unique IP.
let mut attempts = 0;
Expand Down Expand Up @@ -301,6 +319,7 @@ impl MarketMakerIt {
.current_dir(&folder)
.env("_MM2_TEST_CONF", try_s!(json::to_string(&conf)))
.env("MM2_UNBUFFERED_OUTPUT", "1")
.envs(envs.to_vec())
.stdout(try_s!(log.try_clone()))
.stderr(log)
.spawn());
Expand Down
4 changes: 4 additions & 0 deletions mm2src/common/mm_number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ impl From<BigRational> for MmNumber {
fn from(r: BigRational) -> MmNumber { MmNumber(r) }
}

impl From<Fraction> for MmNumber {
fn from(f: Fraction) -> MmNumber { MmNumber(f.into()) }
}

impl From<MmNumber> for BigDecimal {
fn from(n: MmNumber) -> BigDecimal { from_ratio_to_dec(&n.0) }
}
Expand Down
124 changes: 123 additions & 1 deletion mm2src/docker_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ mod docker_tests {

// https://github.com/KomodoPlatform/atomicDEX-API/issues/471
#[test]
fn test_match_and_trade_max() {
fn test_match_and_trade_setprice_max() {
let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into());
let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000.into());
let coins = json! ([
Expand Down Expand Up @@ -1206,6 +1206,128 @@ mod docker_tests {
block_on(mm_alice.stop()).unwrap();
}

#[test]
// https://github.com/KomodoPlatform/atomicDEX-API/issues/888
fn test_max_taker_vol_swap() {
let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into());
let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 50.into());
let coins = json! ([
{"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}},
{"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}},
]);
let mut mm_bob = MarketMakerIt::start_with_envs(
json! ({
"gui": "nogui",
"netid": 9000,
"dht": "on", // Enable DHT without delay.
"passphrase": format!("0x{}", hex::encode(bob_priv_key)),
"coins": coins,
"rpc_password": "pass",
"i_am_seed": true,
}),
"pass".to_string(),
None,
&[("MYCOIN_FEE_DISCOUNT", "")],
)
.unwrap();
let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path);
block_on(mm_bob.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap();

let mut mm_alice = MarketMakerIt::start_with_envs(
json! ({
"gui": "nogui",
"netid": 9000,
"dht": "on", // Enable DHT without delay.
"passphrase": format!("0x{}", hex::encode(alice_priv_key)),
"coins": coins,
"rpc_password": "pass",
"seednodes": vec![format!("{}", mm_bob.ip)],
}),
"pass".to_string(),
None,
&[("MYCOIN_FEE_DISCOUNT", "")],
)
.unwrap();
let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path);
block_on(mm_alice.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap();

log!([block_on(enable_native(&mm_bob, "MYCOIN", &[]))]);
log!([block_on(enable_native(&mm_bob, "MYCOIN1", &[]))]);
log!([block_on(enable_native(&mm_alice, "MYCOIN", &[]))]);
log!([block_on(enable_native(&mm_alice, "MYCOIN1", &[]))]);
let price = MmNumber::from((100, 1620));
let rc = block_on(mm_bob.rpc(json! ({
"userpass": mm_bob.userpass,
"method": "setprice",
"base": "MYCOIN",
"rel": "MYCOIN1",
"price": price,
"max": true,
})))
.unwrap();
assert!(rc.0.is_success(), "!setprice: {}", rc.1);

let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
"method": "orderbook",
"base": "MYCOIN1",
"rel": "MYCOIN",
})))
.unwrap();
assert!(rc.0.is_success(), "!orderbook: {}", rc.1);
log!((rc.1));
thread::sleep(Duration::from_secs(3));

let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
"method": "max_taker_vol",
"coin": "MYCOIN1",
"trade_with": "MYCOIN",
})))
.unwrap();
assert!(rc.0.is_success(), "!max_taker_vol: {}", rc.1);
let vol: MaxTakerVolResponse = json::from_str(&rc.1).unwrap();
let expected_vol = MmNumber::from((647499741, 12965000));

let actual_vol = MmNumber::from(vol.result.clone());
assert_eq!(expected_vol, actual_vol);

let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
"method": "sell",
"base": "MYCOIN1",
"rel": "MYCOIN",
"price": "16",
"volume": vol.result,
})))
.unwrap();
assert!(rc.0.is_success(), "!sell: {}", rc.1);
let sell_res: BuyOrSellRpcResult = json::from_str(&rc.1).unwrap();

block_on(mm_bob.wait_for_log(22., |log| log.contains("Entering the maker_swap_loop MYCOIN/MYCOIN1"))).unwrap();
block_on(mm_alice.wait_for_log(22., |log| log.contains("Entering the taker_swap_loop MYCOIN/MYCOIN1")))
.unwrap();

thread::sleep(Duration::from_secs(3));

let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
"method": "my_swap_status",
"params": {
"uuid": sell_res.result.uuid
}
})))
.unwrap();
assert!(rc.0.is_success(), "!my_swap_status: {}", rc.1);

let status_response: Json = json::from_str(&rc.1).unwrap();
let events_array = status_response["result"]["events"].as_array().unwrap();
let first_event_type = events_array[0]["event"]["type"].as_str().unwrap();
assert_eq!("Started", first_event_type);
block_on(mm_bob.stop()).unwrap();
block_on(mm_alice.stop()).unwrap();
}

#[test]
fn test_buy_when_coins_locked_by_other_swap() {
let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into());
Expand Down
11 changes: 8 additions & 3 deletions mm2src/lp_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ use common::{bits256, block_on, calc_total_pages,
log::{error, info},
mm_ctx::{from_ctx, MmArc},
mm_number::{Fraction, MmNumber},
now_ms, read_dir, rpc_response, slurp, write, HyRes, TraceSource, Traceable};
now_ms, read_dir, rpc_response, slurp, var, write, HyRes, TraceSource, Traceable};
use futures::compat::Future01CompatExt;
use futures::future::{abortable, AbortHandle, TryFutureExt};
use http::Response;
Expand Down Expand Up @@ -92,7 +92,7 @@ pub use maker_swap::{calc_max_maker_vol, check_balance_for_maker_swap, maker_swa
use maker_swap::{stats_maker_swap_file_path, MakerSwapEvent};
pub use taker_swap::{calc_max_taker_vol, check_balance_for_taker_swap, max_taker_vol, max_taker_vol_from_available,
run_taker_swap, stats_taker_swap_dir, taker_swap_trade_preimage, RunTakerSwapInput,
TakerSavedSwap, TakerSwap, TakerTradePreimage};
TakerSavedSwap, TakerSwap, TakerSwapPreparedParams, TakerTradePreimage};
use taker_swap::{stats_taker_swap_file_path, TakerSwapEvent};

pub const SWAP_PREFIX: TopicPrefix = "swap";
Expand Down Expand Up @@ -724,7 +724,12 @@ fn dex_fee_threshold(min_tx_amount: MmNumber) -> MmNumber {
}

fn dex_fee_rate(base: &str, rel: &str) -> MmNumber {
if base == "KMD" || rel == "KMD" {
let fee_discount_tickers: &[&str] = if cfg!(test) && var("MYCOIN_FEE_DISCOUNT").is_ok() {
&["KMD", "MYCOIN"]
} else {
&["KMD"]
};
if fee_discount_tickers.contains(&base) || fee_discount_tickers.contains(&rel) {
// 1/777 - 10%
BigRational::new(9.into(), 7770.into()).into()
} else {
Expand Down
1 change: 1 addition & 0 deletions mm2src/lp_swap/maker_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ impl MakerSwap {
}

async fn start(&self) -> Result<(Option<MakerSwapCommand>, Vec<MakerSwapEvent>), String> {
// do not use self.r().data here as it is not initialized at this step yet
let preimage_value = TradePreimageValue::Exact(self.maker_amount.clone());
let stage = FeeApproxStage::StartSwap;
let get_sender_trade_fee_fut = self.maker_coin.get_sender_trade_fee(preimage_value, stage.clone());
Expand Down
9 changes: 7 additions & 2 deletions mm2src/lp_swap/taker_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,8 +673,9 @@ impl TakerSwap {
}

async fn start(&self) -> Result<(Option<TakerSwapCommand>, Vec<TakerSwapEvent>), String> {
// do not use self.r().data here as it is not initialized at this step yet
let stage = FeeApproxStage::StartSwap;
let dex_fee = dex_fee_amount_from_taker_coin(&self.taker_coin, &self.r().data.maker_coin, &self.taker_amount);
let dex_fee = dex_fee_amount_from_taker_coin(&self.taker_coin, self.maker_coin.ticker(), &self.taker_amount);
let preimage_value = TradePreimageValue::Exact(self.taker_amount.to_decimal());

let fee_to_send_dex_fee_fut = self
Expand Down Expand Up @@ -1518,7 +1519,11 @@ pub async fn check_balance_for_taker_swap(
)
.await
);
try_s!(check_other_coin_balance_for_swap(ctx, other_coin, swap_uuid, params.maker_payment_spend_trade_fee).await);
if !params.maker_payment_spend_trade_fee.paid_from_trading_vol {
try_s!(
check_other_coin_balance_for_swap(ctx, other_coin, swap_uuid, params.maker_payment_spend_trade_fee).await
);
}
Ok(())
}

Expand Down
6 changes: 6 additions & 0 deletions mm2src/mm2_tests/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,9 @@ impl TradePreimageResponse {
}
}
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MaxTakerVolResponse {
pub result: Fraction,
}

0 comments on commit d3e81c3

Please sign in to comment.