diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index fe687f58912..ecdc57bc83b 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -16,7 +16,7 @@ custom-swap-locktime = [] # only for testing purposes, should never be activated native = [] # Deprecated track-ctx-pointer = ["common/track-ctx-pointer"] zhtlc-native-tests = ["coins/zhtlc-native-tests"] -run-docker-tests = ["coins/run-docker-tests"] +run-docker-tests = [] # TODO enable-solana = [] default = ["run-docker-tests"] diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 85a41eb9f57..90cd1b761ea 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -1,4 +1,6 @@ -use crate::docker_tests::docker_tests_common::{generate_utxo_coin_with_privkey, GETH_RPC_URL}; +use crate::docker_tests::docker_tests_common::{generate_utxo_coin_with_privkey, GETH_RPC_URL, MM_CTX}; +use crate::docker_tests::eth_docker_tests::{erc20_coin_with_random_privkey, erc20_contract_checksum, + fill_eth_erc20_with_private_key, swap_contract}; use crate::integration_tests_common::*; use crate::{fill_address, generate_utxo_coin_with_random_privkey, random_secp256k1_secret, rmd160_from_priv, utxo_coin_from_privkey}; @@ -6,16 +8,22 @@ use bitcrypto::dhash160; use chain::OutPoint; use coins::utxo::rpc_clients::UnspentInfo; use coins::utxo::{GetUtxoListOps, UtxoCommonOps}; +use coins::TxFeeDetails; use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TransactionEnum, WithdrawRequest}; -use common::{block_on, now_sec, wait_until_sec}; +use common::{block_on, executor::Timer, get_utc_timestamp, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; +use crypto::{CryptoCtx, KeyPairPolicy, Secp256k1Secret, StandardHDCoinAddress}; use futures01::Future; -use mm2_number::{BigDecimal, MmNumber}; -use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, eth_testnet_conf, get_locked_amount, kmd_conf, - max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, set_price, start_swaps, - MarketMakerIt, Mm2TestConf}; +use mm2_number::{BigDecimal, BigRational, MmNumber}; +use mm2_rpc::data::legacy::OrderbookResponse; +use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, check_recent_swaps, enable_eth_coin, + enable_eth_coin_hd, erc20_dev_conf, eth_dev_conf, eth_testnet_conf, + get_locked_amount, kmd_conf, max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, + set_price, start_swaps, wait_check_stats_swap_status, + wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, + wait_for_swaps_finish_and_check_status, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::{get_passphrase, structs::*}; use serde_json::Value as Json; use std::collections::HashMap; @@ -376,8 +384,8 @@ fn test_one_hundred_maker_payments_in_a_row_native() { assert_eq!(vec![expected_unspent], unspents); } -// https://github.com/KomodoPlatform/atomicDEX-API/issues/554 #[test] +// https://github.com/KomodoPlatform/atomicDEX-API/issues/554 fn order_should_be_cancelled_when_entire_balance_is_withdrawn() { let (_ctx, _, priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000.into()); let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); @@ -880,8 +888,8 @@ fn test_order_should_be_updated_when_matched_partially() { block_on(mm_alice.stop()).unwrap(); } -// https://github.com/KomodoPlatform/atomicDEX-API/issues/471 #[test] +// https://github.com/KomodoPlatform/atomicDEX-API/issues/471 fn test_match_and_trade_setprice_max() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000.into()); let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN1", 2000.into()); @@ -2849,7 +2857,7 @@ fn test_taker_order_converted_to_maker_should_cancel_properly_when_matched() { assert!(rc.0.is_success(), "!sell: {}", rc.1); log!("Give Bob 4 seconds to convert order to maker"); - thread::sleep(Duration::from_secs(4)); + block_on(Timer::sleep(4.)); let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, @@ -3565,3 +3573,1808 @@ fn test_locked_amount() { let expected_result: MmNumberMultiRepr = MmNumber::from("778.00002").into(); assert_eq!(expected_result, locked_alice.locked_amount); } + +#[test] +fn test_eth_swap_contract_addr_negotiation_same_fallback() { + let bob_coin = erc20_coin_with_random_privkey(swap_contract()); + let alice_coin = erc20_coin_with_random_privkey(swap_contract()); + + let bob_priv_key = bob_coin.display_priv_key().unwrap(); + let alice_priv_key = alice_coin.display_priv_key().unwrap(); + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + + let bob_conf = Mm2TestConf::seednode(&bob_priv_key, &coins); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let alice_conf = Mm2TestConf::light_node(&alice_priv_key, &coins, &[&mm_bob.ip.to_string()]); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ETH", + &[GETH_RPC_URL], + // using arbitrary address + "0x6c2858f6afac835c43ffda248aea167e1a58436c", + Some(&swap_contract), + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ERC20DEV", + &[GETH_RPC_URL], + // using arbitrary address + "0x6c2858f6afac835c43ffda248aea167e1a58436c", + Some(&swap_contract), + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ETH", + &[GETH_RPC_URL], + // using arbitrary address + "0x24abe4c71fc658c01313b6552cd40cd808b3ea80", + Some(&swap_contract), + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ERC20DEV", + &[GETH_RPC_URL], + // using arbitrary address + "0x24abe4c71fc658c01313b6552cd40cd808b3ea80", + Some(&swap_contract), + false + ))); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("ETH", "ERC20DEV")], + 1., + 1., + 0.0001, + )); + + // give few seconds for swap statuses to be saved + thread::sleep(Duration::from_secs(3)); + + let wait_until = get_utc_timestamp() + 30; + let expected_contract = Json::from(swap_contract.trim_start_matches("0x")); + + block_on(wait_for_swap_contract_negotiation( + &mm_bob, + &uuids[0], + expected_contract.clone(), + wait_until, + )); + block_on(wait_for_swap_contract_negotiation( + &mm_alice, + &uuids[0], + expected_contract, + wait_until, + )); +} + +#[test] +fn test_eth_swap_negotiation_fails_maker_no_fallback() { + let bob_coin = erc20_coin_with_random_privkey(swap_contract()); + let alice_coin = erc20_coin_with_random_privkey(swap_contract()); + + let bob_priv_key = bob_coin.display_priv_key().unwrap(); + let alice_priv_key = alice_coin.display_priv_key().unwrap(); + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + + let bob_conf = Mm2TestConf::seednode(&bob_priv_key, &coins); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let alice_conf = Mm2TestConf::light_node(&alice_priv_key, &coins, &[&mm_bob.ip.to_string()]); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ETH", + &[GETH_RPC_URL], + // using arbitrary address + "0x6c2858f6afac835c43ffda248aea167e1a58436c", + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ERC20DEV", + &[GETH_RPC_URL], + // using arbitrary address + "0x6c2858f6afac835c43ffda248aea167e1a58436c", + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ETH", + &[GETH_RPC_URL], + // using arbitrary address + "0x24abe4c71fc658c01313b6552cd40cd808b3ea80", + Some(&swap_contract), + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ERC20DEV", + &[GETH_RPC_URL], + // using arbitrary address + "0x24abe4c71fc658c01313b6552cd40cd808b3ea80", + Some(&swap_contract), + false + ))); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("ETH", "ERC20DEV")], + 1., + 1., + 0.0001, + )); + + // give few seconds for swap statuses to be saved + thread::sleep(Duration::from_secs(3)); + + let wait_until = get_utc_timestamp() + 30; + block_on(wait_for_swap_negotiation_failure(&mm_bob, &uuids[0], wait_until)); + block_on(wait_for_swap_negotiation_failure(&mm_alice, &uuids[0], wait_until)); +} + +fn trade_base_rel( + bob_priv_key: Secp256k1Secret, + alice_priv_key: Secp256k1Secret, + pairs: &[(&'static str, &'static str)], + maker_price: f64, + taker_price: f64, + volume: f64, +) { + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), bob_priv_key); + generate_utxo_coin_with_privkey("MYCOIN1", 1000.into(), bob_priv_key); + fill_eth_erc20_with_private_key(bob_priv_key); + + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), alice_priv_key); + generate_utxo_coin_with_privkey("MYCOIN1", 1000.into(), alice_priv_key); + fill_eth_erc20_with_private_key(alice_priv_key); + + let coins = json!([ + mycoin_conf(1000), + mycoin1_conf(1000), + eth_dev_conf(), + erc20_dev_conf(&erc20_contract_checksum()), + ]); + + let bob_conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(bob_priv_key)), &coins); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let alice_conf = Mm2TestConf::light_node(&format!("0x{}", hex::encode(alice_priv_key)), &coins, &[&mm_bob + .ip + .to_string()]); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + pairs, + maker_price, + taker_price, + volume, + )); + + for uuid in uuids.iter() { + // ensure the swaps are indexed to the SQLite database + let expected_log = format!("Inserting new swap {} to the SQLite database", uuid); + block_on(mm_alice.wait_for_log(5., |log| log.contains(&expected_log))).unwrap(); + block_on(mm_bob.wait_for_log(5., |log| log.contains(&expected_log))).unwrap() + } + + block_on(wait_for_swaps_finish_and_check_status( + &mut mm_bob, + &mut mm_alice, + &uuids, + volume, + maker_price, + )); + + log!("Waiting 3 seconds for nodes to broadcast their swaps data.."); + block_on(Timer::sleep(5.)); + + for uuid in uuids.iter() { + log!("Checking alice status.."); + block_on(wait_check_stats_swap_status(&mm_alice, uuid, 30)); + + log!("Checking bob status.."); + block_on(wait_check_stats_swap_status(&mm_bob, uuid, 30)); + } + + log!("Checking alice recent swaps.."); + block_on(check_recent_swaps(&mm_alice, uuids.len())); + log!("Checking bob recent swaps.."); + block_on(check_recent_swaps(&mm_bob, uuids.len())); + + for (base, rel) in pairs.iter() { + log!("Get {}/{} orderbook", base, rel); + let rc = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "orderbook", + "base": base, + "rel": rel, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let bob_orderbook: OrderbookResponse = serde_json::from_str(&rc.1).unwrap(); + log!("{}/{} orderbook {:?}", base, rel, bob_orderbook); + + assert_eq!(0, bob_orderbook.bids.len(), "{} {} bids must be empty", base, rel); + assert_eq!(0, bob_orderbook.asks.len(), "{} {} asks must be empty", base, rel); + } + + block_on(mm_bob.stop()).unwrap(); + block_on(mm_alice.stop()).unwrap(); +} + +#[test] +fn test_trade_base_rel_eth_erc20_coins() { + let bob_priv_key = random_secp256k1_secret(); + let alice_priv_key = random_secp256k1_secret(); + + let pairs = &[("ETH", "ERC20DEV")]; + trade_base_rel(bob_priv_key, alice_priv_key, pairs, 1., 2., 0.1); +} + +#[test] +fn test_trade_base_rel_mycoin_mycoin1_coins() { + let bob_priv_key = random_secp256k1_secret(); + let alice_priv_key = random_secp256k1_secret(); + + let pairs = &[("MYCOIN", "MYCOIN1")]; + trade_base_rel(bob_priv_key, alice_priv_key, pairs, 1., 2., 0.1); +} + +fn withdraw_and_send( + mm: &MarketMakerIt, + coin: &str, + from: Option, + to: &str, + from_addr: &str, + expected_bal_change: &str, + amount: f64, +) { + use std::str::FromStr; + + let withdraw = block_on(mm.rpc(&json! ({ + "mmrpc": "2.0", + "userpass": mm.userpass, + "method": "withdraw", + "params": { + "coin": coin, + "from": from, + "to": to, + "amount": amount, + }, + "id": 0, + }))) + .unwrap(); + + assert!(withdraw.0.is_success(), "!withdraw: {}", withdraw.1); + let res: RpcSuccessResponse = + serde_json::from_str(&withdraw.1).expect("Expected 'RpcSuccessResponse'"); + let tx_details = res.result; + + let mut expected_bal_change = BigDecimal::from_str(expected_bal_change).expect("!BigDecimal::from_str"); + + let fee_details: TxFeeDetails = serde_json::from_value(tx_details.fee_details).unwrap(); + + if let TxFeeDetails::Eth(fee_details) = fee_details { + if coin == "ETH" { + expected_bal_change -= fee_details.total_fee; + } + } + + assert_eq!(tx_details.to, vec![to.to_owned()]); + assert_eq!(tx_details.my_balance_change, expected_bal_change); + // Todo: Should check the from address for withdraws from another HD wallet address when there is an RPC method for addresses + if from.is_none() { + assert_eq!(tx_details.from, vec![from_addr.to_owned()]); + } + + let send = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "send_raw_transaction", + "coin": coin, + "tx_hex": tx_details.tx_hex, + }))) + .unwrap(); + assert!(send.0.is_success(), "!{} send: {}", coin, send.1); + let send_json: Json = serde_json::from_str(&send.1).unwrap(); + assert_eq!(tx_details.tx_hash, send_json["tx_hash"]); +} + +#[test] +fn test_withdraw_and_send_eth_erc20() { + let privkey = random_secp256k1_secret(); + fill_eth_erc20_with_private_key(privkey); + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + let mm = MarketMakerIt::start( + json!({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(privkey)), + "coins": coins, + "rpc_password": "pass", + "i_am_seed": true, + }), + "pass".to_string(), + None, + ) + .unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("Alice log path: {}", mm.log_path.display()); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + let eth_enable = block_on(enable_eth_coin( + &mm, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false, + )); + let erc20_enable = block_on(enable_eth_coin( + &mm, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false, + )); + + withdraw_and_send( + &mm, + "ETH", + None, + "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", + eth_enable["address"].as_str().unwrap(), + "-0.001", + 0.001, + ); + + withdraw_and_send( + &mm, + "ERC20DEV", + None, + "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", + erc20_enable["address"].as_str().unwrap(), + "-0.001", + 0.001, + ); + + // must not allow to withdraw to invalid checksum address + let withdraw = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "withdraw", + "params": { + "coin": "ETH", + "to": "0x4b2d0d6c2c785217457b69b922a2A9cEA98f71E9", + "amount": "0.001", + }, + "id": 0, + }))) + .unwrap(); + + assert!(withdraw.0.is_client_error(), "ETH withdraw: {}", withdraw.1); + let res: RpcErrorResponse = serde_json::from_str(&withdraw.1).unwrap(); + assert_eq!(res.error_type, "InvalidAddress"); + assert!(res.error.contains("Invalid address checksum")); +} + +#[test] +fn test_withdraw_and_send_hd_eth_erc20() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let KeyPairPolicy::GlobalHDAccount(hd_acc) = CryptoCtx::init_with_global_hd_account(MM_CTX.clone(), PASSPHRASE).unwrap().key_pair_policy().clone() else { + panic!("Expected 'KeyPairPolicy::GlobalHDAccount'"); + }; + + // Withdraw from HD account 0, change address 0, index 1 + let mut path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }; + // Get the private key associated with this account and fill it with eth and erc20 token. + let priv_key = hd_acc + .derive_secp256k1_secret( + &serde_json::from_value(eth_dev_conf()["derivation_path"].clone()).unwrap(), + &path_to_address, + ) + .unwrap(); + fill_eth_erc20_with_private_key(priv_key); + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let eth_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address.clone()), + )); + + let erc20_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address.clone()), + )); + + assert_eq!( + eth_enable["address"].as_str().unwrap(), + "0xDe841899aB4A22E23dB21634e54920aDec402397" + ); + assert_eq!( + erc20_enable["address"].as_str().unwrap(), + "0xDe841899aB4A22E23dB21634e54920aDec402397" + ); + + withdraw_and_send( + &mm_hd, + "ETH", + Some(path_to_address.clone()), + "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", + eth_enable["address"].as_str().unwrap(), + "-0.001", + 0.001, + ); + + withdraw_and_send( + &mm_hd, + "ERC20DEV", + Some(path_to_address.clone()), + "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", + erc20_enable["address"].as_str().unwrap(), + "-0.001", + 0.001, + ); + + // Change the address index, the withdrawal should fail. + path_to_address.address_index = 0; + + let withdraw = block_on(mm_hd.rpc(&json! ({ + "mmrpc": "2.0", + "userpass": mm_hd.userpass, + "method": "withdraw", + "params": { + "coin": "ETH", + "from": path_to_address, + "to": "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", + "amount": 0.001, + }, + "id": 0, + }))) + .unwrap(); + assert!(!withdraw.0.is_success(), "!withdraw: {}", withdraw.1); + + // But if we fill it, we should be able to withdraw. + let priv_key = hd_acc + .derive_secp256k1_secret( + &serde_json::from_value(eth_dev_conf()["derivation_path"].clone()).unwrap(), + &path_to_address, + ) + .unwrap(); + fill_eth_erc20_with_private_key(priv_key); + + let withdraw = block_on(mm_hd.rpc(&json! ({ + "mmrpc": "2.0", + "userpass": mm_hd.userpass, + "method": "withdraw", + "params": { + "coin": "ETH", + "from": path_to_address, + "to": "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", + "amount": 0.001, + }, + "id": 0, + }))) + .unwrap(); + assert!(withdraw.0.is_success(), "!withdraw: {}", withdraw.1); + + block_on(mm_hd.stop()).unwrap(); +} + +fn check_too_low_volume_order_creation_fails(mm: &MarketMakerIt, base: &str, rel: &str) { + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": base, + "rel": rel, + "price": "1", + "volume": "0.00000099", + "cancel_previous": false, + }))) + .unwrap(); + assert!(!rc.0.is_success(), "setprice success, but should be error {}", rc.1); + + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": base, + "rel": rel, + "price": "0.00000000000000000099", + "volume": "1", + "cancel_previous": false, + }))) + .unwrap(); + assert!(!rc.0.is_success(), "setprice success, but should be error {}", rc.1); + + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": base, + "rel": rel, + "price": "1", + "volume": "0.00000099", + }))) + .unwrap(); + assert!(!rc.0.is_success(), "sell success, but should be error {}", rc.1); + + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "buy", + "base": base, + "rel": rel, + "price": "1", + "volume": "0.00000099", + }))) + .unwrap(); + assert!(!rc.0.is_success(), "buy success, but should be error {}", rc.1); +} + +#[test] +// https://github.com/KomodoPlatform/atomicDEX-API/issues/481 +fn test_setprice_buy_sell_too_low_volume() { + let privkey = random_secp256k1_secret(); + + // Fill the addresses with coins. + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + generate_utxo_coin_with_privkey("MYCOIN1", 1000.into(), privkey); + fill_eth_erc20_with_private_key(privkey); + + let coins = json!([ + mycoin_conf(1000), + mycoin1_conf(1000), + eth_dev_conf(), + erc20_dev_conf(&erc20_contract_checksum()) + ]); + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_dump_log, _dump_dashboard) = mm.mm_dump(); + log!("Log path: {}", mm.log_path.display()); + + // Enable all the coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + check_too_low_volume_order_creation_fails(&mm, "MYCOIN", "ETH"); + check_too_low_volume_order_creation_fails(&mm, "ETH", "MYCOIN"); + check_too_low_volume_order_creation_fails(&mm, "ERC20DEV", "MYCOIN1"); +} + +#[test] +fn test_fill_or_kill_taker_order_should_not_transform_to_maker() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob MYCOIN/MYCOIN1 sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": 0.1, + "order_type": { + "type": "FillOrKill" + }, + "timeout": 2, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + let sell_json: Json = serde_json::from_str(&rc.1).unwrap(); + let order_type = sell_json["result"]["order_type"]["type"].as_str(); + assert_eq!(order_type, Some("FillOrKill")); + + log!("Wait for 4 seconds for Bob order to be cancelled"); + block_on(Timer::sleep(4.)); + + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "my_orders", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!my_orders: {}", rc.1); + let my_orders: Json = serde_json::from_str(&rc.1).unwrap(); + let my_maker_orders: HashMap = + serde_json::from_value(my_orders["result"]["maker_orders"].clone()).unwrap(); + let my_taker_orders: HashMap = + serde_json::from_value(my_orders["result"]["taker_orders"].clone()).unwrap(); + assert!(my_maker_orders.is_empty(), "maker_orders must be empty"); + assert!(my_taker_orders.is_empty(), "taker_orders must be empty"); +} + +#[test] +fn test_gtc_taker_order_should_transform_to_maker() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob MYCOIN/MYCOIN1 sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": 0.1, + "order_type": { + "type": "GoodTillCancelled" + }, + "timeout": 2, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + let rc_json: Json = serde_json::from_str(&rc.1).unwrap(); + let uuid: String = serde_json::from_value(rc_json["result"]["uuid"].clone()).unwrap(); + + log!("Wait for 4 seconds for Bob order to be converted to maker"); + block_on(Timer::sleep(4.)); + + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "my_orders", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!my_orders: {}", rc.1); + let my_orders: Json = serde_json::from_str(&rc.1).unwrap(); + let my_maker_orders: HashMap = + serde_json::from_value(my_orders["result"]["maker_orders"].clone()).unwrap(); + let my_taker_orders: HashMap = + serde_json::from_value(my_orders["result"]["taker_orders"].clone()).unwrap(); + assert_eq!(1, my_maker_orders.len(), "maker_orders must have exactly 1 order"); + assert!(my_taker_orders.is_empty(), "taker_orders must be empty"); + let order_path = mm.folder.join(format!( + "DB/{}/ORDERS/MY/MAKER/{}.json", + hex::encode(rmd160_from_passphrase(&format!("0x{}", hex::encode(privkey)))), + uuid + )); + log!("Order path {}", order_path.display()); + assert!(order_path.exists()); +} + +#[test] +fn test_set_price_must_save_order_to_db() { + let private_key_str = erc20_coin_with_random_privkey(swap_contract()) + .display_priv_key() + .unwrap(); + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + + let conf = Mm2TestConf::seednode(&private_key_str, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + log!("Issue bob ETH/ERC20DEV sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.1 + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + let rc_json: Json = serde_json::from_str(&rc.1).unwrap(); + let uuid: String = serde_json::from_value(rc_json["result"]["uuid"].clone()).unwrap(); + let order_path = mm.folder.join(format!( + "DB/{}/ORDERS/MY/MAKER/{}.json", + hex::encode(rmd160_from_passphrase(&private_key_str)), + uuid + )); + assert!(order_path.exists()); +} + +#[test] +fn test_set_price_response_format() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob MYCOIN/MYCOIN1 sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": 0.1 + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + let rc_json: Json = serde_json::from_str(&rc.1).unwrap(); + let _: BigDecimal = serde_json::from_value(rc_json["result"]["max_base_vol"].clone()).unwrap(); + let _: BigDecimal = serde_json::from_value(rc_json["result"]["min_base_vol"].clone()).unwrap(); + let _: BigDecimal = serde_json::from_value(rc_json["result"]["price"].clone()).unwrap(); + + let _: BigRational = serde_json::from_value(rc_json["result"]["max_base_vol_rat"].clone()).unwrap(); + let _: BigRational = serde_json::from_value(rc_json["result"]["min_base_vol_rat"].clone()).unwrap(); + let _: BigRational = serde_json::from_value(rc_json["result"]["price_rat"].clone()).unwrap(); +} + +#[test] +fn test_buy_response_format() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN1", 1000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob buy request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "buy", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": 0.1, + "base_confs": 5, + "base_nota": true, + "rel_confs": 4, + "rel_nota": false, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + let _: BuyOrSellRpcResult = serde_json::from_str(&rc.1).unwrap(); +} + +#[test] +fn test_sell_response_format() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": 0.1, + "base_confs": 5, + "base_nota": true, + "rel_confs": 4, + "rel_nota": false, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + let _: BuyOrSellRpcResult = serde_json::from_str(&rc.1).unwrap(); +} + +#[test] +fn test_set_price_conf_settings() { + let private_key_str = erc20_coin_with_random_privkey(swap_contract()) + .display_priv_key() + .unwrap(); + + let coins = json!([eth_dev_conf(),{"coin":"ERC20DEV","name":"erc20dev","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address":erc20_contract_checksum()}},"required_confirmations":2},]); + + let conf = Mm2TestConf::seednode(&private_key_str, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + log!("Issue bob sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.1, + "base_confs": 5, + "base_nota": true, + "rel_confs": 4, + "rel_nota": false, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + let json: Json = serde_json::from_str(&rc.1).unwrap(); + assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(5)); + assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(true)); + assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(4)); + assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); + + // must use coin config as defaults if not set in request + log!("Issue bob sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.1, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + let json: Json = serde_json::from_str(&rc.1).unwrap(); + assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(1)); + assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(false)); + assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(2)); + assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); +} + +#[test] +fn test_buy_conf_settings() { + let private_key_str = erc20_coin_with_random_privkey(swap_contract()) + .display_priv_key() + .unwrap(); + + let coins = json!([eth_dev_conf(),{"coin":"ERC20DEV","name":"erc20dev","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address":erc20_contract_checksum()}},"required_confirmations":2},]); + + let conf = Mm2TestConf::seednode(&private_key_str, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + log!("Issue bob buy request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "buy", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.1, + "base_confs": 5, + "base_nota": true, + "rel_confs": 4, + "rel_nota": false, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + let json: Json = serde_json::from_str(&rc.1).unwrap(); + assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(5)); + assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(true)); + assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(4)); + assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); + + // must use coin config as defaults if not set in request + log!("Issue bob buy request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "buy", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.1, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + let json: Json = serde_json::from_str(&rc.1).unwrap(); + assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(1)); + assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(false)); + assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(2)); + assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); +} + +#[test] +fn test_sell_conf_settings() { + let private_key_str = erc20_coin_with_random_privkey(swap_contract()) + .display_priv_key() + .unwrap(); + + let coins = json!([eth_dev_conf(),{"coin":"ERC20DEV","name":"erc20dev","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address":erc20_contract_checksum()}},"required_confirmations":2},]); + + let conf = Mm2TestConf::seednode(&private_key_str, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + dbg!(block_on(enable_eth_coin( + &mm, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + log!("Issue bob sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.1, + "base_confs": 5, + "base_nota": true, + "rel_confs": 4, + "rel_nota": false, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + let json: Json = serde_json::from_str(&rc.1).unwrap(); + assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(5)); + assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(true)); + assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(4)); + assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); + + // must use coin config as defaults if not set in request + log!("Issue bob sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.1, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + let json: Json = serde_json::from_str(&rc.1).unwrap(); + assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(1)); + assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(false)); + assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(2)); + assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); +} + +#[test] +fn test_my_orders_response_format() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN1", 10000.into(), privkey); + generate_utxo_coin_with_privkey("MYCOIN", 10000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob buy request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "buy", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": 0.1, + "base_confs": 5, + "base_nota": true, + "rel_confs": 4, + "rel_nota": false, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + + log!("Issue bob setprice request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": 0.1, + "base_confs": 5, + "base_nota": true, + "rel_confs": 4, + "rel_nota": false, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + + log!("Issue bob my_orders request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "my_orders", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!my_orders: {}", rc.1); + + let _: MyOrdersRpcResult = serde_json::from_str(&rc.1).unwrap(); +} + +#[test] +fn test_my_orders_after_matched() { + let bob_coin = erc20_coin_with_random_privkey(swap_contract()); + let alice_coin = erc20_coin_with_random_privkey(swap_contract()); + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + + let bob_conf = Mm2TestConf::seednode(&bob_coin.display_priv_key().unwrap(), &coins); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let alice_conf = Mm2TestConf::light_node(&alice_coin.display_priv_key().unwrap(), &coins, &[&mm_bob + .ip + .to_string()]); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + let rc = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "setprice", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.000001, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + + let rc = block_on(mm_alice.rpc(&json! ({ + "userpass": mm_alice.userpass, + "method": "buy", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.000001, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + + block_on(mm_bob.wait_for_log(22., |log| log.contains("Entering the maker_swap_loop ETH/ERC20DEV"))).unwrap(); + block_on(mm_alice.wait_for_log(22., |log| log.contains("Entering the taker_swap_loop ETH/ERC20DEV"))).unwrap(); + + log!("Issue bob my_orders request"); + let rc = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "my_orders", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!my_orders: {}", rc.1); + + let _: MyOrdersRpcResult = serde_json::from_str(&rc.1).unwrap(); + block_on(mm_bob.stop()).unwrap(); + block_on(mm_alice.stop()).unwrap(); +} + +#[test] +fn test_update_maker_order_after_matched() { + let bob_coin = erc20_coin_with_random_privkey(swap_contract()); + let alice_coin = erc20_coin_with_random_privkey(swap_contract()); + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + + let bob_conf = Mm2TestConf::seednode(&bob_coin.display_priv_key().unwrap(), &coins); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let alice_conf = Mm2TestConf::light_node(&alice_coin.display_priv_key().unwrap(), &coins, &[&mm_bob + .ip + .to_string()]); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_bob, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + None, + false + ))); + + let rc = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "setprice", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.00002, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + let setprice_json: Json = serde_json::from_str(&rc.1).unwrap(); + let uuid: String = serde_json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); + + let rc = block_on(mm_alice.rpc(&json! ({ + "userpass": mm_alice.userpass, + "method": "buy", + "base": "ETH", + "rel": "ERC20DEV", + "price": 1, + "volume": 0.00001, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + + block_on(mm_bob.wait_for_log(22., |log| log.contains("Entering the maker_swap_loop ETH/ERC20DEV"))).unwrap(); + block_on(mm_alice.wait_for_log(22., |log| log.contains("Entering the taker_swap_loop ETH/ERC20DEV"))).unwrap(); + + log!("Issue bob update maker order request that should fail because new volume is less than reserved amount"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "update_maker_order", + "uuid": uuid, + "volume_delta": -0.00002, + }))) + .unwrap(); + assert!( + !update_maker_order.0.is_success(), + "update_maker_order success, but should be error {}", + update_maker_order.1 + ); + + log!("Issue another bob update maker order request"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "update_maker_order", + "uuid": uuid, + "volume_delta": 0.00001, + }))) + .unwrap(); + assert!( + update_maker_order.0.is_success(), + "!update_maker_order: {}", + update_maker_order.1 + ); + let update_maker_order_json: Json = serde_json::from_str(&update_maker_order.1).unwrap(); + log!("{}", update_maker_order.1); + assert_eq!(update_maker_order_json["result"]["max_base_vol"], Json::from("0.00003")); + + log!("Issue bob my_orders request"); + let rc = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "my_orders", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!my_orders: {}", rc.1); + + let _: MyOrdersRpcResult = serde_json::from_str(&rc.1).unwrap(); + block_on(mm_bob.stop()).unwrap(); + block_on(mm_alice.stop()).unwrap(); +} + +#[test] +fn test_buy_min_volume() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN1", 1000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + let min_volume: BigDecimal = "0.1".parse().unwrap(); + log!("Issue bob MYCOIN/MYCOIN1 buy request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "buy", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": "2", + "volume": "1", + "min_volume": min_volume, + "order_type": { + "type": "GoodTillCancelled" + }, + "timeout": 2, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + let response: BuyOrSellRpcResult = serde_json::from_str(&rc.1).unwrap(); + assert_eq!(min_volume, response.result.min_volume); + + log!("Wait for 4 seconds for Bob order to be converted to maker"); + block_on(Timer::sleep(4.)); + + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "my_orders", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!my_orders: {}", rc.1); + let my_orders: MyOrdersRpcResult = serde_json::from_str(&rc.1).unwrap(); + assert_eq!( + 1, + my_orders.result.maker_orders.len(), + "maker_orders must have exactly 1 order" + ); + assert!(my_orders.result.taker_orders.is_empty(), "taker_orders must be empty"); + let maker_order = my_orders.result.maker_orders.get(&response.result.uuid).unwrap(); + + let expected_min_volume: BigDecimal = "0.2".parse().unwrap(); + assert_eq!(expected_min_volume, maker_order.min_base_vol); +} + +#[test] +fn test_sell_min_volume() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000),]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + let min_volume: BigDecimal = "0.1".parse().unwrap(); + log!("Issue bob MYCOIN/MYCOIN1 sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": "1", + "volume": "1", + "min_volume": min_volume, + "order_type": { + "type": "GoodTillCancelled" + }, + "timeout": 2, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + let rc_json: Json = serde_json::from_str(&rc.1).unwrap(); + let uuid: String = serde_json::from_value(rc_json["result"]["uuid"].clone()).unwrap(); + let min_volume_response: BigDecimal = serde_json::from_value(rc_json["result"]["min_volume"].clone()).unwrap(); + assert_eq!(min_volume, min_volume_response); + + log!("Wait for 4 seconds for Bob order to be converted to maker"); + block_on(Timer::sleep(4.)); + + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "my_orders", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!my_orders: {}", rc.1); + let my_orders: Json = serde_json::from_str(&rc.1).unwrap(); + let my_maker_orders: HashMap = + serde_json::from_value(my_orders["result"]["maker_orders"].clone()).unwrap(); + let my_taker_orders: HashMap = + serde_json::from_value(my_orders["result"]["taker_orders"].clone()).unwrap(); + assert_eq!(1, my_maker_orders.len(), "maker_orders must have exactly 1 order"); + assert!(my_taker_orders.is_empty(), "taker_orders must be empty"); + let maker_order = my_maker_orders.get(&uuid).unwrap(); + let min_volume_maker: BigDecimal = serde_json::from_value(maker_order["min_base_vol"].clone()).unwrap(); + assert_eq!(min_volume, min_volume_maker); +} + +#[test] +fn test_setprice_min_volume_dust() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000,"dust":10000000,"protocol":{"type":"UTXO"}}, + mycoin1_conf(1000), + ]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob MYCOIN/MYCOIN1 sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": "1", + "volume": "1", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + let response: SetPriceResponse = serde_json::from_str(&rc.1).unwrap(); + let expected_min = BigDecimal::from(1); + assert_eq!(expected_min, response.result.min_base_vol); + + log!("Issue bob MYCOIN/MYCOIN1 sell request less than dust"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "setprice", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": "1", + // Less than dust, should fial + "volume": 0.01, + }))) + .unwrap(); + assert!(!rc.0.is_success(), "!setprice: {}", rc.1); +} + +#[test] +fn test_sell_min_volume_dust() { + let privkey = random_secp256k1_secret(); + generate_utxo_coin_with_privkey("MYCOIN", 1000.into(), privkey); + + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000,"dust":10000000,"protocol":{"type":"UTXO"}}, + mycoin1_conf(1000), + ]); + + let conf = Mm2TestConf::seednode(&format!("0x{}", hex::encode(privkey)), &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let (_mm_dump_log, _mm_dump_dashboard) = mm.mm_dump(); + log!("MM log path: {}", mm.log_path.display()); + + // Enable coins + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + + log!("Issue bob MYCOIN/MYCOIN1 sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": "1", + "volume": "1", + "order_type": { + "type": "FillOrKill" + } + }))) + .unwrap(); + assert!(rc.0.is_success(), "!sell: {}", rc.1); + let response: BuyOrSellRpcResult = serde_json::from_str(&rc.1).unwrap(); + let expected_min = BigDecimal::from(1); + assert_eq!(response.result.min_volume, expected_min); + + log!("Issue bob MYCOIN/MYCOIN1 sell request"); + let rc = block_on(mm.rpc(&json! ({ + "userpass": mm.userpass, + "method": "sell", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": "1", + // Less than dust + "volume": 0.01, + "order_type": { + "type": "FillOrKill" + } + }))) + .unwrap(); + assert!(!rc.0.is_success(), "!sell: {}", rc.1); +} + +#[test] +fn test_enable_eth_erc20_coins_with_enable_hd() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + // Withdraw from HD account 0, change address 0, index 0 + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 0, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address.clone()), + )); + assert_eq!( + eth_enable["address"].as_str().unwrap(), + "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" + ); + + let erc20_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address), + )); + assert_eq!( + erc20_enable["address"].as_str().unwrap(), + "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" + ); + + // Withdraw from HD account 0, change address 0, index 1 + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address.clone()), + )); + assert_eq!( + eth_enable["address"].as_str().unwrap(), + "0xDe841899aB4A22E23dB21634e54920aDec402397" + ); + let erc20_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address), + )); + assert_eq!( + erc20_enable["address"].as_str().unwrap(), + "0xDe841899aB4A22E23dB21634e54920aDec402397" + ); + + // Withdraw from HD account 77, change address 0, index 7 + let path_to_address = StandardHDCoinAddress { + account: 77, + is_change: false, + address_index: 7, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ETH", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address.clone()), + )); + assert_eq!( + eth_enable["address"].as_str().unwrap(), + "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" + ); + let erc20_enable = block_on(enable_eth_coin_hd( + &mm_hd, + "ERC20DEV", + &[GETH_RPC_URL], + &swap_contract, + Some(path_to_address), + )); + assert_eq!( + erc20_enable["address"].as_str().unwrap(), + "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" + ); +} diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 1f08f9efeca..c62188d9ac2 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -6,6 +6,7 @@ use coins::eth::{checksum_address, eth_coin_from_conf_and_request, EthCoin, ERC2 use coins::{CoinProtocol, ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, PrivKeyBuildPolicy, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash}; use common::{block_on, now_sec}; +use crypto::Secp256k1Secret; use ethereum_types::U256; use futures01::Future; use mm2_test_helpers::for_tests::{erc20_dev_conf, eth_dev_conf}; @@ -113,6 +114,7 @@ pub fn eth_coin_with_random_privkey_using_urls(swap_contract: Address, urls: &[& eth_coin } +/// Creates ETH protocol coin supplied with 100 ETH, using the default GETH_RPC_URL pub fn eth_coin_with_random_privkey(swap_contract: Address) -> EthCoin { eth_coin_with_random_privkey_using_urls(swap_contract, &[GETH_RPC_URL]) } @@ -148,6 +150,54 @@ pub fn erc20_coin_with_random_privkey(swap_contract: Address) -> EthCoin { erc20_coin } +/// Fills the private key's public address with ETH and ERC20 tokens +pub fn fill_eth_erc20_with_private_key(priv_key: Secp256k1Secret) { + let eth_conf = eth_dev_conf(); + let req = json!({ + "method": "enable", + "coin": "ETH", + "urls": [GETH_RPC_URL], + "swap_contract_address": swap_contract(), + }); + + let eth_coin = block_on(eth_coin_from_conf_and_request( + &MM_CTX, + "ETH", + ð_conf, + &req, + CoinProtocol::ETH, + PrivKeyBuildPolicy::IguanaPrivKey(priv_key), + )) + .unwrap(); + + // 100 ETH + fill_eth(eth_coin.my_address, U256::from(10).pow(U256::from(20))); + + let erc20_conf = erc20_dev_conf(&erc20_contract_checksum()); + let req = json!({ + "method": "enable", + "coin": "ERC20DEV", + "urls": [GETH_RPC_URL], + "swap_contract_address": swap_contract(), + }); + + let erc20_coin = block_on(eth_coin_from_conf_and_request( + &MM_CTX, + "ERC20DEV", + &erc20_conf, + &req, + CoinProtocol::ERC20 { + platform: "ETH".to_string(), + contract_address: erc20_contract_checksum(), + }, + PrivKeyBuildPolicy::IguanaPrivKey(priv_key), + )) + .unwrap(); + + // 100 tokens (it has 8 decimals) + fill_erc20(erc20_coin.my_address, U256::from(10000000000u64)); +} + #[test] fn send_and_refund_eth_maker_payment() { let eth_coin = eth_coin_with_random_privkey(swap_contract()); diff --git a/mm2src/mm2_main/tests/integration_tests_common/mod.rs b/mm2src/mm2_main/tests/integration_tests_common/mod.rs index 39712f53ab5..7ec7fcdf6e5 100644 --- a/mm2src/mm2_main/tests/integration_tests_common/mod.rs +++ b/mm2src/mm2_main/tests/integration_tests_common/mod.rs @@ -125,7 +125,6 @@ pub async fn enable_coins_eth_electrum( enable_electrum_json(mm, "MORTY", false, marty_electrums(), path_to_address.clone()).await, ); replies.insert("ETH", enable_native(mm, "ETH", eth_urls, path_to_address.clone()).await); - replies.insert("JST", enable_native(mm, "JST", eth_urls, path_to_address).await); replies } diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 7e5714f5149..3cb349cb6df 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -2,7 +2,7 @@ use super::enable_z_coin; use crate::integration_tests_common::*; use common::executor::Timer; -use common::{cfg_native, cfg_wasm32, get_utc_timestamp, log, new_uuid}; +use common::{cfg_native, cfg_wasm32, log, new_uuid}; use crypto::privkey::key_pair_from_seed; use crypto::StandardHDCoinAddress; use http::{HeaderMap, StatusCode}; @@ -11,20 +11,17 @@ use mm2_metrics::{MetricType, MetricsJson}; use mm2_number::{BigDecimal, BigRational, Fraction, MmNumber}; use mm2_rpc::data::legacy::{CoinInitResponse, MmVersionResponse, OrderbookResponse}; use mm2_test_helpers::electrums::*; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "zhtlc-native-tests")))] -use mm2_test_helpers::for_tests::check_stats_swap_status; #[cfg(all(not(target_arch = "wasm32")))] use mm2_test_helpers::for_tests::{btc_segwit_conf, btc_with_spv_conf, btc_with_sync_starting_header, - check_recent_swaps, enable_eth_coin, enable_qrc20, eth_jst_testnet_conf, - eth_testnet_conf, find_metrics_in_json, from_env_file, get_shared_db_id, mm_spat, - morty_conf, rick_conf, sign_message, start_swaps, tbtc_segwit_conf, - tbtc_with_spv_conf, test_qrc20_history_impl, tqrc20_conf, verify_message, - wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, - wait_for_swaps_finish_and_check_status, wait_till_history_has_records, - MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, RaiiDump, - DOC_ELECTRUM_ADDRS, ETH_DEV_NODES, ETH_DEV_SWAP_CONTRACT, ETH_DEV_TOKEN_CONTRACT, - ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, MARTY_ELECTRUM_ADDRS, MORTY, - QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; + check_recent_swaps, enable_qrc20, eth_testnet_conf, find_metrics_in_json, + from_env_file, get_shared_db_id, mm_spat, morty_conf, rick_conf, sign_message, + start_swaps, tbtc_segwit_conf, tbtc_with_spv_conf, test_qrc20_history_impl, + tqrc20_conf, verify_message, wait_for_swaps_finish_and_check_status, + wait_till_history_has_records, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, + Mm2TestConfForSwap, RaiiDump, DOC_ELECTRUM_ADDRS, ETH_DEV_NODES, + ETH_DEV_SWAP_CONTRACT, ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, + MARTY_ELECTRUM_ADDRS, MORTY, QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, + TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::*; use serde_json::{self as json, json, Value as Json}; @@ -739,7 +736,6 @@ async fn trade_base_rel_electrum( rick_conf(), morty_conf(), eth_testnet_conf(), - eth_jst_testnet_conf(), {"coin":"ZOMBIE","asset":"ZOMBIE","fname":"ZOMBIE (TESTCOIN)","txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"ZHTLC"},"required_confirmations":0}, ]); @@ -873,30 +869,6 @@ async fn trade_base_rel_electrum( } } -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn trade_test_electrum_and_eth_coins() { - let bob_policy = Mm2InitPrivKeyPolicy::Iguana; - let alice_policy = Mm2InitPrivKeyPolicy::GlobalHDAccount; - let alice_path_to_address = StandardHDCoinAddress { - account: 0, - is_change: false, - address_index: 0, - }; - let pairs = &[("ETH", "JST")]; - block_on(trade_base_rel_electrum( - bob_policy, - alice_policy, - None, - Some(alice_path_to_address), - pairs, - 1., - 2., - 0.000001, - )); -} - #[test] #[cfg(all(not(target_arch = "wasm32"), feature = "zhtlc-native-tests"))] fn trade_test_electrum_rick_zombie() { @@ -980,7 +952,6 @@ fn withdraw_and_send( } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_and_send() { let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); @@ -989,8 +960,6 @@ fn test_withdraw_and_send() { {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, {"coin":"MORTY","asset":"MORTY","rpcport":8923,"txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, {"coin":"MORTY_SEGWIT","asset":"MORTY_SEGWIT","txversion":4,"overwintered":1,"segwit":true,"txfee":1000,"protocol":{"type":"UTXO"}}, - eth_testnet_conf(), - eth_jst_testnet_conf(), ]); let mm_alice = MarketMakerIt::start( @@ -1012,10 +981,8 @@ fn test_withdraw_and_send() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); - // wait until RPC API is active - // Enable coins. Print the replies in case we need the address. - let mut enable_res = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); + let mut enable_res = block_on(enable_coins_rick_morty_electrum(&mm_alice)); enable_res.insert( "MORTY_SEGWIT", block_on(enable_electrum( @@ -1037,26 +1004,6 @@ fn test_withdraw_and_send() { "-0.00101", 0.001, ); - withdraw_and_send( - &mm_alice, - "ETH", - None, - "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", - &enable_res, - "-0.001", - 0.001, - ); - log!("Wait for the ETH payment to be sent"); - thread::sleep(Duration::from_secs(15)); - withdraw_and_send( - &mm_alice, - "JST", - None, - "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", - &enable_res, - "-0.001", - 0.001, - ); // allow to withdraw non-Segwit coin to P2SH addresses let withdraw = block_on(mm_alice.rpc(&json! ({ @@ -1089,25 +1036,6 @@ fn test_withdraw_and_send() { assert!(withdraw.0.is_success(), "MORTY_SEGWIT withdraw: {}", withdraw.1); - // must not allow to withdraw to invalid checksum address - let withdraw = block_on(mm_alice.rpc(&json! ({ - "userpass": mm_alice.userpass, - "mmrpc": "2.0", - "method": "withdraw", - "params": { - "coin": "ETH", - "to": "0x4b2d0d6c2c785217457b69b922a2A9cEA98f71E9", - "amount": "0.001", - }, - "id": 0, - }))) - .unwrap(); - - assert!(withdraw.0.is_client_error(), "ETH withdraw: {}", withdraw.1); - let res: RpcErrorResponse = json::from_str(&withdraw.1).unwrap(); - assert_eq!(res.error_type, "InvalidAddress"); - assert!(res.error.contains("Invalid address checksum")); - // must not allow to withdraw too small amount 0.000005 (less than 0.00001 dust) let small_amount = MmNumber::from("0.000005").to_decimal(); let withdraw = block_on(mm_alice.rpc(&json! ({ @@ -1145,7 +1073,7 @@ fn test_withdraw_and_send_hd() { const TX_HISTORY: bool = false; const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - let coins = json!([rick_conf(), tbtc_segwit_conf(), eth_testnet_conf()]); + let coins = json!([rick_conf(), tbtc_segwit_conf()]); let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); @@ -1162,23 +1090,8 @@ fn test_withdraw_and_send_hd() { let mut tbtc_segwit_enable_res = HashMap::new(); tbtc_segwit_enable_res.insert("tBTC-Segwit", tbtc_segwit); - // Enable ETH with HD account 0, change address 0, index 1 to try to withdraw from index 0 which has funds - let eth = block_on(enable_native( - &mm_hd, - "ETH", - ETH_DEV_NODES, - Some(StandardHDCoinAddress { - account: 0, - is_change: false, - address_index: 1, - }), - )); - assert_eq!(eth.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); - let mut eth_enable_res = HashMap::new(); - eth_enable_res.insert("ETH", eth); - // Withdraw from HD account 0, change address 0, index 1 - let mut from_account_address = StandardHDCoinAddress { + let from_account_address = StandardHDCoinAddress { account: 0, is_change: false, address_index: 1, @@ -1197,26 +1110,13 @@ fn test_withdraw_and_send_hd() { withdraw_and_send( &mm_hd, "tBTC-Segwit", - Some(from_account_address.clone()), + Some(from_account_address), "tb1q7z9vzf8wpp9cks0l4nj5v28zf7jt56kuekegh5", &tbtc_segwit_enable_res, "-0.00100144", 0.001, ); - from_account_address.address_index = 0; - withdraw_and_send( - &mm_hd, - "ETH", - Some(from_account_address), - "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", - ð_enable_res, - "-0.001", - 0.001, - ); - log!("Wait for the ETH payment to be sent"); - thread::sleep(Duration::from_secs(15)); - block_on(mm_hd.stop()).unwrap(); } @@ -2222,11 +2122,10 @@ fn check_priv_key(mm: &MarketMakerIt, coin: &str, expected_priv_key: &str) { } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/519#issuecomment-589149811 fn test_show_priv_key() { - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf()]); let mm = MarketMakerIt::start( json! ({ @@ -2360,505 +2259,168 @@ fn test_electrum_and_enable_response() { assert_eq!(eth_response.get("mature_confirmations"), None); } -#[cfg(not(target_arch = "wasm32"))] -fn check_too_low_volume_order_creation_fails(mm: &MarketMakerIt, base: &str, rel: &str) { - let rc = block_on(mm.rpc(&json! ({ - "userpass": mm.userpass, - "method": "setprice", - "base": base, - "rel": rel, - "price": "1", - "volume": "0.00000099", - "cancel_previous": false, - }))) - .unwrap(); - assert!(!rc.0.is_success(), "setprice success, but should be error {}", rc.1); - - let rc = block_on(mm.rpc(&json! ({ - "userpass": mm.userpass, - "method": "setprice", - "base": base, - "rel": rel, - "price": "0.00000000000000000099", - "volume": "1", - "cancel_previous": false, - }))) - .unwrap(); - assert!(!rc.0.is_success(), "setprice success, but should be error {}", rc.1); - - let rc = block_on(mm.rpc(&json! ({ - "userpass": mm.userpass, - "method": "sell", - "base": base, - "rel": rel, - "price": "1", - "volume": "0.00000099", - }))) - .unwrap(); - assert!(!rc.0.is_success(), "sell success, but should be error {}", rc.1); - - let rc = block_on(mm.rpc(&json! ({ - "userpass": mm.userpass, - "method": "buy", - "base": base, - "rel": rel, - "price": "1", - "volume": "0.00000099", - }))) - .unwrap(); - assert!(!rc.0.is_success(), "buy success, but should be error {}", rc.1); -} - #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] -// https://github.com/KomodoPlatform/atomicDEX-API/issues/481 -fn setprice_buy_sell_too_low_volume() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); +// https://github.com/KomodoPlatform/atomicDEX-API/issues/635 +fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { + let coins = json!([ + {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, + {"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} + ]); - let mm = MarketMakerIt::start( + // start bob and immediately place the order + let mm_bob = MarketMakerIt::start( json! ({ "gui": "nogui", "netid": 9998, + "dht": "on", // Enable DHT without delay. "myipaddr": env::var ("BOB_TRADE_IP") .ok(), "rpcip": env::var ("BOB_TRADE_IP") .ok(), "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, + "passphrase": "bob passphrase", "coins": coins, - "rpc_password": "pass", "i_am_seed": true, + "rpc_password": "pass", }), "pass".into(), None, ) .unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + // Enable coins on Bob side. Print the replies in case we need the "address". + log!( + "enable_coins (bob): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_bob)) + ); - let (_dump_log, _dump_dashboard) = mm.mm_dump(); - log!("Log path: {}", mm.log_path.display()); - - let enable = block_on(enable_coins_eth_electrum(&mm, ETH_DEV_NODES, None)); - log!("{:?}", enable); - - check_too_low_volume_order_creation_fails(&mm, "MORTY", "ETH"); - check_too_low_volume_order_creation_fails(&mm, "ETH", "MORTY"); - check_too_low_volume_order_creation_fails(&mm, "JST", "MORTY"); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_fill_or_kill_taker_order_should_not_transform_to_maker() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + let set_price_json = json! ({ + "userpass": mm_bob.userpass, + "method": "setprice", + "base": "RICK", + "rel": "MORTY", + "price": 0.9, + "volume": "0.9", + }); + log!("Issue sell request on Bob side by setting base/rel price…"); + let rc = block_on(mm_bob.rpc(&set_price_json)).unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let mm_bob = MarketMakerIt::start( + let mm_alice = MarketMakerIt::start( json! ({ "gui": "nogui", - "netid": 8999, + "netid": 9998, "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, + "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), + "rpcip": env::var ("ALICE_TRADE_IP") .ok(), + "passphrase": "alice passphrase", "coins": coins, - "rpc_password": "password", - "i_am_seed": true, + "seednodes": [mm_bob.ip.to_string()], + "rpc_password": "pass", }), - "password".into(), + "pass".into(), None, ) .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + // Enable coins on Alice side. Print the replies in case we need the "address". log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + "enable_coins (alice): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_alice)) ); - log!("Issue bob ETH/JST sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "sell", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - "order_type": { - "type": "FillOrKill" - }, - "timeout": 2, + log!("Get RICK/MORTY orderbook on Alice side"); + let rc = block_on(mm_alice.rpc(&json! ({ + "userpass": mm_alice.userpass, + "method": "orderbook", + "base": "RICK", + "rel": "MORTY", }))) .unwrap(); - assert!(rc.0.is_success(), "!sell: {}", rc.1); - let sell_json: Json = json::from_str(&rc.1).unwrap(); - let order_type = sell_json["result"]["order_type"]["type"].as_str(); - assert_eq!(order_type, Some("FillOrKill")); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); + log!("Alice orderbook {:?}", alice_orderbook); + let asks = alice_orderbook["asks"].as_array().unwrap(); + assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); + + log!("Issue sell request again on Bob side by setting base/rel price…"); + let rc = block_on(mm_bob.rpc(&set_price_json)).unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); - log!("Wait for 4 seconds for Bob order to be cancelled"); - thread::sleep(Duration::from_secs(4)); + let pause = 2; + log!("Waiting ({} seconds) for Bob to broadcast messages…", pause); + thread::sleep(Duration::from_secs(pause)); + // Bob orderbook must show 1 order + log!("Get RICK/MORTY orderbook on Bob side"); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "my_orders", + "method": "orderbook", + "base": "RICK", + "rel": "MORTY", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let bob_orderbook: Json = json::from_str(&rc.1).unwrap(); + log!("Bob orderbook {:?}", bob_orderbook); + let asks = bob_orderbook["asks"].as_array().unwrap(); + assert_eq!(asks.len(), 1, "Bob RICK/MORTY orderbook must have exactly 1 ask"); + + // Alice orderbook must have 1 order + log!("Get RICK/MORTY orderbook on Alice side"); + let rc = block_on(mm_alice.rpc(&json! ({ + "userpass": mm_alice.userpass, + "method": "orderbook", + "base": "RICK", + "rel": "MORTY", }))) .unwrap(); - assert!(rc.0.is_success(), "!my_orders: {}", rc.1); - let my_orders: Json = json::from_str(&rc.1).unwrap(); - let my_maker_orders: HashMap = json::from_value(my_orders["result"]["maker_orders"].clone()).unwrap(); - let my_taker_orders: HashMap = json::from_value(my_orders["result"]["taker_orders"].clone()).unwrap(); - assert!(my_maker_orders.is_empty(), "maker_orders must be empty"); - assert!(my_taker_orders.is_empty(), "taker_orders must be empty"); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); + log!("Alice orderbook {:?}", alice_orderbook); + let asks = alice_orderbook["asks"].as_array().unwrap(); + assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] -fn test_gtc_taker_order_should_transform_to_maker() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); +fn test_batch_requests() { + let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(),]); + // start bob and immediately place the order let mm_bob = MarketMakerIt::start( json! ({ "gui": "nogui", - "netid": 8999, + "netid": 9998, "dht": "on", // Enable DHT without delay. "myipaddr": env::var ("BOB_TRADE_IP") .ok(), "rpcip": env::var ("BOB_TRADE_IP") .ok(), "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, + "passphrase": "bob passphrase", "coins": coins, - "rpc_password": "password", "i_am_seed": true, + "rpc_password": "pass", }), - "password".into(), + "pass".into(), None, ) .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); - log!("Issue bob ETH/JST sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "sell", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - "order_type": { - "type": "GoodTillCancelled" - }, - "timeout": 2, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let rc_json: Json = json::from_str(&rc.1).unwrap(); - let uuid: Uuid = json::from_value(rc_json["result"]["uuid"].clone()).unwrap(); - - log!("Wait for 4 seconds for Bob order to be converted to maker"); - thread::sleep(Duration::from_secs(4)); - - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "my_orders", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!my_orders: {}", rc.1); - let my_orders: Json = json::from_str(&rc.1).unwrap(); - let my_maker_orders: HashMap = json::from_value(my_orders["result"]["maker_orders"].clone()).unwrap(); - let my_taker_orders: HashMap = json::from_value(my_orders["result"]["taker_orders"].clone()).unwrap(); - assert_eq!(1, my_maker_orders.len(), "maker_orders must have exactly 1 order"); - assert!(my_taker_orders.is_empty(), "taker_orders must be empty"); - let order_path = mm_bob.folder.join(format!( - "DB/{}/ORDERS/MY/MAKER/{}.json", - hex::encode(rmd160_from_passphrase(&bob_passphrase)), - uuid - )); - log!("Order path {}", order_path.display()); - assert!(order_path.exists()); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_set_price_must_save_order_to_db() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); - - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); - - log!("Issue bob ETH/JST sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1 - }))) - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let rc_json: Json = json::from_str(&rc.1).unwrap(); - let uuid: Uuid = json::from_value(rc_json["result"]["uuid"].clone()).unwrap(); - let order_path = mm_bob.folder.join(format!( - "DB/{}/ORDERS/MY/MAKER/{}.json", - hex::encode(rmd160_from_passphrase(&bob_passphrase)), - uuid - )); - assert!(order_path.exists()); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_set_price_response_format() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); - - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); - - log!("Issue bob ETH/JST sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1 - }))) - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let rc_json: Json = json::from_str(&rc.1).unwrap(); - let _: BigDecimal = json::from_value(rc_json["result"]["max_base_vol"].clone()).unwrap(); - let _: BigDecimal = json::from_value(rc_json["result"]["min_base_vol"].clone()).unwrap(); - let _: BigDecimal = json::from_value(rc_json["result"]["price"].clone()).unwrap(); - - let _: BigRational = json::from_value(rc_json["result"]["max_base_vol_rat"].clone()).unwrap(); - let _: BigRational = json::from_value(rc_json["result"]["min_base_vol_rat"].clone()).unwrap(); - let _: BigRational = json::from_value(rc_json["result"]["price_rat"].clone()).unwrap(); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -// https://github.com/KomodoPlatform/atomicDEX-API/issues/635 -fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { - let coins = json!([ - {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, - {"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} - ]); - - // start bob and immediately place the order - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9998, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": "bob passphrase", - "coins": coins, - "i_am_seed": true, - "rpc_password": "pass", - }), - "pass".into(), - None, - ) - .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - // Enable coins on Bob side. Print the replies in case we need the "address". - log!( - "enable_coins (bob): {:?}", - block_on(enable_coins_rick_morty_electrum(&mm_bob)) - ); - - let set_price_json = json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "RICK", - "rel": "MORTY", - "price": 0.9, - "volume": "0.9", - }); - log!("Issue sell request on Bob side by setting base/rel price…"); - let rc = block_on(mm_bob.rpc(&set_price_json)).unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - - let mm_alice = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9998, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), - "rpcip": env::var ("ALICE_TRADE_IP") .ok(), - "passphrase": "alice passphrase", - "coins": coins, - "seednodes": [mm_bob.ip.to_string()], - "rpc_password": "pass", - }), - "pass".into(), - None, - ) - .unwrap(); - - let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); - log!("Alice log path: {}", mm_alice.log_path.display()); - - // Enable coins on Alice side. Print the replies in case we need the "address". - log!( - "enable_coins (alice): {:?}", - block_on(enable_coins_rick_morty_electrum(&mm_alice)) - ); - - log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ - "userpass": mm_alice.userpass, - "method": "orderbook", - "base": "RICK", - "rel": "MORTY", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); - log!("Alice orderbook {:?}", alice_orderbook); - let asks = alice_orderbook["asks"].as_array().unwrap(); - assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); - - log!("Issue sell request again on Bob side by setting base/rel price…"); - let rc = block_on(mm_bob.rpc(&set_price_json)).unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - - let pause = 2; - log!("Waiting ({} seconds) for Bob to broadcast messages…", pause); - thread::sleep(Duration::from_secs(pause)); - - // Bob orderbook must show 1 order - log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "orderbook", - "base": "RICK", - "rel": "MORTY", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let bob_orderbook: Json = json::from_str(&rc.1).unwrap(); - log!("Bob orderbook {:?}", bob_orderbook); - let asks = bob_orderbook["asks"].as_array().unwrap(); - assert_eq!(asks.len(), 1, "Bob RICK/MORTY orderbook must have exactly 1 ask"); - - // Alice orderbook must have 1 order - log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ - "userpass": mm_alice.userpass, - "method": "orderbook", - "base": "RICK", - "rel": "MORTY", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); - log!("Alice orderbook {:?}", alice_orderbook); - let asks = alice_orderbook["asks"].as_array().unwrap(); - assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_batch_requests() { - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); - - // start bob and immediately place the order - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9998, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": "bob passphrase", - "coins": coins, - "i_am_seed": true, - "rpc_password": "pass", - }), - "pass".into(), - None, - ) - .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - - let batch_json = json!([ - { - "userpass": mm_bob.userpass, - "method": "electrum", - "coin": "RICK", - "servers": doc_electrums(), - "mm2": 1, + let batch_json = json!([ + { + "userpass": mm_bob.userpass, + "method": "electrum", + "coin": "RICK", + "servers": doc_electrums(), + "mm2": 1, }, { "userpass": mm_bob.userpass, @@ -3769,10 +3331,9 @@ fn test_convert_qrc20_address() { } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] fn test_validateaddress() { - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf()]); let (bob_file_passphrase, _bob_file_userpass) = from_env_file(slurp(&".env.seed").unwrap()); let bob_passphrase = var("BOB_PASSPHRASE") @@ -4089,7 +3650,6 @@ fn qrc20_activate_electrum() { } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] fn test_qrc20_withdraw() { // corresponding private key: [3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49] @@ -4721,13 +4281,14 @@ fn test_tx_history_tbtc_non_segwit() { } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] -fn test_buy_conf_settings() { +fn test_update_maker_order() { let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), - {"coin":"JST","name":"jst","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address":ETH_DEV_TOKEN_CONTRACT}},"required_confirmations":2},]); + let coins = json! ([ + {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, + {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} + ]); let mm_bob = MarketMakerIt::start( json! ({ @@ -4749,109 +4310,125 @@ fn test_buy_conf_settings() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); + log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); - log!("Issue bob buy request"); - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue bob sell request"); + let setprice = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "buy", - "base": "ETH", - "rel": "JST", + "method": "setprice", + "base": "RICK", + "rel": "MORTY", "price": 1, - "volume": 0.1, + "volume": 2, + "min_volume": 1, "base_confs": 5, "base_nota": true, "rel_confs": 4, "rel_nota": false, }))) .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); - let json: Json = json::from_str(&rc.1).unwrap(); - assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(5)); - assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(true)); - assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(4)); - assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); + assert!(setprice.0.is_success(), "!setprice: {}", setprice.1); + let setprice_json: Json = json::from_str(&setprice.1).unwrap(); + let uuid: Uuid = json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); - // must use coin config as defaults if not set in request - log!("Issue bob buy request"); - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue bob update maker order request"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "buy", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, + "method": "update_maker_order", + "uuid": uuid, + "new_price": 2, }))) .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); - let json: Json = json::from_str(&rc.1).unwrap(); - assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(1)); - assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(false)); - assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(2)); - assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_buy_response_format() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); + assert!( + update_maker_order.0.is_success(), + "!update_maker_order: {}", + update_maker_order.1 + ); + let update_maker_order_json: Json = json::from_str(&update_maker_order.1).unwrap(); + assert_eq!(update_maker_order_json["result"]["price"], Json::from("2")); + assert_eq!(update_maker_order_json["result"]["max_base_vol"], Json::from("2")); + assert_eq!(update_maker_order_json["result"]["min_base_vol"], Json::from("1")); - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + log!("Issue another bob update maker order request"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "update_maker_order", + "uuid": uuid, + "volume_delta": 2, + }))) + .unwrap(); + assert!( + update_maker_order.0.is_success(), + "!update_maker_order: {}", + update_maker_order.1 + ); + let update_maker_order_json: Json = json::from_str(&update_maker_order.1).unwrap(); + assert_eq!(update_maker_order_json["result"]["price"], Json::from("2")); + assert_eq!(update_maker_order_json["result"]["max_base_vol"], Json::from("4")); + assert_eq!(update_maker_order_json["result"]["min_base_vol"], Json::from("1")); - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) + log!("Get bob balance"); + let my_balance = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "my_balance", + "coin": "RICK", + }))) .unwrap(); + assert!(my_balance.0.is_success(), "!my_balance: {}", my_balance.1); + let my_balance_json: Json = json::from_str(&my_balance.1).unwrap(); + let balance: BigDecimal = json::from_value(my_balance_json["balance"].clone()).unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); + log!("Get RICK trade fee"); + let trade_preimage = block_on(mm_bob.rpc(&json!({ + "userpass": mm_bob.userpass, + "mmrpc": "2.0", + "method": "trade_preimage", + "params": { + "base": "RICK", + "rel": "MORTY", + "swap_method": "setprice", + "price": 2, + "max": true, + }, + }))) + .unwrap(); + assert!(trade_preimage.0.is_success(), "!trade_preimage: {}", trade_preimage.1); + let get_trade_fee_json: Json = json::from_str(&trade_preimage.1).unwrap(); + let trade_fee: BigDecimal = + json::from_value(get_trade_fee_json["result"]["base_coin_fee"]["amount"].clone()).unwrap(); + let max_volume = balance - trade_fee; - log!("Issue bob buy request"); - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue another bob update maker order request"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "buy", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - "base_confs": 5, - "base_nota": true, - "rel_confs": 4, - "rel_nota": false, + "method": "update_maker_order", + "uuid": uuid, + "max": true, }))) .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); - let _: BuyOrSellRpcResult = json::from_str(&rc.1).unwrap(); + assert!( + update_maker_order.0.is_success(), + "!update_maker_order: {}", + update_maker_order.1 + ); + let update_maker_order_json: Json = json::from_str(&update_maker_order.1).unwrap(); + let max_base_vol = + BigDecimal::from_str(update_maker_order_json["result"]["max_base_vol"].as_str().unwrap()).unwrap(); + assert_eq!(update_maker_order_json["result"]["price"], Json::from("2")); + assert_eq!(max_base_vol, max_volume); + + block_on(mm_bob.stop()).unwrap(); } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] -fn test_sell_response_format() { +fn test_update_maker_order_fail() { let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + let coins = json! ([ + {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, + {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} + ]); let mm_bob = MarketMakerIt::start( json! ({ @@ -4873,17 +4450,14 @@ fn test_sell_response_format() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); + log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ + let setprice = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "sell", - "base": "ETH", - "rel": "JST", + "method": "setprice", + "base": "RICK", + "rel": "MORTY", "price": 1, "volume": 0.1, "base_confs": 5, @@ -4892,1242 +4466,372 @@ fn test_sell_response_format() { "rel_nota": false, }))) .unwrap(); - assert!(rc.0.is_success(), "!sell: {}", rc.1); - let _: BuyOrSellRpcResult = json::from_str(&rc.1).unwrap(); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_my_orders_response_format() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + assert!(setprice.0.is_success(), "!setprice: {}", setprice.1); + let setprice_json: Json = json::from_str(&setprice.1).unwrap(); + let uuid: Uuid = json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) + log!("Issue bob update maker order request that should fail because price is too low"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "update_maker_order", + "uuid": uuid, + "new_price": 0.0000000099, + }))) .unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + assert!( + !update_maker_order.0.is_success(), + "update_maker_order success, but should be error {}", + update_maker_order.1 ); - log!("Issue bob buy request"); - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue bob update maker order request that should fail because New Volume is Less than Zero"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "buy", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - "base_confs": 5, - "base_nota": true, - "rel_confs": 4, - "rel_nota": false, + "method": "update_maker_order", + "uuid": uuid, + "volume_delta": -0.11, }))) .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); + assert!( + !update_maker_order.0.is_success(), + "update_maker_order success, but should be error {}", + update_maker_order.1 + ); - log!("Issue bob setprice request"); - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue bob update maker order request that should fail because Min base vol is too low"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "setprice", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - "base_confs": 5, - "base_nota": true, - "rel_confs": 4, - "rel_nota": false, + "method": "update_maker_order", + "uuid": uuid, + "new_price": 2, + "min_volume": 0.000099, }))) .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); + assert!( + !update_maker_order.0.is_success(), + "update_maker_order success, but should be error {}", + update_maker_order.1 + ); - log!("Issue bob my_orders request"); - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue bob update maker order request that should fail because Max base vol is below Min base vol"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "my_orders", + "method": "update_maker_order", + "uuid": uuid, + "volume_delta": -0.0999, + "min_volume": 0.0002, }))) .unwrap(); - assert!(rc.0.is_success(), "!my_orders: {}", rc.1); - - let _: MyOrdersRpcResult = json::from_str(&rc.1).unwrap(); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_my_orders_after_matched() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let alice_passphrase = get_passphrase(&".env.client", "ALICE_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + assert!( + !update_maker_order.0.is_success(), + "update_maker_order success, but should be error {}", + update_maker_order.1 + ); - let mut mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9000, - "dht": "on", // Enable DHT without delay. - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "pass", - "i_am_seed": true, - }), - "pass".to_string(), - None, - ) - .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - - let mut mm_alice = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9000, - "dht": "on", // Enable DHT without delay. - "passphrase": alice_passphrase, - "coins": coins, - "rpc_password": "pass", - "seednodes": vec![format!("{}", mm_bob.ip)], - }), - "pass".to_string(), - None, - ) - .unwrap(); - let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); - - // Enable coins on Bob side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); - log!("enable_coins (bob): {:?}", rc); - // Enable coins on Alice side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); - log!("enable_coins (alice): {:?}", rc); - - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.000001, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - - let rc = block_on(mm_alice.rpc(&json! ({ - "userpass": mm_alice.userpass, - "method": "buy", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.000001, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); - - block_on(mm_bob.wait_for_log(22., |log| log.contains("Entering the maker_swap_loop ETH/JST"))).unwrap(); - block_on(mm_alice.wait_for_log(22., |log| log.contains("Entering the taker_swap_loop ETH/JST"))).unwrap(); - - log!("Issue bob my_orders request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "my_orders", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!my_orders: {}", rc.1); - - let _: MyOrdersRpcResult = json::from_str(&rc.1).unwrap(); - block_on(mm_bob.stop()).unwrap(); - block_on(mm_alice.stop()).unwrap(); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_sell_conf_settings() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), - {"coin":"JST","name":"jst","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address": ETH_DEV_TOKEN_CONTRACT}},"required_confirmations":2},]); - - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); - - log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "sell", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - "base_confs": 5, - "base_nota": true, - "rel_confs": 4, - "rel_nota": false, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!sell: {}", rc.1); - let json: Json = json::from_str(&rc.1).unwrap(); - assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(5)); - assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(true)); - assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(4)); - assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); - - // must use coin config as defaults if not set in request - log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "sell", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!sell: {}", rc.1); - let json: Json = json::from_str(&rc.1).unwrap(); - assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(1)); - assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(false)); - assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(2)); - assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_set_price_conf_settings() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), - {"coin":"JST","name":"jst","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address": ETH_DEV_TOKEN_CONTRACT}},"required_confirmations":2},]); - - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); - - log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - "base_confs": 5, - "base_nota": true, - "rel_confs": 4, - "rel_nota": false, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let json: Json = json::from_str(&rc.1).unwrap(); - assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(5)); - assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(true)); - assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(4)); - assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); - - // must use coin config as defaults if not set in request - log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.1, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let json: Json = json::from_str(&rc.1).unwrap(); - assert_eq!(json["result"]["conf_settings"]["base_confs"], Json::from(1)); - assert_eq!(json["result"]["conf_settings"]["base_nota"], Json::from(false)); - assert_eq!(json["result"]["conf_settings"]["rel_confs"], Json::from(2)); - assert_eq!(json["result"]["conf_settings"]["rel_nota"], Json::from(false)); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_update_maker_order() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json! ([ - {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, - {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} - ]); - - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); - - log!("Issue bob sell request"); - let setprice = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "RICK", - "rel": "MORTY", - "price": 1, - "volume": 2, - "min_volume": 1, - "base_confs": 5, - "base_nota": true, - "rel_confs": 4, - "rel_nota": false, - }))) - .unwrap(); - assert!(setprice.0.is_success(), "!setprice: {}", setprice.1); - let setprice_json: Json = json::from_str(&setprice.1).unwrap(); - let uuid: Uuid = json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); - - log!("Issue bob update maker order request"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 2, - }))) - .unwrap(); - assert!( - update_maker_order.0.is_success(), - "!update_maker_order: {}", - update_maker_order.1 - ); - let update_maker_order_json: Json = json::from_str(&update_maker_order.1).unwrap(); - assert_eq!(update_maker_order_json["result"]["price"], Json::from("2")); - assert_eq!(update_maker_order_json["result"]["max_base_vol"], Json::from("2")); - assert_eq!(update_maker_order_json["result"]["min_base_vol"], Json::from("1")); - - log!("Issue another bob update maker order request"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "volume_delta": 2, - }))) - .unwrap(); - assert!( - update_maker_order.0.is_success(), - "!update_maker_order: {}", - update_maker_order.1 - ); - let update_maker_order_json: Json = json::from_str(&update_maker_order.1).unwrap(); - assert_eq!(update_maker_order_json["result"]["price"], Json::from("2")); - assert_eq!(update_maker_order_json["result"]["max_base_vol"], Json::from("4")); - assert_eq!(update_maker_order_json["result"]["min_base_vol"], Json::from("1")); - - log!("Get bob balance"); - let my_balance = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "my_balance", - "coin": "RICK", - }))) - .unwrap(); - assert!(my_balance.0.is_success(), "!my_balance: {}", my_balance.1); - let my_balance_json: Json = json::from_str(&my_balance.1).unwrap(); - let balance: BigDecimal = json::from_value(my_balance_json["balance"].clone()).unwrap(); - - log!("Get RICK trade fee"); - let trade_preimage = block_on(mm_bob.rpc(&json!({ - "userpass": mm_bob.userpass, - "mmrpc": "2.0", - "method": "trade_preimage", - "params": { - "base": "RICK", - "rel": "MORTY", - "swap_method": "setprice", - "price": 2, - "max": true, - }, - }))) - .unwrap(); - assert!(trade_preimage.0.is_success(), "!trade_preimage: {}", trade_preimage.1); - let get_trade_fee_json: Json = json::from_str(&trade_preimage.1).unwrap(); - let trade_fee: BigDecimal = - json::from_value(get_trade_fee_json["result"]["base_coin_fee"]["amount"].clone()).unwrap(); - let max_volume = balance - trade_fee; - - log!("Issue another bob update maker order request"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "max": true, - }))) - .unwrap(); - assert!( - update_maker_order.0.is_success(), - "!update_maker_order: {}", - update_maker_order.1 - ); - let update_maker_order_json: Json = json::from_str(&update_maker_order.1).unwrap(); - let max_base_vol = - BigDecimal::from_str(update_maker_order_json["result"]["max_base_vol"].as_str().unwrap()).unwrap(); - assert_eq!(update_maker_order_json["result"]["price"], Json::from("2")); - assert_eq!(max_base_vol, max_volume); - - block_on(mm_bob.stop()).unwrap(); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_update_maker_order_fail() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json! ([ - {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, - {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} - ]); - - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); - - log!("Issue bob sell request"); - let setprice = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "RICK", - "rel": "MORTY", - "price": 1, - "volume": 0.1, - "base_confs": 5, - "base_nota": true, - "rel_confs": 4, - "rel_nota": false, - }))) - .unwrap(); - assert!(setprice.0.is_success(), "!setprice: {}", setprice.1); - let setprice_json: Json = json::from_str(&setprice.1).unwrap(); - let uuid: Uuid = json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); - - log!("Issue bob update maker order request that should fail because price is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 0.0000000099, - }))) - .unwrap(); - assert!( - !update_maker_order.0.is_success(), - "update_maker_order success, but should be error {}", - update_maker_order.1 - ); - - log!("Issue bob update maker order request that should fail because New Volume is Less than Zero"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "volume_delta": -0.11, - }))) - .unwrap(); - assert!( - !update_maker_order.0.is_success(), - "update_maker_order success, but should be error {}", - update_maker_order.1 - ); - - log!("Issue bob update maker order request that should fail because Min base vol is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 2, - "min_volume": 0.000099, - }))) - .unwrap(); - assert!( - !update_maker_order.0.is_success(), - "update_maker_order success, but should be error {}", - update_maker_order.1 - ); - - log!("Issue bob update maker order request that should fail because Max base vol is below Min base vol"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "volume_delta": -0.0999, - "min_volume": 0.0002, - }))) - .unwrap(); - assert!( - !update_maker_order.0.is_success(), - "update_maker_order success, but should be error {}", - update_maker_order.1 - ); - - log!("Issue bob update maker order request that should fail because Max base vol is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 2, - "volume_delta": -0.099901, - }))) - .unwrap(); - assert!( - !update_maker_order.0.is_success(), - "update_maker_order success, but should be error {}", - update_maker_order.1 - ); - - log!("Issue bob update maker order request that should fail because Max rel vol is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 0.5, - "volume_delta": -0.099802, - }))) - .unwrap(); - assert!( - !update_maker_order.0.is_success(), - "update_maker_order success, but should be error {}", - update_maker_order.1 - ); - - log!("Issue bob batch of 2 update maker order requests that should make the second request fail because the order state changed due to the first request"); - let batch_json = json!([ - { - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 3, - "volume_delta": 1, - }, - { - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 2, - "volume_delta": 1, - }, - ]); - - let rc = block_on(mm_bob.rpc(&batch_json)).unwrap(); - assert!(rc.0.is_success(), "!batch: {}", rc.1); - log!("{}", rc.1); - let err_msg = "Order state has changed after price/volume/balance checks. Please try to update the order again if it's still needed."; - let responses = json::from_str::>(&rc.1).unwrap(); - if responses[0].get("error").is_some() { - assert!(responses[0]["error"].as_str().unwrap().contains(err_msg)); - assert!(responses[1].get("result").is_some()); - } else if responses[1].get("error").is_some() { - assert!(responses[0].get("result").is_some()); - assert!(responses[1]["error"].as_str().unwrap().contains(err_msg)); - } - - log!("Issue bob batch update maker order and cancel order request that should make update maker order fail because Order with UUID has been deleted"); - let batch_json = json!([ - { - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "new_price": 1, - "volume_delta": 2.9, - }, - { - "userpass": mm_bob.userpass, - "method": "cancel_order", - "uuid": uuid, - }, - ]); - - let rc = block_on(mm_bob.rpc(&batch_json)).unwrap(); - assert!(rc.0.is_success(), "!batch: {}", rc.1); - log!("{}", rc.1); - let err_msg = format!("Order with UUID: {} has been deleted", uuid); - let responses = json::from_str::>(&rc.1).unwrap(); - if responses[0].get("error").is_some() { - assert!(responses[0]["error"].as_str().unwrap().contains(&err_msg)); - assert!(responses[1].get("result").is_some()); - } else if responses[1].get("error").is_some() { - assert!(responses[0].get("result").is_some()); - assert!(responses[1]["error"].as_str().unwrap().contains(&err_msg)); - } - - block_on(mm_bob.stop()).unwrap(); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_update_maker_order_after_matched() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let alice_passphrase = get_passphrase(&".env.client", "ALICE_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); - - let mut mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9000, - "dht": "on", // Enable DHT without delay. - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "pass", - "i_am_seed": true, - }), - "pass".to_string(), - None, - ) - .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - - let mut mm_alice = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9000, - "dht": "on", // Enable DHT without delay. - "passphrase": alice_passphrase, - "coins": coins, - "rpc_password": "pass", - "seednodes": vec![format!("{}", mm_bob.ip)], - }), - "pass".to_string(), - None, - ) - .unwrap(); - let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); - - // Enable coins on Bob side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); - log!("enable_coins (bob): {:?}", rc); - // Enable coins on Alice side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); - log!("enable_coins (alice): {:?}", rc); - - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.00002, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let setprice_json: Json = json::from_str(&rc.1).unwrap(); - let uuid: Uuid = json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); - - let rc = block_on(mm_alice.rpc(&json! ({ - "userpass": mm_alice.userpass, - "method": "buy", - "base": "ETH", - "rel": "JST", - "price": 1, - "volume": 0.00001, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); - - block_on(mm_bob.wait_for_log(22., |log| log.contains("Entering the maker_swap_loop ETH/JST"))).unwrap(); - block_on(mm_alice.wait_for_log(22., |log| log.contains("Entering the taker_swap_loop ETH/JST"))).unwrap(); - - log!("Issue bob update maker order request that should fail because new volume is less than reserved amount"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "volume_delta": -0.00002, - }))) - .unwrap(); - assert!( - !update_maker_order.0.is_success(), - "update_maker_order success, but should be error {}", - update_maker_order.1 - ); - - log!("Issue another bob update maker order request"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "update_maker_order", - "uuid": uuid, - "volume_delta": 0.00001, - }))) - .unwrap(); - assert!( - update_maker_order.0.is_success(), - "!update_maker_order: {}", - update_maker_order.1 - ); - let update_maker_order_json: Json = json::from_str(&update_maker_order.1).unwrap(); - log!("{}", update_maker_order.1); - assert_eq!(update_maker_order_json["result"]["max_base_vol"], Json::from("0.00003")); - - log!("Issue bob my_orders request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "my_orders", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!my_orders: {}", rc.1); - - let _: MyOrdersRpcResult = json::from_str(&rc.1).unwrap(); - block_on(mm_bob.stop()).unwrap(); - block_on(mm_alice.stop()).unwrap(); -} - -// https://github.com/KomodoPlatform/atomicDEX-API/issues/683 -// trade fee should return numbers in all 3 available formats and -// "amount" must be always in decimal representation for backwards compatibility -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_trade_fee_returns_numbers_in_various_formats() { - let coins = json!([ - {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, - {"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} - ]); - - // start bob and immediately place the order - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9998, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": "bob passphrase", - "coins": coins, - "i_am_seed": true, - "rpc_password": "pass", - }), - "pass".into(), - None, - ) - .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - block_on(enable_coins_rick_morty_electrum(&mm_bob)); - - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "get_trade_fee", - "coin": "RICK", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!get_trade_fee: {}", rc.1); - let trade_fee_json: Json = json::from_str(&rc.1).unwrap(); - let _amount_dec: BigDecimal = json::from_value(trade_fee_json["result"]["amount"].clone()).unwrap(); - let _amount_rat: BigRational = json::from_value(trade_fee_json["result"]["amount_rat"].clone()).unwrap(); - let _amount_fraction: Fraction = json::from_value(trade_fee_json["result"]["amount_fraction"].clone()).unwrap(); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_orderbook_is_mine_orders() { - let coins = json!([{"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, - {"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} - ]); - - // start bob and immediately place the order - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9998, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": "bob passphrase", - "coins": coins, - "i_am_seed": true, - "rpc_password": "pass", - }), - "pass".into(), - None, - ) - .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - // Enable coins on Bob side. Print the replies in case we need the "address". - log!( - "enable_coins (bob): {:?}", - block_on(enable_coins_rick_morty_electrum(&mm_bob)) - ); - - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue bob update maker order request that should fail because Max base vol is too low"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "setprice", - "base": "RICK", - "rel": "MORTY", - "price": 0.9, - "volume": "0.9", + "method": "update_maker_order", + "uuid": uuid, + "new_price": 2, + "volume_delta": -0.099901, }))) .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let _bob_setprice: Json = json::from_str(&rc.1).unwrap(); - - let mm_alice = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9998, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), - "rpcip": env::var ("ALICE_TRADE_IP") .ok(), - "passphrase": "alice passphrase", - "coins": coins, - "seednodes": [mm_bob.ip.to_string()], - "rpc_password": "pass", - }), - "pass".into(), - None, - ) - .unwrap(); - - let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); - log!("Alice log path: {}", mm_alice.log_path.display()); - - // Enable coins on Alice side. Print the replies in case we need the "address". - log!( - "enable_coins (alice): {:?}", - block_on(enable_coins_rick_morty_electrum(&mm_alice)) + assert!( + !update_maker_order.0.is_success(), + "update_maker_order success, but should be error {}", + update_maker_order.1 ); - // Bob orderbook must show 1 mine order - log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "orderbook", - "base": "RICK", - "rel": "MORTY", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let bob_orderbook: Json = json::from_str(&rc.1).unwrap(); - log!("Bob orderbook {:?}", bob_orderbook); - let asks = bob_orderbook["asks"].as_array().unwrap(); - assert_eq!(asks.len(), 1, "Bob RICK/MORTY orderbook must have exactly 1 ask"); - let is_mine = asks[0]["is_mine"].as_bool().unwrap(); - assert!(is_mine); - - // Alice orderbook must show 1 not-mine order - log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ - "userpass": mm_alice.userpass, - "method": "orderbook", - "base": "RICK", - "rel": "MORTY", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); - log!("Alice orderbook {:?}", alice_orderbook); - let asks = alice_orderbook["asks"].as_array().unwrap(); - assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); - let is_mine = asks[0]["is_mine"].as_bool().unwrap(); - assert!(!is_mine); - - // make another order by Alice - let rc = block_on(mm_alice.rpc(&json! ({ - "userpass": mm_alice.userpass, - "method": "setprice", - "base": "RICK", - "rel": "MORTY", - "price": 1, - "volume": 0.1, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); - - log!("Give Bob 2 seconds to import the order…"); - thread::sleep(Duration::from_secs(2)); - - // Bob orderbook must show 1 mine and 1 non-mine orders. - // Request orderbook with reverse base and rel coins to check bids instead of asks - log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "orderbook", - "base": "MORTY", - "rel": "RICK", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let bob_orderbook: Json = json::from_str(&rc.1).unwrap(); - log!("Bob orderbook {:?}", bob_orderbook); - let asks = bob_orderbook["asks"].as_array().unwrap(); - let bids = bob_orderbook["bids"].as_array().unwrap(); - assert!(asks.is_empty(), "Bob MORTY/RICK orderbook must contain an empty asks"); - assert_eq!(bids.len(), 2, "Bob MORTY/RICK orderbook must have exactly 2 bids"); - let mine_orders = bids.iter().filter(|bid| bid["is_mine"].as_bool().unwrap()).count(); - assert_eq!(mine_orders, 1, "Bob RICK/MORTY orderbook must have exactly 1 mine bid"); - - // Alice orderbook must show 1 mine and 1 non-mine orders - log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_bob.rpc(&json! ({ + log!("Issue bob update maker order request that should fail because Max rel vol is too low"); + let update_maker_order = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "orderbook", - "base": "RICK", - "rel": "MORTY", + "method": "update_maker_order", + "uuid": uuid, + "new_price": 0.5, + "volume_delta": -0.099802, }))) .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); - log!("Alice orderbook {:?}", alice_orderbook); - let asks = alice_orderbook["asks"].as_array().unwrap(); - let bids = alice_orderbook["bids"].as_array().unwrap(); - assert!(bids.is_empty(), "Alice MORTY/RICK orderbook must contain an empty bids"); - assert_eq!(asks.len(), 2, "Alice MORTY/RICK orderbook must have exactly 2 asks"); - let mine_orders = asks.iter().filter(|ask| ask["is_mine"].as_bool().unwrap()).count(); - assert_eq!( - mine_orders, 1, - "Alice RICK/MORTY orderbook must have exactly 1 mine bid" + assert!( + !update_maker_order.0.is_success(), + "update_maker_order success, but should be error {}", + update_maker_order.1 ); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_sell_min_volume() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); - - let mm_bob = MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .unwrap(); + log!("Issue bob batch of 2 update maker order requests that should make the second request fail because the order state changed due to the first request"); + let batch_json = json!([ + { + "userpass": mm_bob.userpass, + "method": "update_maker_order", + "uuid": uuid, + "new_price": 3, + "volume_delta": 1, + }, + { + "userpass": mm_bob.userpass, + "method": "update_maker_order", + "uuid": uuid, + "new_price": 2, + "volume_delta": 1, + }, + ]); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) - ); + let rc = block_on(mm_bob.rpc(&batch_json)).unwrap(); + assert!(rc.0.is_success(), "!batch: {}", rc.1); + log!("{}", rc.1); + let err_msg = "Order state has changed after price/volume/balance checks. Please try to update the order again if it's still needed."; + let responses = json::from_str::>(&rc.1).unwrap(); + if responses[0].get("error").is_some() { + assert!(responses[0]["error"].as_str().unwrap().contains(err_msg)); + assert!(responses[1].get("result").is_some()); + } else if responses[1].get("error").is_some() { + assert!(responses[0].get("result").is_some()); + assert!(responses[1]["error"].as_str().unwrap().contains(err_msg)); + } - let min_volume: BigDecimal = "0.1".parse().unwrap(); - log!("Issue bob ETH/JST sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "sell", - "base": "ETH", - "rel": "JST", - "price": "1", - "volume": "1", - "min_volume": min_volume, - "order_type": { - "type": "GoodTillCancelled" + log!("Issue bob batch update maker order and cancel order request that should make update maker order fail because Order with UUID has been deleted"); + let batch_json = json!([ + { + "userpass": mm_bob.userpass, + "method": "update_maker_order", + "uuid": uuid, + "new_price": 1, + "volume_delta": 2.9, }, - "timeout": 2, - }))) - .unwrap(); - assert!(rc.0.is_success(), "!sell: {}", rc.1); - let rc_json: Json = json::from_str(&rc.1).unwrap(); - let uuid: Uuid = json::from_value(rc_json["result"]["uuid"].clone()).unwrap(); - let min_volume_response: BigDecimal = json::from_value(rc_json["result"]["min_volume"].clone()).unwrap(); - assert_eq!(min_volume, min_volume_response); + { + "userpass": mm_bob.userpass, + "method": "cancel_order", + "uuid": uuid, + }, + ]); - log!("Wait for 4 seconds for Bob order to be converted to maker"); - thread::sleep(Duration::from_secs(4)); + let rc = block_on(mm_bob.rpc(&batch_json)).unwrap(); + assert!(rc.0.is_success(), "!batch: {}", rc.1); + log!("{}", rc.1); + let err_msg = format!("Order with UUID: {} has been deleted", uuid); + let responses = json::from_str::>(&rc.1).unwrap(); + if responses[0].get("error").is_some() { + assert!(responses[0]["error"].as_str().unwrap().contains(&err_msg)); + assert!(responses[1].get("result").is_some()); + } else if responses[1].get("error").is_some() { + assert!(responses[0].get("result").is_some()); + assert!(responses[1]["error"].as_str().unwrap().contains(&err_msg)); + } - let rc = block_on(mm_bob.rpc(&json! ({ - "userpass": mm_bob.userpass, - "method": "my_orders", - }))) - .unwrap(); - assert!(rc.0.is_success(), "!my_orders: {}", rc.1); - let my_orders: Json = json::from_str(&rc.1).unwrap(); - let my_maker_orders: HashMap = json::from_value(my_orders["result"]["maker_orders"].clone()).unwrap(); - let my_taker_orders: HashMap = json::from_value(my_orders["result"]["taker_orders"].clone()).unwrap(); - assert_eq!(1, my_maker_orders.len(), "maker_orders must have exactly 1 order"); - assert!(my_taker_orders.is_empty(), "taker_orders must be empty"); - let maker_order = my_maker_orders.get(&uuid).unwrap(); - let min_volume_maker: BigDecimal = json::from_value(maker_order["min_base_vol"].clone()).unwrap(); - assert_eq!(min_volume, min_volume_maker); + block_on(mm_bob.stop()).unwrap(); } +// https://github.com/KomodoPlatform/atomicDEX-API/issues/683 +// trade fee should return numbers in all 3 available formats and +// "amount" must be always in decimal representation for backwards compatibility #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] -fn test_sell_min_volume_dust() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json! ([ - {"coin":"RICK","asset":"RICK","dust":10000000,"required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, - {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} +fn test_trade_fee_returns_numbers_in_various_formats() { + let coins = json!([ + {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, + {"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} ]); + // start bob and immediately place the order let mm_bob = MarketMakerIt::start( json! ({ "gui": "nogui", - "netid": 8999, + "netid": 9998, "dht": "on", // Enable DHT without delay. "myipaddr": env::var ("BOB_TRADE_IP") .ok(), "rpcip": env::var ("BOB_TRADE_IP") .ok(), "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, + "passphrase": "bob passphrase", "coins": coins, - "rpc_password": "password", "i_am_seed": true, + "rpc_password": "pass", }), - "password".into(), + "pass".into(), None, ) .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); + block_on(enable_coins_rick_morty_electrum(&mm_bob)); - log!("Issue bob RICK/MORTY sell request"); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "sell", - "base": "RICK", - "rel": "MORTY", - "price": "1", - "volume": "1", - "order_type": { - "type": "FillOrKill" - } + "method": "get_trade_fee", + "coin": "RICK", }))) .unwrap(); - assert!(rc.0.is_success(), "!sell: {}", rc.1); - let response: BuyOrSellRpcResult = json::from_str(&rc.1).unwrap(); - let expected_min = BigDecimal::from(1); - assert_eq!(response.result.min_volume, expected_min); + assert!(rc.0.is_success(), "!get_trade_fee: {}", rc.1); + let trade_fee_json: Json = json::from_str(&rc.1).unwrap(); + let _amount_dec: BigDecimal = json::from_value(trade_fee_json["result"]["amount"].clone()).unwrap(); + let _amount_rat: BigRational = json::from_value(trade_fee_json["result"]["amount_rat"].clone()).unwrap(); + let _amount_fraction: Fraction = json::from_value(trade_fee_json["result"]["amount_fraction"].clone()).unwrap(); } #[test] #[cfg(not(target_arch = "wasm32"))] -fn test_setprice_min_volume_dust() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json! ([ - {"coin":"RICK","asset":"RICK","dust":10000000,"required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, - {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} +fn test_orderbook_is_mine_orders() { + let coins = json!([{"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, + {"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} ]); + // start bob and immediately place the order let mm_bob = MarketMakerIt::start( json! ({ "gui": "nogui", - "netid": 8999, + "netid": 9998, "dht": "on", // Enable DHT without delay. "myipaddr": env::var ("BOB_TRADE_IP") .ok(), "rpcip": env::var ("BOB_TRADE_IP") .ok(), "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, + "passphrase": "bob passphrase", "coins": coins, - "rpc_password": "password", "i_am_seed": true, + "rpc_password": "pass", }), - "password".into(), + "pass".into(), None, ) .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); + // Enable coins on Bob side. Print the replies in case we need the "address". + log!( + "enable_coins (bob): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_bob)) + ); - log!("Issue bob RICK/MORTY sell request"); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", "rel": "MORTY", - "price": "1", - "volume": "1", + "price": 0.9, + "volume": "0.9", }))) .unwrap(); assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let response: SetPriceResponse = json::from_str(&rc.1).unwrap(); - let expected_min = BigDecimal::from(1); - assert_eq!(expected_min, response.result.min_base_vol); -} - -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_buy_min_volume() { - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + let _bob_setprice: Json = json::from_str(&rc.1).unwrap(); - let mm_bob = MarketMakerIt::start( + let mm_alice = MarketMakerIt::start( json! ({ "gui": "nogui", - "netid": 8999, + "netid": 9998, "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, + "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), + "rpcip": env::var ("ALICE_TRADE_IP") .ok(), + "passphrase": "alice passphrase", "coins": coins, - "rpc_password": "password", - "i_am_seed": true, + "seednodes": [mm_bob.ip.to_string()], + "rpc_password": "pass", }), - "password".into(), + "pass".into(), None, ) .unwrap(); - thread::sleep(Duration::from_secs(2)); - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + // Enable coins on Alice side. Print the replies in case we need the "address". log!( - "{:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + "enable_coins (alice): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_alice)) ); - let min_volume: BigDecimal = "0.1".parse().unwrap(); - log!("Issue bob ETH/JST sell request"); + // Bob orderbook must show 1 mine order + log!("Get RICK/MORTY orderbook on Bob side"); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "buy", - "base": "ETH", - "rel": "JST", - "price": "2", - "volume": "1", - "min_volume": min_volume, - "order_type": { - "type": "GoodTillCancelled" - }, - "timeout": 2, + "method": "orderbook", + "base": "RICK", + "rel": "MORTY", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let bob_orderbook: Json = json::from_str(&rc.1).unwrap(); + log!("Bob orderbook {:?}", bob_orderbook); + let asks = bob_orderbook["asks"].as_array().unwrap(); + assert_eq!(asks.len(), 1, "Bob RICK/MORTY orderbook must have exactly 1 ask"); + let is_mine = asks[0]["is_mine"].as_bool().unwrap(); + assert!(is_mine); + + // Alice orderbook must show 1 not-mine order + log!("Get RICK/MORTY orderbook on Alice side"); + let rc = block_on(mm_alice.rpc(&json! ({ + "userpass": mm_alice.userpass, + "method": "orderbook", + "base": "RICK", + "rel": "MORTY", + }))) + .unwrap(); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); + log!("Alice orderbook {:?}", alice_orderbook); + let asks = alice_orderbook["asks"].as_array().unwrap(); + assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); + let is_mine = asks[0]["is_mine"].as_bool().unwrap(); + assert!(!is_mine); + + // make another order by Alice + let rc = block_on(mm_alice.rpc(&json! ({ + "userpass": mm_alice.userpass, + "method": "setprice", + "base": "RICK", + "rel": "MORTY", + "price": 1, + "volume": 0.1, + }))) + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + + log!("Give Bob 2 seconds to import the order…"); + thread::sleep(Duration::from_secs(2)); + + // Bob orderbook must show 1 mine and 1 non-mine orders. + // Request orderbook with reverse base and rel coins to check bids instead of asks + log!("Get RICK/MORTY orderbook on Bob side"); + let rc = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "orderbook", + "base": "MORTY", + "rel": "RICK", }))) .unwrap(); - assert!(rc.0.is_success(), "!sell: {}", rc.1); - let response: BuyOrSellRpcResult = json::from_str(&rc.1).unwrap(); - assert_eq!(min_volume, response.result.min_volume); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - log!("Wait for 4 seconds for Bob order to be converted to maker"); - thread::sleep(Duration::from_secs(4)); + let bob_orderbook: Json = json::from_str(&rc.1).unwrap(); + log!("Bob orderbook {:?}", bob_orderbook); + let asks = bob_orderbook["asks"].as_array().unwrap(); + let bids = bob_orderbook["bids"].as_array().unwrap(); + assert!(asks.is_empty(), "Bob MORTY/RICK orderbook must contain an empty asks"); + assert_eq!(bids.len(), 2, "Bob MORTY/RICK orderbook must have exactly 2 bids"); + let mine_orders = bids.iter().filter(|bid| bid["is_mine"].as_bool().unwrap()).count(); + assert_eq!(mine_orders, 1, "Bob RICK/MORTY orderbook must have exactly 1 mine bid"); + // Alice orderbook must show 1 mine and 1 non-mine orders + log!("Get RICK/MORTY orderbook on Alice side"); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, - "method": "my_orders", + "method": "orderbook", + "base": "RICK", + "rel": "MORTY", }))) .unwrap(); - assert!(rc.0.is_success(), "!my_orders: {}", rc.1); - let my_orders: MyOrdersRpcResult = json::from_str(&rc.1).unwrap(); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let alice_orderbook: Json = json::from_str(&rc.1).unwrap(); + log!("Alice orderbook {:?}", alice_orderbook); + let asks = alice_orderbook["asks"].as_array().unwrap(); + let bids = alice_orderbook["bids"].as_array().unwrap(); + assert!(bids.is_empty(), "Alice MORTY/RICK orderbook must contain an empty bids"); + assert_eq!(asks.len(), 2, "Alice MORTY/RICK orderbook must have exactly 2 asks"); + let mine_orders = asks.iter().filter(|ask| ask["is_mine"].as_bool().unwrap()).count(); assert_eq!( - 1, - my_orders.result.maker_orders.len(), - "maker_orders must have exactly 1 order" + mine_orders, 1, + "Alice RICK/MORTY orderbook must have exactly 1 mine bid" ); - assert!(my_orders.result.taker_orders.is_empty(), "taker_orders must be empty"); - let maker_order = my_orders.result.maker_orders.get(&response.result.uuid).unwrap(); - - let expected_min_volume: BigDecimal = "0.2".parse().unwrap(); - assert_eq!(expected_min_volume, maker_order.min_base_vol); } #[cfg(not(target_arch = "wasm32"))] @@ -6166,12 +4870,11 @@ fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] fn test_orderbook_depth() { let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf()]); // start bob and immediately place the orders let mut mm_bob = MarketMakerIt::start( @@ -6271,7 +4974,7 @@ fn test_orderbook_depth() { fn test_mm2_db_migration() { let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); + let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(),]); let mm2_folder = new_mm2_temp_folder_path(None); let swaps_dir = mm2_folder.join(format!( @@ -7468,19 +6171,12 @@ fn test_tbtc_block_header_sync() { } #[test] -#[ignore] #[cfg(not(target_arch = "wasm32"))] fn test_enable_coins_with_enable_hd() { const TX_HISTORY: bool = false; const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - let coins = json!([ - eth_testnet_conf(), - eth_jst_testnet_conf(), - rick_conf(), - tqrc20_conf(), - btc_segwit_conf(), - ]); + let coins = json!([rick_conf(), tqrc20_conf(), btc_segwit_conf(),]); let path_to_address = StandardHDCoinAddress { account: 0, @@ -7492,20 +6188,6 @@ fn test_enable_coins_with_enable_hd() { let (_dump_log, _dump_dashboard) = mm_hd_0.mm_dump(); log!("log path: {}", mm_hd_0.log_path.display()); - let eth = block_on(enable_native( - &mm_hd_0, - "ETH", - ETH_DEV_NODES, - Some(path_to_address.clone()), - )); - assert_eq!(eth.address, "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93"); - let jst = block_on(enable_native( - &mm_hd_0, - "JST", - ETH_DEV_NODES, - Some(path_to_address.clone()), - )); - assert_eq!(jst.address, "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93"); let rick = block_on(enable_electrum( &mm_hd_0, "RICK", @@ -7541,20 +6223,6 @@ fn test_enable_coins_with_enable_hd() { let (_dump_log, _dump_dashboard) = mm_hd_1.mm_dump(); log!("log path: {}", mm_hd_1.log_path.display()); - let eth = block_on(enable_native( - &mm_hd_1, - "ETH", - ETH_DEV_NODES, - Some(path_to_address.clone()), - )); - assert_eq!(eth.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); - let jst = block_on(enable_native( - &mm_hd_1, - "JST", - ETH_DEV_NODES, - Some(path_to_address.clone()), - )); - assert_eq!(jst.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); let rick = block_on(enable_electrum( &mm_hd_1, "RICK", @@ -7590,20 +6258,6 @@ fn test_enable_coins_with_enable_hd() { let (_dump_log, _dump_dashboard) = mm_hd_1.mm_dump(); log!("log path: {}", mm_hd_1.log_path.display()); - let eth = block_on(enable_native( - &mm_hd_1, - "ETH", - ETH_DEV_NODES, - Some(path_to_address.clone()), - )); - assert_eq!(eth.address, "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B"); - let jst = block_on(enable_native( - &mm_hd_1, - "JST", - ETH_DEV_NODES, - Some(path_to_address.clone()), - )); - assert_eq!(jst.address, "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B"); let rick = block_on(enable_electrum( &mm_hd_1, "RICK", @@ -7667,172 +6321,6 @@ fn test_get_shared_db_id() { ); } -#[test] -#[ignore] -fn test_eth_swap_contract_addr_negotiation_same_fallback() { - let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); - let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); - - let coins = json!([eth_testnet_conf(), eth_jst_testnet_conf(),]); - - let bob_conf = Mm2TestConf::seednode(&bob_passphrase, &coins); - let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - - let alice_conf = Mm2TestConf::light_node(&alice_passphrase, &coins, &[&mm_bob.ip.to_string()]); - let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); - - let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); - log!("Alice log path: {}", mm_alice.log_path.display()); - - dbg!(block_on(enable_eth_coin( - &mm_bob, - "ETH", - ETH_DEV_NODES, - // using arbitrary address - ETH_DEV_TOKEN_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT), - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_bob, - "JST", - ETH_DEV_NODES, - // using arbitrary address - ETH_DEV_TOKEN_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT), - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_alice, - "ETH", - ETH_DEV_NODES, - // using arbitrary address - ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT), - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_alice, - "JST", - ETH_DEV_NODES, - // using arbitrary address - ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT), - false - ))); - - let uuids = block_on(start_swaps( - &mut mm_bob, - &mut mm_alice, - &[("ETH", "JST")], - 1., - 1., - 0.0001, - )); - - // give few seconds for swap statuses to be saved - thread::sleep(Duration::from_secs(3)); - - let wait_until = get_utc_timestamp() + 30; - let expected_contract = Json::from(ETH_DEV_SWAP_CONTRACT.trim_start_matches("0x")); - - block_on(wait_for_swap_contract_negotiation( - &mm_bob, - &uuids[0], - expected_contract.clone(), - wait_until, - )); - block_on(wait_for_swap_contract_negotiation( - &mm_alice, - &uuids[0], - expected_contract, - wait_until, - )); -} - -#[test] -#[ignore] -fn test_eth_swap_negotiation_fails_maker_no_fallback() { - let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); - let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); - - let coins = json!([eth_testnet_conf(), eth_jst_testnet_conf(),]); - - let bob_conf = Mm2TestConf::seednode(&bob_passphrase, &coins); - let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); - - let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - log!("Bob log path: {}", mm_bob.log_path.display()); - - let alice_conf = Mm2TestConf::light_node(&alice_passphrase, &coins, &[&mm_bob.ip.to_string()]); - let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); - - let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); - log!("Alice log path: {}", mm_alice.log_path.display()); - - dbg!(block_on(enable_eth_coin( - &mm_bob, - "ETH", - ETH_DEV_NODES, - // using arbitrary address - ETH_DEV_TOKEN_CONTRACT, - None, - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_bob, - "JST", - ETH_DEV_NODES, - // using arbitrary address - ETH_DEV_TOKEN_CONTRACT, - None, - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_alice, - "ETH", - ETH_DEV_NODES, - // using arbitrary address - ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT), - false - ))); - - dbg!(block_on(enable_eth_coin( - &mm_alice, - "JST", - ETH_DEV_NODES, - // using arbitrary address - ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT), - false - ))); - - let uuids = block_on(start_swaps( - &mut mm_bob, - &mut mm_alice, - &[("ETH", "JST")], - 1., - 1., - 0.00001, - )); - - // give few seconds for swap statuses to be saved - thread::sleep(Duration::from_secs(3)); - - let wait_until = get_utc_timestamp() + 30; - block_on(wait_for_swap_negotiation_failure(&mm_bob, &uuids[0], wait_until)); - block_on(wait_for_swap_negotiation_failure(&mm_alice, &uuids[0], wait_until)); -} - #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sign_raw_transaction_rick() { diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 1728d92464d..d0af04affac 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -1846,6 +1846,29 @@ pub async fn enable_eth_coin( json::from_str(&enable.1).unwrap() } +pub async fn enable_eth_coin_hd( + mm: &MarketMakerIt, + coin: &str, + urls: &[&str], + swap_contract_address: &str, + path_to_address: Option, +) -> Json { + let enable = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "enable", + "coin": coin, + "urls": urls, + "swap_contract_address": swap_contract_address, + "mm2": 1, + "path_to_address": path_to_address.unwrap_or_default(), + })) + .await + .unwrap(); + assert_eq!(enable.0, StatusCode::OK, "'enable' failed: {}", enable.1); + json::from_str(&enable.1).unwrap() +} + pub async fn enable_spl(mm: &MarketMakerIt, coin: &str) -> Json { let req = json!({ "userpass": mm.userpass, @@ -2179,12 +2202,18 @@ pub async fn wait_for_swap_status(mm: &MarketMakerIt, uuid: &str, wait_sec: i64) } } -pub async fn wait_for_swap_finished(mm: &MarketMakerIt, uuid: &str, wait_sec: i64) { +/// Wait until one of the MM2 isntances has the swap finished. +/// This is useful because we don't then have to wait for each of them if the swap failed. +/// Since failures (e.g. `MakerPaymentTransactionFailed`) doesn't get communicated to +/// the other side of the swap, it is left waiting till timeout. +pub async fn wait_for_swap_finished_on_any(mms: &[&MarketMakerIt], uuid: &str, wait_sec: i64) { let wait_until = get_utc_timestamp() + wait_sec; - loop { - let status = my_swap_status(mm, uuid).await.unwrap(); - if status["result"]["is_finished"].as_bool().unwrap() { - break; + 'outer: loop { + for mm in mms { + let status = my_swap_status(mm, uuid).await.unwrap(); + if status["result"]["is_finished"].as_bool().unwrap() { + break 'outer; + } } if get_utc_timestamp() > wait_until { @@ -2195,6 +2224,10 @@ pub async fn wait_for_swap_finished(mm: &MarketMakerIt, uuid: &str, wait_sec: i6 } } +pub async fn wait_for_swap_finished(mm: &MarketMakerIt, uuid: &str, wait_sec: i64) { + wait_for_swap_finished_on_any(&[mm], uuid, wait_sec).await +} + pub async fn wait_for_swap_contract_negotiation(mm: &MarketMakerIt, swap: &str, expected_contract: Json, until: i64) { let events = loop { if get_utc_timestamp() > until { @@ -2320,6 +2353,31 @@ pub async fn check_stats_swap_status(mm: &MarketMakerIt, uuid: &str) { ); } +pub async fn wait_check_stats_swap_status(mm: &MarketMakerIt, uuid: &str, timeout: i64) { + let wait_until = get_utc_timestamp() + timeout; + loop { + let response = mm + .rpc(&json!({ + "method": "stats_swap_status", + "params": { + "uuid": uuid, + } + })) + .await + .unwrap(); + assert!(response.0.is_success(), "!status of {}: {}", uuid, response.1); + let status_response: Json = json::from_str(&response.1).unwrap(); + if !status_response["result"]["maker"].is_null() && !status_response["result"]["taker"].is_null() { + break; + } + Timer::sleep(1.).await; + if get_utc_timestamp() > wait_until { + panic!("Timed out waiting for swap stats status uuid={}", uuid); + } + } + check_stats_swap_status(mm, uuid).await; +} + pub async fn check_recent_swaps(mm: &MarketMakerIt, expected_len: usize) { let response = mm .rpc(&json!({ @@ -3072,8 +3130,7 @@ pub async fn wait_for_swaps_finish_and_check_status( maker_price: f64, ) { for uuid in uuids.iter() { - wait_for_swap_finished(maker, uuid.as_ref(), 900).await; - wait_for_swap_finished(taker, uuid.as_ref(), 900).await; + wait_for_swap_finished_on_any(&[maker, taker], uuid.as_ref(), 900).await; log!("Checking taker status.."); check_my_swap_status(