Skip to content

Commit

Permalink
wallet-only mode #864 (#915)
Browse files Browse the repository at this point in the history
* wallet-only mode

* added 2 wallet_only functions to avoid duplication

* filter wallet-only coins from best_orders response

* code formatting
  • Loading branch information
shamardy authored Apr 23, 2021
1 parent 4fb8d62 commit 4fbe21e
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 26 deletions.
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
12 changes: 11 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,13 @@ 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
12 changes: 11 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_conf, is_wallet_only_ticker};
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,13 @@ 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
.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
117 changes: 117 additions & 0 deletions mm2src/mm2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5597,6 +5597,123 @@ 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

0 comments on commit 4fbe21e

Please sign in to comment.