From 91fcacc339a1966fb059e4a446eab4970a202f00 Mon Sep 17 00:00:00 2001 From: Artem Pikulin Date: Fri, 20 Nov 2020 21:23:11 +0700 Subject: [PATCH] Fix max_taker_vol. Take min dex fee and KMD discount into account. #733 (#734) * Fix max_taker_vol. Take min dex fee and KMD discount into account. #733 * Fix comment about expected volume equation. --- mm2src/common/mm_number.rs | 4 + mm2src/docker_tests.rs | 171 ++++++++++++++++-- .../swaps_confs_settings_sync_tests.rs | 8 +- mm2src/docker_tests/swaps_file_lock_tests.rs | 8 +- mm2src/lp_swap.rs | 2 +- mm2src/lp_swap/taker_swap.rs | 24 ++- 6 files changed, 184 insertions(+), 33 deletions(-) diff --git a/mm2src/common/mm_number.rs b/mm2src/common/mm_number.rs index d4562cad39..b27922c2c3 100644 --- a/mm2src/common/mm_number.rs +++ b/mm2src/common/mm_number.rs @@ -133,6 +133,10 @@ impl From for MmNumber { fn from(n: u64) -> MmNumber { BigRational::from_integer(n.into()).into() } } +impl From<&'static str> for MmNumber { + fn from(n: &'static str) -> MmNumber { MmNumber::from(n.parse::().unwrap()) } +} + impl From<(u64, u64)> for MmNumber { fn from(tuple: (u64, u64)) -> MmNumber { BigRational::new(tuple.0.into(), tuple.1.into()).into() } } diff --git a/mm2src/docker_tests.rs b/mm2src/docker_tests.rs index 36498869b1..ec781107e9 100644 --- a/mm2src/docker_tests.rs +++ b/mm2src/docker_tests.rs @@ -55,12 +55,14 @@ mod docker_tests { #[rustfmt::skip] mod swaps_file_lock_tests; + use bigdecimal::BigDecimal; use bitcrypto::ChecksumType; use coins::utxo::rpc_clients::{UtxoRpcClientEnum, UtxoRpcClientOps}; use coins::utxo::utxo_standard::{utxo_standard_coin_from_conf_and_request, UtxoStandardCoin}; use coins::utxo::{coin_daemon_data_dir, dhash160, zcash_params_path, UtxoCommonOps}; use coins::{FoundSwapTxSpend, MarketCoinOps, SwapOps}; use common::block_on; + use common::for_tests::enable_electrum; use common::{file_lock::FileLock, for_tests::{enable_native, mm_dump, new_mm2_temp_folder_path, MarketMakerIt}, mm_ctx::{MmArc, MmCtxBuilder}}; @@ -235,7 +237,7 @@ mod docker_tests { } // generate random privkey, create a coin and fill it's address with 1000 coins - fn generate_coin_with_random_privkey(ticker: &str, balance: u64) -> (MmArc, UtxoStandardCoin, [u8; 32]) { + fn generate_coin_with_random_privkey(ticker: &str, balance: BigDecimal) -> (MmArc, UtxoStandardCoin, [u8; 32]) { // prevent concurrent initialization since daemon RPC returns errors if send_to_address // is called concurrently (insufficient funds) and it also may return other errors // if previous transaction is not confirmed yet @@ -252,12 +254,12 @@ mod docker_tests { (ctx, coin, priv_key) } - fn fill_address(coin: &UtxoStandardCoin, address: &str, amount: u64, timeout: u64) { + fn fill_address(coin: &UtxoStandardCoin, address: &str, amount: BigDecimal, timeout: u64) { if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { unwrap!(client .import_address(&coin.my_address().unwrap(), &coin.my_address().unwrap(), false) .wait()); - let hash = client.send_to_address(address, &amount.into()).wait().unwrap(); + let hash = client.send_to_address(address, &amount).wait().unwrap(); let tx_bytes = client.get_transaction_bytes(hash).wait().unwrap(); unwrap!(coin.wait_for_confirmations(&tx_bytes, 1, false, timeout, 1).wait()); log!({ "{:02x}", tx_bytes }); @@ -279,7 +281,7 @@ mod docker_tests { #[test] fn test_search_for_swap_tx_spend_native_was_refunded_taker() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run - let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); let time_lock = (now_ms() / 1000) as u32 - 3600; let tx = coin @@ -311,7 +313,7 @@ mod docker_tests { #[test] fn test_search_for_swap_tx_spend_native_was_refunded_maker() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run - let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); let time_lock = (now_ms() / 1000) as u32 - 3600; let tx = coin @@ -343,7 +345,7 @@ mod docker_tests { #[test] fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run - let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); let secret = [0; 32]; let time_lock = (now_ms() / 1000) as u32 - 3600; @@ -376,7 +378,7 @@ mod docker_tests { #[test] fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run - let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_ctx, coin, _) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); let secret = [0; 32]; let time_lock = (now_ms() / 1000) as u32 - 3600; @@ -409,7 +411,7 @@ mod docker_tests { // https://github.com/KomodoPlatform/atomicDEX-API/issues/554 #[test] fn order_should_be_cancelled_when_entire_balance_is_withdrawn() { - let (_ctx, _, priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_ctx, _, priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.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"}}, @@ -516,8 +518,8 @@ mod docker_tests { // https://github.com/KomodoPlatform/atomicDEX-API/issues/471 #[test] fn test_match_and_trade_max() { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000); + 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! ([ {"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"}}, @@ -611,8 +613,8 @@ mod docker_tests { #[test] fn test_buy_when_coins_locked_by_other_swap() { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2); + let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); + let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2.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"}}, @@ -709,8 +711,8 @@ mod docker_tests { #[test] fn test_sell_when_coins_locked_by_other_swap() { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2); + let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); + let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2.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"}}, @@ -807,7 +809,7 @@ mod docker_tests { #[test] fn test_buy_max() { - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 1); + let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 1.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"}}, @@ -865,7 +867,7 @@ mod docker_tests { #[test] fn test_get_max_taker_vol() { - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 1); + let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 1.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"}}, @@ -889,6 +891,7 @@ mod docker_tests { )); log!([block_on(enable_native(&mm_alice, "MYCOIN1", vec![]))]); + log!([block_on(enable_native(&mm_alice, "MYCOIN", vec![]))]); let rc = unwrap!(block_on(mm_alice.rpc(json! ({ "userpass": mm_alice.userpass, "method": "max_taker_vol", @@ -899,12 +902,142 @@ mod docker_tests { // the result of equation x + x / 777 + 0.00002 = 1 assert_eq!(json["result"]["numer"], Json::from("38849223")); assert_eq!(json["result"]["denom"], Json::from("38900000")); + + let rc = unwrap!(block_on(mm_alice.rpc(json! ({ + "userpass": mm_alice.userpass, + "method": "sell", + "base": "MYCOIN1", + "rel": "MYCOIN", + "price": 1, + "volume": { + "numer": json["result"]["numer"], + "denom": json["result"]["denom"], + } + })))); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + + unwrap!(block_on(mm_alice.stop())); + } + + // https://github.com/KomodoPlatform/atomicDEX-API/issues/733 + #[test] + fn test_get_max_taker_vol_dex_fee_threshold() { + let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", "0.05328455".parse().unwrap()); + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":10000,"protocol":{"type":"UTXO"}}, + {"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":10000,"protocol":{"type":"UTXO"}}, + ]); + let mut mm_alice = unwrap!(MarketMakerIt::start( + json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(alice_priv_key)), + "coins": coins, + "rpc_password": "pass", + "i_am_see": true, + }), + "pass".to_string(), + None, + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); + unwrap!(block_on( + mm_alice.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats ")) + )); + + log!([block_on(enable_native(&mm_alice, "MYCOIN1", vec![]))]); + log!([block_on(enable_native(&mm_alice, "MYCOIN", vec![]))]); + let rc = unwrap!(block_on(mm_alice.rpc(json! ({ + "userpass": mm_alice.userpass, + "method": "max_taker_vol", + "coin": "MYCOIN1", + })))); + assert!(rc.0.is_success(), "!max_taker_vol: {}", rc.1); + let json: Json = json::from_str(&rc.1).unwrap(); + // the result of equation x + 0.0001 (dex fee) + 0.0002 (miner fee * 2) = 0.05328455 + assert_eq!(json["result"]["numer"], Json::from("1059691")); + assert_eq!(json["result"]["denom"], Json::from("20000000")); + + let rc = unwrap!(block_on(mm_alice.rpc(json! ({ + "userpass": mm_alice.userpass, + "method": "sell", + "base": "MYCOIN1", + "rel": "MYCOIN", + "price": 1, + "volume": { + "numer": json["result"]["numer"], + "denom": json["result"]["denom"], + } + })))); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + + unwrap!(block_on(mm_alice.stop())); + } + + #[test] + fn test_get_max_taker_vol_with_kmd() { + let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 1.into()); + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":10000,"protocol":{"type":"UTXO"}}, + {"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":10000,"protocol":{"type":"UTXO"}}, + {"coin":"KMD","txversion":4,"overwintered":1,"txfee":10000,"protocol":{"type":"UTXO"}}, + ]); + let mut mm_alice = unwrap!(MarketMakerIt::start( + json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(alice_priv_key)), + "coins": coins, + "rpc_password": "pass", + "i_am_see": true, + }), + "pass".to_string(), + None, + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); + unwrap!(block_on( + mm_alice.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats ")) + )); + + log!([block_on(enable_native(&mm_alice, "MYCOIN1", vec![]))]); + log!([block_on(enable_native(&mm_alice, "MYCOIN", vec![]))]); + log!([block_on(enable_electrum(&mm_alice, "KMD", vec![ + "electrum1.cipig.net:10001", + "electrum2.cipig.net:10001", + "electrum3.cipig.net:10001" + ]))]); + let rc = unwrap!(block_on(mm_alice.rpc(json! ({ + "userpass": mm_alice.userpass, + "method": "max_taker_vol", + "coin": "MYCOIN1", + "trade_with": "KMD", + })))); + assert!(rc.0.is_success(), "!max_taker_vol: {}", rc.1); + let json: Json = json::from_str(&rc.1).unwrap(); + // the result of equation x + x * 9 / 7770 + 0.0002 = 1 + assert_eq!(json["result"]["numer"], Json::from("1294741")); + assert_eq!(json["result"]["denom"], Json::from("1296500")); + + let rc = unwrap!(block_on(mm_alice.rpc(json! ({ + "userpass": mm_alice.userpass, + "method": "sell", + "base": "MYCOIN1", + "rel": "KMD", + "price": 1, + "volume": { + "numer": json["result"]["numer"], + "denom": json["result"]["denom"], + } + })))); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + unwrap!(block_on(mm_alice.stop())); } #[test] fn test_set_price_max() { - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1); + let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1.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"}}, @@ -961,8 +1094,8 @@ mod docker_tests { #[test] fn swaps_should_stop_on_stop_rpc() { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000); + 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! ([ {"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"}}, diff --git a/mm2src/docker_tests/swaps_confs_settings_sync_tests.rs b/mm2src/docker_tests/swaps_confs_settings_sync_tests.rs index 3c713c7ce8..3ec2e80329 100644 --- a/mm2src/docker_tests/swaps_confs_settings_sync_tests.rs +++ b/mm2src/docker_tests/swaps_confs_settings_sync_tests.rs @@ -16,8 +16,8 @@ fn test_confirmation_settings_sync_correctly_on_buy( expected_taker: SwapConfirmationsSettings, expected_lock_duration: u64, ) { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000); + 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! ([ {"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"}}, @@ -185,8 +185,8 @@ fn test_confirmation_settings_sync_correctly_on_sell( expected_taker: SwapConfirmationsSettings, expected_lock_duration: u64, ) { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000); + 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! ([ {"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"}}, diff --git a/mm2src/docker_tests/swaps_file_lock_tests.rs b/mm2src/docker_tests/swaps_file_lock_tests.rs index accc4f7113..9b24676b72 100644 --- a/mm2src/docker_tests/swaps_file_lock_tests.rs +++ b/mm2src/docker_tests/swaps_file_lock_tests.rs @@ -7,7 +7,7 @@ const UNFINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469 const FINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036253,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","maker":"987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","my_persistent_pub":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"taker_payment_lock":1588251836,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_payment_wait":1588247156,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038239,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1588259636,"maker_pubkey":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107"}}},{"timestamp":1588244038271,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"2000","received_by_me":"1998.71298873","my_balance_change":"-1.28701127","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}},{"timestamp":1588244038698,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"0400008085202f890163cc0aceb3b432f84c1407991a0389b74b7842f030aa261203aa0b4b9a9a15fd010000006b483045022100f3239794b7b0e1c75aae084a65535270f154231ce86a4739976ff69eeff1ebd402206c0aeb28b7b4b2e77e98b3c3efd9a20d85c2cd0627acc3f45d577c0e88c34fb4012102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dffffffff0118e476481700000017a914dc4ed4686174503b85374bfb0aefe07a9fb37bcf8746aeaa5e000000000000000000000000000000","tx_hash":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf","from":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"to":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"total_amount":"1000","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf"}}},{"timestamp":1588244038699,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1588244053712,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1588244053745,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f8901c6272ed6f0900c449a6a6f948d74f85688228a843a672661ddbf1c4d48d71871010000006b483045022100c37627385c66b7bdf466b4dd4e7095b7551e6f5ea35f9bcc6344eb629f2edcb202203280eaba64b4d72010500166fab62cf34a687a516b2fe83d4eceaf8572cb37a7012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff0218e476481700000017a91431ff75ed72cd135a7ce50d121e71efc37066b9f9873915cb40170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac55aeaa5e000000000000000000000000000000","tx_hash":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC","bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"total_amount":"1998.71298873","spent_by_me":"1998.71298873","received_by_me":"998.71298873","my_balance_change":"-1000","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f"}}},{"timestamp":1588244078860,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f89018ff02d45bbd1f70ff21071fb3873584beab48ac1700bab73903fa44ae98c71b400000000d747304402204f0d641a3916e54d6788744c3229110a431eff18634c66fbd1741f9ca7dba99d02202315ee1d9317cc4d5d75d01f066f2c8a59876f790106d310144cdc03c25f985e0120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b6304bcccaa5eb1752102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac68ffffffff0130e07648170000001976a91412c553e8469363f2d30268c475af1e9186cc90af88ac54a0aa5e000000000000000000000000000000","tx_hash":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9","from":["bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"to":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":29,"timestamp":1588244070,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9"},"secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498"}}},{"timestamp":1588244078870,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"0400008085202f8901dfbf5ad1339c6f9478c4917cd3d90ac56bfdc2e75c57c94db20bed853f0b4c9a00000000d848304502210092535c081325ba5261699d7cfd4c503fb6125dde86389b83f40f3e2c006039bb022063cfd72aa15558dee874cac08b22dbcf11d3f06c8e48b0ddaf75b86887d604410120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b630434ebaa5eb1752102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac68ffffffff0130e07648170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac5ea0aa5e000000000000000000000000000000","tx_hash":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc","from":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"999.99998","my_balance_change":"999.99998","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc"}}},{"timestamp":1588244078871,"event":{"type":"Finished"}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; fn swap_file_lock_prevents_double_swap_start_on_kick_start(swap_json: &str) { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); let addr_hash = addr_hash_for_privkey(bob_priv_key); let db_folder = new_mm2_temp_folder_path(None).join("DB"); let swaps_db_folder = db_folder.join(addr_hash).join("SWAPS").join("MY"); @@ -63,8 +63,8 @@ fn test_swap_file_lock_prevents_double_swap_start_on_kick_start_taker() { #[test] fn test_swaps_should_kick_start_if_process_was_killed() { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); - let (_ctx, _, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000); + 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! ([ {"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"}}, @@ -184,7 +184,7 @@ fn swap_should_not_kick_start_if_finished_during_waiting_for_file_lock( unfinished_swap_json: &str, finished_swap_json: &str, ) { - let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_ctx, _, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000.into()); let addr_hash = addr_hash_for_privkey(bob_priv_key); let db_folder = new_mm2_temp_folder_path(None).join("DB"); let swaps_db_folder = db_folder.join(addr_hash).join("SWAPS").join("MY"); diff --git a/mm2src/lp_swap.rs b/mm2src/lp_swap.rs index 8890c13b1c..1f36f66194 100644 --- a/mm2src/lp_swap.rs +++ b/mm2src/lp_swap.rs @@ -396,7 +396,7 @@ fn dex_fee_rate(base: &str, rel: &str) -> MmNumber { pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber) -> MmNumber { let rate = dex_fee_rate(base, rel); - // 0.00001 + // 0.0001 let min_fee = BigRational::new(1.into(), 10000.into()).into(); let fee_amount = trade_amount * &rate; if fee_amount < min_fee { diff --git a/mm2src/lp_swap/taker_swap.rs b/mm2src/lp_swap/taker_swap.rs index d7c47629ce..e78193e56c 100644 --- a/mm2src/lp_swap/taker_swap.rs +++ b/mm2src/lp_swap/taker_swap.rs @@ -4,6 +4,7 @@ use super::{ban_pubkey, broadcast_my_swap_status, dex_fee_amount, get_locked_amo my_swap_file_path, my_swaps_dir, AtomicSwap, LockedAmount, MySwapInfo, RecoveredSwap, RecoveredSwapAction, SavedSwap, SwapConfirmationsSettings, SwapError, SwapNegotiationData, SwapsContext, TransactionIdentifier, BASIC_COMM_TIMEOUT, WAIT_CONFIRM_INTERVAL}; +use crate::mm2::lp_swap::dex_fee_rate; use atomic::Atomic; use bigdecimal::BigDecimal; use coins::{lp_coinfindᵃ, FoundSwapTxSpend, MmCoinEnum, TradeFee}; @@ -1400,12 +1401,18 @@ pub async fn check_balance_for_taker_swap( } } +#[derive(Deserialize)] +struct MaxTakerVolRequest { + coin: String, + trade_with: Option, +} + pub async fn max_taker_vol(ctx: MmArc, req: Json) -> Result>, String> { - let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); - let coin = match lp_coinfindᵃ(&ctx, &ticker).await { + let req: MaxTakerVolRequest = try_s!(json::from_value(req)); + let coin = match lp_coinfindᵃ(&ctx, &req.coin).await { Ok(Some(t)) => t, - Ok(None) => return ERR!("No such coin: {}", ticker), - Err(err) => return ERR!("!lp_coinfind({}): {}", ticker, err), + Ok(None) => return ERR!("No such coin: {}", req.coin), + Err(err) => return ERR!("!lp_coinfind({}): {}", req.coin, err), }; let balance = try_s!(coin.my_balance().compat().await); let fee_info = try_s!(coin.get_trade_fee().compat().await); @@ -1414,7 +1421,14 @@ pub async fn max_taker_vol(ctx: MmArc, req: Json) -> Result>, S if fee_info.coin == coin.ticker() { available_vol = available_vol - fee_info.amount * 2.into(); } - available_vol = available_vol * 777.into() / 778.into(); + let dex_fee_rate = dex_fee_rate(coin.ticker(), req.trade_with.as_ref().unwrap_or(&req.coin)); + let fee_threshold = MmNumber::from("0.0001"); + let threshold_coef = &(&MmNumber::from(1) + &dex_fee_rate) / &dex_fee_rate; + if available_vol > &fee_threshold * &threshold_coef { + available_vol = available_vol / (MmNumber::from(1) + dex_fee_rate); + } else { + available_vol = available_vol - fee_threshold; + } let res = try_s!(json::to_vec(&json!({ "result": available_vol.to_fraction()