From 9006a9f235e02485a30758c39e926b25bf68b118 Mon Sep 17 00:00:00 2001 From: "Sergey O. Boyko" <58207208+sergeyboyko0791@users.noreply.github.com> Date: Tue, 9 Jun 2020 12:10:18 +0700 Subject: [PATCH] Add cashaddress support #466 (#666) * Add cashaddr supporting for BCH * Add green withdraw and common (my_balance, get_enabled_coins) tests * Add red test_cashaddresses_in_tx_details_by_hash test * Make green the test_cashaddresses_in_tx_details_by_hash test * Fill comments and do refactoring --- Cargo.lock | 16 ++-- mm2src/coins/eth.rs | 7 +- mm2src/coins/lp_coins.rs | 18 +++-- mm2src/coins/test_coin.rs | 3 +- mm2src/coins/utxo.rs | 82 +++++++++++++++---- mm2src/coins/utxo/utxo_tests.rs | 32 ++++++++ mm2src/docker_tests.rs | 8 +- mm2src/mm2_tests.rs | 138 ++++++++++++++++++++++++++++++++ mm2src/rpc/lp_commands.rs | 6 +- 9 files changed, 268 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86851516f1..5caa07850f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,7 +194,7 @@ dependencies = [ [[package]] name = "bitcrypto" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "groestl 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", @@ -367,7 +367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "chain" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "bitcrypto 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", "primitives 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", @@ -1521,7 +1521,7 @@ dependencies = [ [[package]] name = "keys" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitcrypto 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", @@ -2070,7 +2070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "primitives" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "bigint 4.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2389,7 +2389,7 @@ dependencies = [ [[package]] name = "rpc" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "chain 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", "keys 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", @@ -2492,7 +2492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "script" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "bitcrypto 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", "blake2b_simd 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2581,7 +2581,7 @@ dependencies = [ [[package]] name = "serialization" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", @@ -2590,7 +2590,7 @@ dependencies = [ [[package]] name = "serialization_derive" version = "0.1.0" -source = "git+https://github.com/artemii235/parity-bitcoin.git#836938d0fa4651db5f9c5a8b632c4ca07104484e" +source = "git+https://github.com/artemii235/parity-bitcoin.git#1b129897cd4eb630839edfb1641f6cbab9c2ecf7" dependencies = [ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9b08d63600..1a6fd2142d 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -42,7 +42,6 @@ use rand::seq::SliceRandom; use rpc::v1::types::{Bytes as BytesJson}; use serde_json::{self as json, Value as Json}; use sha3::{Keccak256, Digest}; -use std::borrow::Cow; use std::collections::HashMap; use std::cmp::Ordering; use std::ops::Deref; @@ -421,7 +420,7 @@ async fn withdraw_impl(ctx: MmArc, coin: EthCoin, req: WithdrawRequest) -> Resul } Ok(TransactionDetails { to: vec![checksum_address(&format!("{:#02x}", to_addr))], - from: vec![coin.my_address().into()], + from: vec![try_s!(coin.my_address())], total_amount: amount_decimal, my_balance_change: &received_by_me - &spent_by_me, spent_by_me, @@ -689,8 +688,8 @@ impl SwapOps for EthCoin { impl MarketCoinOps for EthCoin { fn ticker (&self) -> &str {&self.ticker[..]} - fn my_address(&self) -> Cow { - checksum_address(&format!("{:#02x}", self.my_address)).into() + fn my_address(&self) -> Result { + Ok(checksum_address(&format!("{:#02x}", self.my_address))) } fn my_balance(&self) -> Box + Send> { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 390ebad349..a0937b65be 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -44,7 +44,6 @@ use gstuff::{slurp}; use http::Response; use rpc::v1::types::{Bytes as BytesJson}; use serde_json::{self as json, Value as Json}; -use std::borrow::Cow; use std::collections::hash_map::{HashMap, RawEntryMut}; use std::fmt; use std::ops::Deref; @@ -211,7 +210,7 @@ pub trait SwapOps { pub trait MarketCoinOps { fn ticker (&self) -> &str; - fn my_address(&self) -> Cow; + fn my_address(&self) -> Result; fn my_balance(&self) -> Box + Send>; @@ -367,7 +366,8 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static { /// Path to tx history file fn tx_history_path(&self, ctx: &MmArc) -> PathBuf { - ctx.dbdir().join("TRANSACTIONS").join(format!("{}_{}.json", self.ticker(), self.my_address())) + let my_address = self.my_address().unwrap_or(Default::default()); + ctx.dbdir().join("TRANSACTIONS").join(format!("{}_{}.json", self.ticker(), my_address)) } /// Loads existing tx history from file, returns empty vector if file is not found @@ -767,10 +767,14 @@ struct EnabledCoin { pub async fn get_enabled_coins(ctx: MmArc) -> Result>, String> { let coins_ctx: Arc = try_s!(CoinsContext::from_ctx(&ctx)); let coins = try_s!(coins_ctx.coins.sleeplock(77).await); - let enabled_coins: Vec<_> = coins.iter().map(|(ticker, coin)| EnabledCoin { - ticker: ticker.clone(), - address: coin.my_address().to_string(), - }).collect(); + let enabled_coins: Vec<_> = try_s!(coins.iter().map(|(ticker, coin)| { + let address = try_s!(coin.my_address()); + Ok(EnabledCoin { + ticker: ticker.clone(), + address, + }) + }).collect()); + let res = try_s!(json::to_vec(&json!({ "result": enabled_coins }))); diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 50e3ce673b..d3994d2d68 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -4,7 +4,6 @@ use common::mm_number::MmNumber; use crate::{TradeInfo, FoundSwapTxSpend, WithdrawRequest}; use futures01::Future; use mocktopus::macros::*; -use std::borrow::Cow; use super::{HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionDetails, TransactionEnum, TransactionFut}; /// Dummy coin struct used in tests which functions are unimplemented but then mocked @@ -18,7 +17,7 @@ impl MarketCoinOps for TestCoin { unimplemented!() } - fn my_address(&self) -> Cow { + fn my_address(&self) -> Result { unimplemented!() } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 324c47bf36..4d9791ddb4 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -50,7 +50,6 @@ use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; use script::{Opcode, Builder, Script, ScriptAddress, TransactionInputSigner, UnsignedTransactionInput, SignatureVersion}; use serde_json::{self as json, Value as Json}; use serialization::{serialize, deserialize}; -use std::borrow::Cow; use std::collections::hash_map::{HashMap, Entry}; use std::convert::TryInto; use std::cmp::Ordering; @@ -159,6 +158,25 @@ enum FeePolicy { DeductFromOutput(usize), } +#[derive(Debug, Serialize, Deserialize)] +#[serde(tag = "format")] +enum UtxoAddressFormat { + /// Standard UTXO address format. + /// In Bitcoin Cash context the standard format also known as 'legacy'. + #[serde(rename = "standard")] + Standard, + /// Bitcoin Cash specific address format. + /// https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md + #[serde(rename = "cashaddress")] + CashAddress { network: String }, +} + +impl Default for UtxoAddressFormat { + fn default() -> Self { + UtxoAddressFormat::Standard + } +} + #[derive(Debug)] pub struct UtxoCoinImpl { // pImpl idiom. ticker: String, @@ -204,6 +222,8 @@ pub struct UtxoCoinImpl { // pImpl idiom. key_pair: KeyPair, /// Lock the mutex when we deal with address utxos my_address: Address, + /// The address format indicates how to parse and display UTXO addresses over RPC calls + address_format: UtxoAddressFormat, /// Is current coin KMD asset chain? /// https://komodoplatform.atlassian.net/wiki/spaces/KPSD/pages/71729160/What+is+a+Parallel+Chain+Asset+Chain asset_chain: bool, @@ -332,6 +352,15 @@ impl UtxoCoinImpl { pub fn rpc_client(&self) -> &UtxoRpcClientEnum { &self.rpc_client } + + pub fn display_address(&self, address: &Address) -> Result { + match &self.address_format { + UtxoAddressFormat::Standard => Ok(address.to_string()), + UtxoAddressFormat::CashAddress { network } => + address.to_cashaddress(&network, self.pub_addr_prefix, self.p2sh_addr_prefix) + .and_then(|cashaddress| cashaddress.encode()), + } + } } fn payment_script( @@ -760,7 +789,7 @@ impl UtxoCoin { for input in unsigned.inputs.iter() { let prev_hash = input.previous_output.hash.reversed().into(); let tx = try_s!(self.rpc_client.get_verbose_transaction(prev_hash).compat().await); - interest += kmd_interest(tx.height, input.amount, tx.locktime as u64, unsigned.lock_time as u64); + interest += kmd_interest(tx.height.unwrap_or(0), input.amount, tx.locktime as u64, unsigned.lock_time as u64); } if interest > 0 { data.received_by_me += interest; @@ -924,7 +953,7 @@ impl SwapOps for UtxoCoin { t_addr_prefix: self.p2sh_t_addr_prefix, }; let arc = self.clone(); - let addr_string = payment_addr.to_string(); + let addr_string = try_fus!(self.display_address(&payment_addr)); Either::B(client.import_address(&addr_string, &addr_string, false).map_err(|e| ERRL!("{}", e)).and_then(move |_| arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out]) )) @@ -975,7 +1004,7 @@ impl SwapOps for UtxoCoin { t_addr_prefix: self.p2sh_t_addr_prefix, }; let arc = self.clone(); - let addr_string = payment_addr.to_string(); + let addr_string = try_fus!(self.display_address(&payment_addr)); Either::B(client.import_address(&addr_string, &addr_string, false).map_err(|e| ERRL!("{}", e)).and_then(move |_| arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out]) )) @@ -1236,7 +1265,8 @@ impl SwapOps for UtxoCoin { prefix: selfi.p2sh_addr_prefix, hash, checksum_type: selfi.checksum_type, - }.to_string(); + }; + let target_addr = try_s!(selfi.display_address(&target_addr)); let received_by_addr = try_s!(client.list_received_by_address(0, true, true).compat().await); for item in received_by_addr { if item.address == target_addr && !item.txids.is_empty() { @@ -1292,8 +1322,8 @@ impl SwapOps for UtxoCoin { impl MarketCoinOps for UtxoCoin { fn ticker (&self) -> &str {&self.ticker[..]} - fn my_address(&self) -> Cow { - self.0.my_address.to_string().into() + fn my_address(&self) -> Result { + self.display_address(&self.my_address) } fn my_balance(&self) -> Box + Send> { @@ -1359,7 +1389,7 @@ impl MarketCoinOps for UtxoCoin { fn address_from_pubkey_str(&self, pubkey: &str) -> Result { let pubkey_bytes = try_s!(hex::decode(pubkey)); let addr = try_s!(address_from_raw_pubkey(&pubkey_bytes, self.pub_addr_prefix, self.pub_t_addr_prefix, self.checksum_type)); - Ok(addr.to_string()) + self.display_address(&addr) } fn display_priv_key(&self) -> String { @@ -1368,7 +1398,11 @@ impl MarketCoinOps for UtxoCoin { } async fn withdraw_impl(coin: UtxoCoin, req: WithdrawRequest) -> Result { - let to = try_s!(Address::from_str(&req.to)); + let to = match &coin.address_format { + UtxoAddressFormat::Standard => try_s!(Address::from_str(&req.to)), + UtxoAddressFormat::CashAddress {..} => try_s!(Address::from_cashaddress( + &req.to, coin.checksum_type.clone(),coin.pub_addr_prefix, coin.p2sh_addr_prefix)) + }; if to.checksum_type != coin.checksum_type { return ERR!("Address {} has invalid checksum type, it must be {:?}", to, coin.checksum_type); } @@ -1402,9 +1436,11 @@ async fn withdraw_impl(coin: UtxoCoin, req: WithdrawRequest) -> Result = None; let history = self.load_history_from_file(&ctx); let mut history_map: HashMap = history.into_iter().map(|tx| (H256Json::from(tx.tx_hash.as_slice()), tx)).collect(); + let my_address = match self.my_address() { + Ok(addr) => addr, + Err(e) => { + log!("Error on getting self address: " [e] ". Stop tx history"); + return; + } + }; let mut success_iteration = 0i32; loop { @@ -1545,7 +1588,7 @@ impl MmCoin for UtxoCoin { "coin" => self.ticker.clone(), "client" => "native", "method" => "listtransactions"); all_transactions.into_iter().filter_map(|item| { - if item.address == self.my_address() { + if item.address == my_address { Some((item.txid, item.blockindex)) } else { None @@ -1730,10 +1773,12 @@ impl MmCoin for UtxoCoin { } // remove address duplicates in case several inputs were spent from same address // or several outputs are sent to same address - let mut from_addresses: Vec = from_addresses.into_iter().flatten().map(|addr| addr.to_string()).collect(); + let mut from_addresses: Vec = + try_s!(from_addresses.into_iter().flatten().map(|addr| selfi.display_address(&addr)).collect()); from_addresses.sort(); from_addresses.dedup(); - let mut to_addresses: Vec = to_addresses.into_iter().flatten().map(|addr| addr.to_string()).collect(); + let mut to_addresses: Vec = + try_s!(to_addresses.into_iter().flatten().map(|addr| selfi.display_address(&addr)).collect()); to_addresses.sort(); to_addresses.dedup(); @@ -1750,7 +1795,7 @@ impl MmCoin for UtxoCoin { fee_details: Some(UtxoFeeDetails { amount: fee, }.into()), - block_height: verbose_tx.height, + block_height: verbose_tx.height.unwrap_or(0), coin: selfi.ticker.clone(), internal_id: tx.hash().reversed().to_vec().into(), timestamp: verbose_tx.time.into(), @@ -1958,6 +2003,12 @@ pub async fn utxo_coin_from_conf_and_request( checksum_type, }; + let address_format = if conf["address_format"].is_null() { + UtxoAddressFormat::Standard + } else { + try_s!(json::from_value(conf["address_format"].clone())) + }; + let rpc_client = match req["method"].as_str() { Some("enable") => { if cfg!(feature = "native") { @@ -2116,6 +2167,7 @@ pub async fn utxo_coin_from_conf_and_request( wif_prefix, tx_version, my_address: my_address.clone(), + address_format, asset_chain, tx_fee, version_group_id, diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 485ca568aa..0aaac36e00 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -68,6 +68,7 @@ fn utxo_coin_for_test(rpc_client: UtxoRpcClientEnum, force_seed: Option<&str>) - segwit: false, tx_version: 4, my_address, + address_format: UtxoAddressFormat::Standard, asset_chain: true, p2sh_addr_prefix: 85, p2sh_t_addr_prefix: 0, @@ -818,3 +819,34 @@ fn test_generate_tx_fee_is_correct_when_dynamic_fee_is_larger_than_relay() { assert_eq!(generated.1.spent_by_me, 20000000000); assert!(unsafe { GET_RELAY_FEE_CALLED }); } + +#[test] +fn test_cashaddresses_in_tx_details_by_hash() { + let conf = json!({ + "coin": "BCH", + "pubtype": 0, + "p2shtype": 5, + "mm2": 1, + "address_format":{"format":"cashaddress","network":"bchtest"}, + }); + let req = json!({ + "method": "electrum", + "servers": [{"url":"blackie.c3-soft.com:60001"}, {"url":"bch0.kister.net:51001"}, {"url":"testnet.imaginary.cash:50001"}], + }); + + let ctx = MmCtxBuilder::new().into_mm_arc(); + + let coin = unwrap!(block_on(utxo_coin_from_conf_and_request( + &ctx, "BCH", &conf, &req, &[1u8; 32]))); + + let hash = hex::decode("0f2f6e0c8f440c641895023782783426c3aca1acc78d7c0db7751995e8aa5751").unwrap(); + let fut = async { + let tx_details = coin.tx_details_by_hash(&hash).compat().await.unwrap(); + log!([tx_details]); + + assert!(tx_details.from.iter().any(|addr| addr == "bchtest:qze8g4gx3z428jjcxzpycpxl7ke7d947gca2a7n2la")); + assert!(tx_details.to.iter().any(|addr| addr == "bchtest:qr39na5d25wdeecgw3euh9fkd4ygvd4pnsury96597")); + }; + + block_on(fut); +} diff --git a/mm2src/docker_tests.rs b/mm2src/docker_tests.rs index ba7f55ef4d..b2bd47e672 100644 --- a/mm2src/docker_tests.rs +++ b/mm2src/docker_tests.rs @@ -199,19 +199,21 @@ mod docker_tests { let priv_key = SecretKey::random(&mut rand4::thread_rng()).serialize(); let coin = unwrap!(block_on(utxo_coin_from_conf_and_request( &ctx, ticker, &conf, &req, &priv_key))); - fill_address(&coin, &coin.my_address(), balance, timeout); + // TODO check what is it + fill_address(&coin, &coin.my_address().unwrap(), balance, timeout); (ctx, coin, priv_key) } fn fill_address(coin: &UtxoCoin, address: &str, amount: u64, timeout: u64) { if let UtxoRpcClientEnum::Native(client) = &coin.rpc_client() { - unwrap!(client.import_address(&coin.my_address(), &coin.my_address(), false).wait()); + // TODO check it + unwrap!(client.import_address(&coin.my_address().unwrap(), &unwrap!(coin.my_address()), false).wait()); let hash = client.send_to_address(address, &amount.into()).wait().unwrap(); let tx_bytes = client.get_transaction_bytes(hash).wait().unwrap(); unwrap!(coin.wait_for_confirmations(&tx_bytes, 1, false, timeout, 1).wait()); log!({ "{:02x}", tx_bytes }); loop { - let unspents = client.list_unspent(0, std::i32::MAX, vec![coin.my_address().into()]).wait().unwrap(); + let unspents = client.list_unspent(0, std::i32::MAX, vec![coin.my_address().unwrap()]).wait().unwrap(); log!([unspents]); if !unspents.is_empty() { break; diff --git a/mm2src/mm2_tests.rs b/mm2src/mm2_tests.rs index b681e292be..234dfc3951 100644 --- a/mm2src/mm2_tests.rs +++ b/mm2src/mm2_tests.rs @@ -2574,6 +2574,144 @@ fn test_electrum_tx_history() { assert_eq!(get_tx_history_request_count(&mm), 2); } +#[test] +fn test_withdraw_cashaddresses() { + let coins = json!([ + {"coin":"BCH","pubtype":0,"p2shtype":5,"mm2":1, + "address_format":{"format":"cashaddress","network":"bchtest"}}, + ]); + + let mut mm = unwrap! (MarketMakerIt::start ( + json! ({ + "gui": "nogui", + "netid": 9998, + "myipaddr": env::var ("BOB_TRADE_IP") .ok(), + "rpcip": env::var ("BOB_TRADE_IP") .ok(), + "passphrase": "face pin block number add byte put seek mime test note password sin tab multiple", + "coins": coins, + "i_am_seed": true, + "rpc_password": "pass", + }), + "pass".into(), + local_start! ("bob") + )); + let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); + log!({ "log path: {}", mm.log_path.display() }); + unwrap!(block_on (mm.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + + // Enable BCH electrum client with tx_history loop. + // Enable RICK electrum client with tx_history loop. + let electrum = unwrap!(block_on(mm.rpc (json! ({ + "userpass": mm.userpass, + "method": "electrum", + "coin": "BCH", + "servers": [{"url":"blackie.c3-soft.com:60001"}, {"url":"bch0.kister.net:51001"}, {"url":"testnet.imaginary.cash:50001"}], + "mm2": 1, + })))); + + assert_eq!(electrum.0, StatusCode::OK, "RPC «electrum» failed with {} {}", electrum.0, electrum.1); + let electrum: Json = unwrap!(json::from_str(&electrum.1)); + log!([electrum]); + + // make withdraw + let withdraw = unwrap!(block_on (mm.rpc (json! ({ + "userpass": mm.userpass, + "method": "withdraw", + "coin": "BCH", + "to": "bchtest:qr39na5d25wdeecgw3euh9fkd4ygvd4pnsury96597", + "amount": 0.0001, + })))); + + assert!(withdraw.0.is_success(), "BCH withdraw: {}", withdraw.1); + let withdraw_json: Json = unwrap!(json::from_str(&withdraw.1)); + log!((withdraw_json)); + + // check "from" addresses + let from: Vec<&str> = withdraw_json["from"].as_array().unwrap() + .iter().map(|v| v.as_str().unwrap()).collect(); + assert_eq!(from, vec!["bchtest:qze8g4gx3z428jjcxzpycpxl7ke7d947gca2a7n2la"]); + + // check "to" addresses + let to: Vec<&str> = withdraw_json["to"].as_array().unwrap() + .iter().map(|v| v.as_str().unwrap()).collect(); + assert_eq!(to, vec!["bchtest:qr39na5d25wdeecgw3euh9fkd4ygvd4pnsury96597"]); + + // send the transaction + let send_tx = unwrap!(block_on (mm.rpc (json! ({ + "userpass": mm.userpass, + "method": "send_raw_transaction", + "coin": "BCH", + "tx_hex": withdraw_json["tx_hex"], + })))); + assert!(send_tx.0.is_success(), "QRC20 send_raw_transaction: {}", send_tx.1); + log!((send_tx.1)); +} + +#[test] +fn test_common_cashaddresses() { + let coins = json!([ + {"coin":"BCH","pubtype":0,"p2shtype":5,"mm2":1, + "address_format":{"format":"cashaddress","network":"bchtest"}}, + ]); + + let mut mm = unwrap! (MarketMakerIt::start ( + json! ({ + "gui": "nogui", + "netid": 9998, + "myipaddr": env::var ("BOB_TRADE_IP") .ok(), + "rpcip": env::var ("BOB_TRADE_IP") .ok(), + "passphrase": "face pin block number add byte put seek mime test note password sin tab multiple", + "coins": coins, + "i_am_seed": true, + "rpc_password": "pass", + }), + "pass".into(), + local_start! ("bob") + )); + let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); + log!({ "log path: {}", mm.log_path.display() }); + unwrap!(block_on (mm.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + + // Enable BCH electrum client with tx_history loop. + // Enable RICK electrum client with tx_history loop. + let electrum = unwrap!(block_on(mm.rpc (json! ({ + "userpass": mm.userpass, + "method": "electrum", + "coin": "BCH", + "servers": [{"url":"blackie.c3-soft.com:60001"}, {"url":"bch0.kister.net:51001"}, {"url":"testnet.imaginary.cash:50001"}], + "mm2": 1, + })))); + + assert_eq!(electrum.0, StatusCode::OK, "RPC «electrum» failed with {} {}", electrum.0, electrum.1); + let electrum: Json = unwrap!(json::from_str(&electrum.1)); + log!([electrum]); + + assert_eq!(unwrap!(electrum["address"].as_str()), "bchtest:qze8g4gx3z428jjcxzpycpxl7ke7d947gca2a7n2la"); + + // check my_balance + let rc = unwrap! (block_on (mm.rpc (json! ({ + "userpass": mm.userpass, + "method": "my_balance", + "coin": "BCH", + })))); + assert_eq! (rc.0, StatusCode::OK, "RPC «my_balance» failed with status «{}»", rc.0); + let json: Json = unwrap!(json::from_str(&rc.1)); + let my_balance_address = unwrap!(json["address"].as_str()); + assert_eq!(my_balance_address, "bchtest:qze8g4gx3z428jjcxzpycpxl7ke7d947gca2a7n2la"); + + // check get_enabled_coins + let rc = unwrap! (block_on (mm.rpc (json! ({ + "userpass": mm.userpass, + "method": "get_enabled_coins", + })))); + assert_eq! (rc.0, StatusCode::OK, "RPC «get_enabled_coins» failed with status «{}»", rc.0); + let json: Json = unwrap!(json::from_str(&rc.1)); + + let obj = &json["result"].as_array().unwrap()[0]; + assert_eq!(obj["ticker"].as_str().unwrap(), "BCH"); + assert_eq!(obj["address"].as_str().unwrap(), "bchtest:qze8g4gx3z428jjcxzpycpxl7ke7d947gca2a7n2la"); +} + // HOWTO // 1. Install Firefox. // 2. Install forked version of wasm-bindgen-cli: cargo install wasm-bindgen-cli --git https://github.com/artemii235/wasm-bindgen.git diff --git a/mm2src/rpc/lp_commands.rs b/mm2src/rpc/lp_commands.rs index 35e0e5bf40..a6928dcff9 100644 --- a/mm2src/rpc/lp_commands.rs +++ b/mm2src/rpc/lp_commands.rs @@ -76,7 +76,7 @@ pub async fn electrum (ctx: MmArc, req: Json) -> Result>, Strin let balance = try_s! (coin.my_balance().compat().await); let res = json! ({ "result": "success", - "address": coin.my_address(), + "address": try_s!(coin.my_address()), "balance": balance, "locked_by_swaps": get_locked_amount (&ctx, &ticker), "coin": coin.ticker(), @@ -94,7 +94,7 @@ pub async fn enable (ctx: MmArc, req: Json) -> Result>, String> let balance = try_s! (coin.my_balance().compat().await); let res = json! ({ "result": "success", - "address": coin.my_address(), + "address": try_s!(coin.my_address()), "balance": balance, "locked_by_swaps": get_locked_amount (&ctx, &ticker), "coin": coin.ticker(), @@ -144,7 +144,7 @@ pub fn my_balance (ctx: MmArc, req: Json) -> HyRes { "coin": ticker, "balance": balance, "locked_by_swaps": get_locked_amount(&ctx, &ticker), - "address": coin.my_address(), + "address": try_h!(coin.my_address()), }).to_string()))) }