Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet-only mode #915

Merged
merged 4 commits into from
Apr 23, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2442,8 +2442,6 @@ impl EthTxFeeDetails {
impl MmCoin for EthCoin {
fn is_asset_chain(&self) -> bool { false }

fn wallet_only(&self) -> bool { false }

fn withdraw(&self, req: WithdrawRequest) -> Box<dyn Future<Item = TransactionDetails, Error = String> + Send> {
let ctx = try_fus!(MmArc::from_weak(&self.ctx).ok_or("!ctx"));
Box::new(Box::pin(withdraw_impl(ctx, self.clone(), req)).compat())
Expand Down
14 changes: 13 additions & 1 deletion mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,10 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static {
fn is_asset_chain(&self) -> bool;

/// The coin can be initialized, but it cannot participate in the swaps.
fn wallet_only(&self) -> bool;
fn wallet_only(&self, ctx: &MmArc) -> bool {
let coin_conf = coin_conf(&ctx, &self.ticker());
coin_conf["wallet_only"].as_bool().unwrap_or(false)
}

fn withdraw(&self, req: WithdrawRequest) -> Box<dyn Future<Item = TransactionDetails, Error = String> + Send>;

Expand Down Expand Up @@ -828,6 +831,15 @@ pub fn coin_conf(ctx: &MmArc, ticker: &str) -> Json {
}
}

pub fn is_wallet_only_conf(conf: &Json) -> bool {
conf["wallet_only"].as_bool().unwrap_or(false)
}

pub fn is_wallet_only_ticker(ctx: &MmArc, ticker: &str) -> bool {
let coin_conf = coin_conf(ctx, ticker);
coin_conf["wallet_only"].as_bool().unwrap_or(false)
}

/// Adds a new currency into the list of currencies configured.
///
/// Returns an error if the currency already exists. Initializing the same currency twice is a bad habit
Expand Down
2 changes: 0 additions & 2 deletions mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,8 +925,6 @@ impl MarketCoinOps for Qrc20Coin {
impl MmCoin for Qrc20Coin {
fn is_asset_chain(&self) -> bool { utxo_common::is_asset_chain(&self.utxo) }

fn wallet_only(&self) -> bool { false }

fn withdraw(&self, req: WithdrawRequest) -> Box<dyn Future<Item = TransactionDetails, Error = String> + Send> {
Box::new(qrc20_withdraw(self.clone(), req).boxed().compat())
}
Expand Down
2 changes: 0 additions & 2 deletions mm2src/coins/test_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,6 @@ impl SwapOps for TestCoin {
impl MmCoin for TestCoin {
fn is_asset_chain(&self) -> bool { unimplemented!() }

fn wallet_only(&self) -> bool { unimplemented!() }

fn withdraw(&self, req: WithdrawRequest) -> Box<dyn Future<Item = TransactionDetails, Error = String> + Send> {
unimplemented!()
}
Expand Down
2 changes: 0 additions & 2 deletions mm2src/coins/utxo/qtum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,6 @@ impl MarketCoinOps for QtumCoin {
impl MmCoin for QtumCoin {
fn is_asset_chain(&self) -> bool { utxo_common::is_asset_chain(&self.utxo_arc) }

fn wallet_only(&self) -> bool { false }

fn withdraw(&self, req: WithdrawRequest) -> Box<dyn Future<Item = TransactionDetails, Error = String> + Send> {
Box::new(utxo_common::withdraw(self.clone(), req).boxed().compat())
}
Expand Down
2 changes: 0 additions & 2 deletions mm2src/coins/utxo/utxo_standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,6 @@ impl MarketCoinOps for UtxoStandardCoin {
impl MmCoin for UtxoStandardCoin {
fn is_asset_chain(&self) -> bool { utxo_common::is_asset_chain(&self.utxo_arc) }

fn wallet_only(&self) -> bool { false }

fn withdraw(&self, req: WithdrawRequest) -> Box<dyn Future<Item = TransactionDetails, Error = String> + Send> {
Box::new(utxo_common::withdraw(self.clone(), req).boxed().compat())
}
Expand Down
24 changes: 12 additions & 12 deletions mm2src/lp_ordermatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2676,11 +2676,11 @@ pub async fn buy(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, String> {
let rel_coin = try_s!(rel_coin.ok_or("Rel coin is not found or inactive"));
let base_coin = try_s!(lp_coinfind(&ctx, &input.base).await);
let base_coin: MmCoinEnum = try_s!(base_coin.ok_or("Base coin is not found or inactive"));
if base_coin.wallet_only() {
return ERR!("Base coin is wallet only");
if base_coin.wallet_only(&ctx) {
return ERR!("Base coin {} is wallet only", input.base);
}
if rel_coin.wallet_only() {
return ERR!("Rel coin is wallet only");
if rel_coin.wallet_only(&ctx) {
return ERR!("Rel coin {} is wallet only", input.rel);
}
let my_amount = &input.volume * &input.price;
try_s!(
Expand Down Expand Up @@ -2708,11 +2708,11 @@ pub async fn sell(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, String> {
let base_coin = try_s!(base_coin.ok_or("Base coin is not found or inactive"));
let rel_coin = try_s!(lp_coinfind(&ctx, &input.rel).await);
let rel_coin = try_s!(rel_coin.ok_or("Rel coin is not found or inactive"));
if base_coin.wallet_only() {
return ERR!("Base coin is wallet only");
if base_coin.wallet_only(&ctx) {
return ERR!("Base coin {} is wallet only", input.base);
}
if rel_coin.wallet_only() {
return ERR!("Rel coin is wallet only");
if rel_coin.wallet_only(&ctx) {
return ERR!("Rel coin {} is wallet only", input.rel);
}
try_s!(
check_balance_for_taker_swap(
Expand Down Expand Up @@ -3183,11 +3183,11 @@ pub async fn set_price(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, Strin
None => return ERR!("Rel coin {} is not found", req.rel),
};

if base_coin.wallet_only() {
return ERR!("Base coin is wallet only");
if base_coin.wallet_only(&ctx) {
return ERR!("Base coin {} is wallet only", req.base);
}
if rel_coin.wallet_only() {
return ERR!("Rel coin is wallet only");
if rel_coin.wallet_only(&ctx) {
return ERR!("Rel coin {} is wallet only", req.rel);
}

let my_balance = try_s!(
Expand Down
9 changes: 8 additions & 1 deletion mm2src/lp_ordermatch/best_orders.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{OrderbookItemWithProof, OrdermatchContext, OrdermatchRequest};
use crate::mm2::lp_network::{request_any_relay, P2PRequest};
use coins::{address_by_coin_conf_and_pubkey_str, coin_conf};
use coins::{address_by_coin_conf_and_pubkey_str, coin_conf, is_wallet_only_ticker, is_wallet_only_conf};
use common::log;
use common::mm_ctx::MmArc;
use common::mm_number::MmNumber;
Expand Down Expand Up @@ -106,6 +106,9 @@ pub async fn process_best_orders_p2p_request(

pub async fn best_orders_rpc(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, String> {
let req: BestOrdersRequest = try_s!(json::from_value(req));
if is_wallet_only_ticker(&ctx, &req.coin) {
return ERR!("Coin {} is wallet only", &req.coin);
}
let p2p_request = OrdermatchRequest::BestOrders {
coin: req.coin,
action: req.action,
Expand All @@ -123,6 +126,10 @@ pub async fn best_orders_rpc(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>,
log::warn!("Coin {} is not found in config", coin);
continue;
}
if is_wallet_only_conf(&coin_conf) {
log::warn!("Coin {} was removed from best orders because it's defined as wallet only in config", coin);
continue;
}
for order_w_proof in orders_w_proofs {
let order = order_w_proof.order;
let address = match address_by_coin_conf_and_pubkey_str(&coin, &coin_conf, &order.pubkey) {
Expand Down
12 changes: 12 additions & 0 deletions mm2src/lp_ordermatch/orderbook_depth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{orderbook_topic_from_base_rel, OrdermatchContext, OrdermatchRequest};
use crate::mm2::lp_network::{request_any_relay, P2PRequest};
use coins::is_wallet_only_ticker;
use common::{log, mm_ctx::MmArc};
use http::Response;
use serde_json::{self as json, Value as Json};
Expand Down Expand Up @@ -30,6 +31,17 @@ struct PairWithDepth {
pub async fn orderbook_depth_rpc(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, String> {
let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap();
let req: OrderbookDepthReq = try_s!(json::from_value(req));

let wallet_only_pairs: Vec<_> = req
artemii235 marked this conversation as resolved.
Show resolved Hide resolved
.pairs
.iter()
.filter(|pair| is_wallet_only_ticker(&ctx, &pair.0) || is_wallet_only_ticker(&ctx, &pair.1))
.collect();

if !wallet_only_pairs.is_empty() {
return ERR!("Pairs {:?} has wallet only coins", wallet_only_pairs);
}

let mut result = Vec::with_capacity(req.pairs.len());

let orderbook = ordermatch_ctx.orderbook.lock().await;
Expand Down
8 changes: 7 additions & 1 deletion mm2src/lp_ordermatch/orderbook_rpc.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{subscribe_to_orderbook_topic, OrdermatchContext, RpcOrderbookEntry};
use coins::{address_by_coin_conf_and_pubkey_str, coin_conf};
use coins::{address_by_coin_conf_and_pubkey_str, coin_conf, is_wallet_only_conf};
use common::{mm_ctx::MmArc, mm_number::MmNumber, now_ms};
use http::Response;
use num_rational::BigRational;
Expand Down Expand Up @@ -82,10 +82,16 @@ pub async fn orderbook_rpc(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, S
if base_coin_conf.is_null() {
return ERR!("Coin {} is not found in config", req.base);
}
if is_wallet_only_conf(&base_coin_conf) {
return ERR!("Base Coin {} is wallet only", req.base);
}
let rel_coin_conf = coin_conf(&ctx, &req.rel);
if rel_coin_conf.is_null() {
return ERR!("Coin {} is not found in config", req.rel);
}
if is_wallet_only_conf(&rel_coin_conf) {
return ERR!("Base Coin {} is wallet only", req.rel);
}
let request_orderbook = true;
try_s!(subscribe_to_orderbook_topic(&ctx, &req.base, &req.rel, request_orderbook).await);
let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(&ctx));
Expand Down
8 changes: 7 additions & 1 deletion mm2src/lp_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
use crate::mm2::lp_network::broadcast_p2p_msg;
use async_std::sync as async_std_sync;
use bigdecimal::BigDecimal;
use coins::{lp_coinfind, MmCoinEnum, TradeFee, TradePreimageError, TransactionEnum};
use coins::{is_wallet_only_ticker, lp_coinfind, MmCoinEnum, TradeFee, TradePreimageError, TransactionEnum};
use common::{bits256, block_on, calc_total_pages,
executor::{spawn, Timer},
log::{error, info},
Expand Down Expand Up @@ -1235,6 +1235,12 @@ construct_detailed!(DetailedRequiredBalance, required_balance);

pub async fn trade_preimage(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, String> {
let req: TradePreimageRequest = try_s!(json::from_value(req));
if is_wallet_only_ticker(&ctx, &req.base) {
return ERR!("Base Coin {} is wallet only", req.base);
}
if is_wallet_only_ticker(&ctx, &req.rel) {
return ERR!("Rel Coin {} is wallet only", req.rel);
}
let result: TradePreimageResponse = match req.swap_method {
TradePreimageMethod::SetPrice => try_s!(maker_swap_trade_preimage(&ctx, req).await).into(),
TradePreimageMethod::Buy | TradePreimageMethod::Sell => {
Expand Down
118 changes: 118 additions & 0 deletions mm2src/mm2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5597,6 +5597,124 @@ fn test_best_orders() {
block_on(mm_alice.stop()).unwrap();
}

#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_best_orders_filter_response() {
let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap();

let bob_coins_config = 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"}},
{"coin":"ETH","name":"ethereum","protocol":{"type":"ETH"},"rpcport":80},
{"coin":"JST","name":"jst","protocol":{"type":"ERC20", "protocol_data":{"platform":"ETH","contract_address":"0x2b294F029Fde858b2c62184e8390591755521d8E"}}}
]);

// alice defined MORTY as "wallet_only" in config
let alice_coins_config = json!([
{"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}},
{"coin":"MORTY","asset":"MORTY","rpcport":11608,"wallet_only": true,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}},
{"coin":"ETH","name":"ethereum","protocol":{"type":"ETH"},"rpcport":80},
{"coin":"JST","name":"jst","protocol":{"type":"ERC20", "protocol_data":{"platform":"ETH","contract_address":"0x2b294F029Fde858b2c62184e8390591755521d8E"}}}
]);

// start bob and immediately place the orders
let mut mm_bob = MarketMakerIt::start(
json! ({
"gui": "nogui",
"netid": 9998,
"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::<i64>().unwrap()),
"passphrase": bob_passphrase,
"coins": bob_coins_config,
"rpc_password": "pass",
"i_am_seed": true,
}),
"pass".into(),
local_start!("bob"),
)
.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".
let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, &["http://195.201.0.6:8565"]));
log!({ "enable_coins (bob): {:?}", bob_coins });
// issue sell request on Bob side by setting base/rel price
log!("Issue bob sell requests");

let bob_orders = [
// (base, rel, price, volume, min_volume)
("RICK", "MORTY", "0.9", "0.9", None),
("RICK", "MORTY", "0.8", "0.9", None),
("RICK", "MORTY", "0.7", "0.9", Some("0.9")),
("RICK", "ETH", "0.8", "0.9", None),
("MORTY", "RICK", "0.8", "0.9", None),
("MORTY", "RICK", "0.9", "0.9", None),
("ETH", "RICK", "0.8", "0.9", None),
("MORTY", "ETH", "0.8", "0.8", None),
("MORTY", "ETH", "0.7", "0.8", Some("0.8")),
];
for (base, rel, price, volume, min_volume) in bob_orders.iter() {
let rc = block_on(mm_bob.rpc(json! ({
"userpass": mm_bob.userpass,
"method": "setprice",
"base": base,
"rel": rel,
"price": price,
"volume": volume,
"min_volume": min_volume.unwrap_or("0.00777"),
"cancel_previous": false,
})))
.unwrap();
assert!(rc.0.is_success(), "!setprice: {}", rc.1);
}

let mm_alice = MarketMakerIt::start(
json! ({
"gui": "nogui",
"netid": 9998,
"myipaddr": env::var ("ALICE_TRADE_IP") .ok(),
"rpcip": env::var ("ALICE_TRADE_IP") .ok(),
"passphrase": "alice passphrase",
"coins": alice_coins_config,
"seednodes": [fomat!((mm_bob.ip))],
"rpc_password": "pass",
}),
"pass".into(),
local_start!("alice"),
)
.unwrap();

let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump();
log!({ "Alice log path: {}", mm_alice.log_path.display() });

block_on(mm_bob.wait_for_log(22., |log| {
log.contains("DEBUG Handling IncludedTorelaysMesh message for peer")
}))
.unwrap();

let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
"method": "best_orders",
"coin": "RICK",
"action": "buy",
"volume": "0.1",
})))
.unwrap();
assert!(rc.0.is_success(), "!best_orders: {}", rc.1);
let response: BestOrdersResponse = json::from_str(&rc.1).unwrap();
let empty_vec = Vec::new();
let best_morty_orders = response.result.get("MORTY").unwrap_or(&empty_vec);
assert_eq!(0, best_morty_orders.len());
let best_eth_orders = response.result.get("ETH").unwrap();
assert_eq!(1, best_eth_orders.len());


block_on(mm_bob.stop()).unwrap();
block_on(mm_alice.stop()).unwrap();
}

fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) {
let rc = block_on(mm_alice.rpc(json! ({
"userpass": mm_alice.userpass,
Expand Down