Skip to content

Commit

Permalink
Merge pull request #832 from KomodoPlatform/mm2.1-buy-sell-min-volume
Browse files Browse the repository at this point in the history
Add min_volume arg to buy and sell RPCs. #825
  • Loading branch information
artemii235 authored Feb 24, 2021
2 parents e9b8f13 + 65929c4 commit bf975ca
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 13 deletions.
40 changes: 27 additions & 13 deletions mm2src/lp_ordermatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,7 @@ struct TakerOrder {
created_at: u64,
request: TakerRequest,
matches: HashMap<Uuid, TakerMatch>,
min_volume: MmNumber,
order_type: OrderType,
}

Expand Down Expand Up @@ -1464,7 +1465,7 @@ impl Into<MakerOrder> for TakerOrder {
TakerAction::Sell => MakerOrder {
price: (self.request.get_rel_amount() / self.request.get_base_amount()),
max_base_vol: self.request.get_base_amount().clone(),
min_base_vol: MIN_TRADING_VOL.into(),
min_base_vol: self.min_volume,
created_at: now_ms(),
base: self.request.base,
rel: self.request.rel,
Expand All @@ -1474,17 +1475,21 @@ impl Into<MakerOrder> for TakerOrder {
conf_settings: self.request.conf_settings,
},
// The "buy" taker order is recreated with reversed pair as Maker order is always considered as "sell"
TakerAction::Buy => MakerOrder {
price: (self.request.get_base_amount() / self.request.get_rel_amount()),
max_base_vol: self.request.get_rel_amount().clone(),
min_base_vol: MIN_TRADING_VOL.into(),
created_at: now_ms(),
base: self.request.rel,
rel: self.request.base,
matches: HashMap::new(),
started_swaps: Vec::new(),
uuid: self.request.uuid,
conf_settings: self.request.conf_settings.map(|s| s.reversed()),
TakerAction::Buy => {
let price = self.request.get_base_amount() / self.request.get_rel_amount();
let min_base_vol = &self.min_volume / &price;
MakerOrder {
price,
max_base_vol: self.request.get_rel_amount().clone(),
min_base_vol,
created_at: now_ms(),
base: self.request.rel,
rel: self.request.base,
matches: HashMap::new(),
started_swaps: Vec::new(),
uuid: self.request.uuid,
conf_settings: self.request.conf_settings.map(|s| s.reversed()),
}
},
}
}
Expand Down Expand Up @@ -2545,6 +2550,8 @@ async fn process_taker_connect(ctx: MmArc, sender_pubkey: H256Json, connect_msg:
}
}

fn min_trading_vol() -> MmNumber { MmNumber::from(MIN_TRADING_VOL) }

#[derive(Deserialize, Debug)]
pub struct AutoBuyInput {
base: String,
Expand All @@ -2568,6 +2575,8 @@ pub struct AutoBuyInput {
base_nota: Option<bool>,
rel_confs: Option<u64>,
rel_nota: Option<bool>,
#[serde(default = "min_trading_vol")]
min_volume: MmNumber,
}

pub async fn buy(ctx: MmArc, req: Json) -> Result<Response<Vec<u8>>, String> {
Expand Down Expand Up @@ -2672,11 +2681,15 @@ impl<'a> From<&'a TakerRequest> for TakerRequestForRpc<'a> {
}
}

construct_detailed!(DetailedMinVolume, min_volume);

#[derive(Serialize)]
struct LpautobuyResult<'a> {
#[serde(flatten)]
request: TakerRequestForRpc<'a>,
order_type: OrderType,
#[serde(flatten)]
min_volume: DetailedMinVolume,
}

#[derive(Clone, Debug, Serialize)]
Expand Down Expand Up @@ -2742,16 +2755,17 @@ pub async fn lp_auto_buy(
let result = json!({ "result": LpautobuyResult {
request: (&request).into(),
order_type: input.order_type,
min_volume: input.min_volume.clone().into(),
} });
let order = TakerOrder {
created_at: now_ms(),
matches: HashMap::new(),
request,
order_type: input.order_type,
min_volume: input.min_volume,
};
save_my_taker_order(ctx, &order);
my_taker_orders.insert(order.request.uuid, order);
drop(my_taker_orders);
Ok(result.to_string())
}

Expand Down
152 changes: 152 additions & 0 deletions mm2src/mm2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5045,6 +5045,158 @@ fn test_orderbook_is_mine_orders() {
);
}

#[test]
fn test_sell_min_volume() {
let bob_passphrase = unwrap!(get_passphrase(&".env.client", "BOB_PASSPHRASE"));

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

let mut mm_bob = unwrap!(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| unwrap! (s.parse::<i64>())),
"passphrase": bob_passphrase,
"coins": coins,
"rpc_password": "password",
"i_am_seed": true,
}),
"password".into(),
local_start!("bob")
));

let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump();
log! ({"Bob log path: {}", mm_bob.log_path.display()});
unwrap!(block_on(
mm_bob.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))
));
log!([block_on(enable_coins_eth_electrum(&mm_bob, vec![
"http://195.201.0.6:8565"
]))]);

let min_volume: BigDecimal = "0.1".parse().unwrap();
log!("Issue bob ETH/JST sell request");
let rc = unwrap!(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"
}
}))));
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);

log!("Wait for 40 seconds for Bob order to be converted to maker");
thread::sleep(Duration::from_secs(40));

let rc = unwrap!(block_on(mm_bob.rpc(json! ({
"userpass": mm_bob.userpass,
"method": "my_orders",
}))));
assert!(rc.0.is_success(), "!my_orders: {}", rc.1);
let my_orders: Json = unwrap!(json::from_str(&rc.1));
let my_maker_orders: HashMap<Uuid, Json> = unwrap!(json::from_value(my_orders["result"]["maker_orders"].clone()));
let my_taker_orders: HashMap<Uuid, Json> = unwrap!(json::from_value(my_orders["result"]["taker_orders"].clone()));
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);
}

#[test]
fn test_buy_min_volume() {
let bob_passphrase = unwrap!(get_passphrase(&".env.client", "BOB_PASSPHRASE"));

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

let mut mm_bob = unwrap!(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| unwrap! (s.parse::<i64>())),
"passphrase": bob_passphrase,
"coins": coins,
"rpc_password": "password",
"i_am_seed": true,
}),
"password".into(),
local_start!("bob")
));

let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump();
log! ({"Bob log path: {}", mm_bob.log_path.display()});
unwrap!(block_on(
mm_bob.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))
));
log!([block_on(enable_coins_eth_electrum(&mm_bob, vec![
"http://195.201.0.6:8565"
]))]);

let min_volume: BigDecimal = "0.1".parse().unwrap();
log!("Issue bob ETH/JST sell request");
let rc = unwrap!(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"
}
}))));
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);

log!("Wait for 40 seconds for Bob order to be converted to maker");
thread::sleep(Duration::from_secs(40));

let rc = unwrap!(block_on(mm_bob.rpc(json! ({
"userpass": mm_bob.userpass,
"method": "my_orders",
}))));
assert!(rc.0.is_success(), "!my_orders: {}", rc.1);
let my_orders: Json = unwrap!(json::from_str(&rc.1));
let my_maker_orders: HashMap<Uuid, Json> = unwrap!(json::from_value(my_orders["result"]["maker_orders"].clone()));
let my_taker_orders: HashMap<Uuid, Json> = unwrap!(json::from_value(my_orders["result"]["taker_orders"].clone()));
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 expected_min_volume: BigDecimal = "0.2".parse().unwrap();
let min_volume_maker: BigDecimal = json::from_value(maker_order["min_base_vol"].clone()).unwrap();
assert_eq!(expected_min_volume, min_volume_maker);
}

// 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
Expand Down
13 changes: 13 additions & 0 deletions mm2src/ordermatch_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -357,6 +358,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -391,6 +393,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -425,6 +428,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -459,6 +463,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -493,6 +498,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -527,6 +533,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -561,6 +568,7 @@ fn test_taker_match_reserved() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -595,6 +603,7 @@ fn test_taker_match_reserved() {
},
matches: HashMap::new(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down Expand Up @@ -632,6 +641,7 @@ fn test_taker_order_cancellable() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

assert!(order.is_cancellable());
Expand All @@ -654,6 +664,7 @@ fn test_taker_order_cancellable() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

order.matches.insert(Uuid::new_v4(), TakerMatch {
Expand Down Expand Up @@ -742,6 +753,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver<AdexBehaviourCmd> {
conf_settings: None,
},
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
});
rx
}
Expand Down Expand Up @@ -829,6 +841,7 @@ fn test_taker_order_match_by() {
matches: HashMap::new(),
created_at: now_ms(),
order_type: OrderType::GoodTillCancelled,
min_volume: 0.into(),
};

let reserved = MakerReserved {
Expand Down

0 comments on commit bf975ca

Please sign in to comment.