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

[r2r] Add max_maker_vol RPC #1618

Merged
merged 7 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
64 changes: 26 additions & 38 deletions mm2src/mm2_main/src/lp_ordermatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ use uuid::Uuid;

use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest};
use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap,
check_other_coin_balance_for_swap, insert_new_swap_to_db, is_pubkey_banned,
lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast,
check_other_coin_balance_for_swap, get_max_maker_vol, insert_new_swap_to_db,
is_pubkey_banned, lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast,
p2p_private_and_peer_id_to_broadcast, run_maker_swap, run_taker_swap, AtomicLocktimeVersion,
MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap};
CheckBalanceError, CheckBalanceResult, CoinVolumeInfo, MakerSwap, RunMakerSwapInput,
RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap};

pub use best_orders::{best_orders_rpc, best_orders_rpc_v2};
use my_orders_storage::{delete_my_maker_order, delete_my_taker_order, save_maker_order_on_update,
Expand Down Expand Up @@ -1039,7 +1040,7 @@ impl BalanceTradeFeeUpdatedHandler for BalanceUpdateOrdermatchHandler {
// Get the max maker available volume to check if the wallet balances are sufficient for the issued maker orders.
// Note although the maker orders are issued already, but they are not matched yet, so pass the `OrderIssue` stage.
let new_volume = match calc_max_maker_vol(&ctx, coin, new_balance, FeeApproxStage::OrderIssue).await {
Ok(v) => v,
Ok(vol_info) => vol_info.volume,
Err(e) if e.get_inner().not_sufficient_balance() => MmNumber::from(0),
Err(e) => {
log::warn!("Couldn't handle the 'balance_updated' event: {}", e);
Expand Down Expand Up @@ -3093,7 +3094,7 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) {
};
let max_vol = match calc_max_maker_vol(&ctx, &base, &current_balance, FeeApproxStage::OrderIssue).await
{
Ok(max) => max,
Ok(vol_info) => vol_info.volume,
Err(e) => {
log::info!("Error {} on balance check to kickstart order {}, cancelling", e, uuid);
to_cancel.push(uuid);
Expand Down Expand Up @@ -4403,30 +4404,17 @@ async fn cancel_orders_on_error<T, E>(ctx: &MmArc, req: &SetPriceReq, error: E)
Err(error)
}

struct CoinVolumeInfo {
volume: MmNumber,
balance: BigDecimal,
}

async fn get_max_volume(ctx: &MmArc, my_coin: &MmCoinEnum, other_coin: &MmCoinEnum) -> Result<CoinVolumeInfo, String> {
let my_balance = try_s!(my_coin.my_spendable_balance().compat().await);

let volume = try_s!(calc_max_maker_vol(ctx, my_coin, &my_balance, FeeApproxStage::OrderIssue).await);

// check if `rel_coin` balance is sufficient
let other_coin_trade_fee = try_s!(
other_coin
.get_receiver_trade_fee(volume.to_decimal(), FeeApproxStage::OrderIssue)
.compat()
.await
);
try_s!(check_other_coin_balance_for_swap(ctx, other_coin, None, other_coin_trade_fee).await);
// calculate max maker volume
// note the `calc_max_maker_vol` returns [`CheckBalanceError::NotSufficientBalance`] error if the balance of `base_coin` is not sufficient
Ok(CoinVolumeInfo {
volume,
balance: my_balance,
})
pub async fn check_other_coin_balance_for_order_issue(
ctx: &MmArc,
other_coin: &MmCoinEnum,
my_coin_volume: BigDecimal,
) -> CheckBalanceResult<()> {
let trade_fee = other_coin
.get_receiver_trade_fee(my_coin_volume, FeeApproxStage::OrderIssue)
.compat()
.await
.mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, other_coin.ticker()))?;
check_other_coin_balance_for_swap(ctx, other_coin, None, trade_fee).await
}

pub async fn check_balance_update_loop(ctx: MmWeak, ticker: String, balance: Option<BigDecimal>) {
Expand Down Expand Up @@ -4470,12 +4458,14 @@ pub async fn create_maker_order(ctx: &MmArc, req: SetPriceReq) -> Result<MakerOr
return ERR!("Rel coin {} is wallet only", req.rel);
}

let CoinVolumeInfo { volume, balance } = if req.max {
try_s!(
get_max_volume(ctx, &base_coin, &rel_coin)
let (volume, balance) = if req.max {
let CoinVolumeInfo { volume, balance, .. } = try_s!(
get_max_maker_vol(ctx, &base_coin)
.or_else(|e| cancel_orders_on_error(ctx, &req, e))
.await
)
);
try_s!(check_other_coin_balance_for_order_issue(ctx, &rel_coin, volume.to_decimal()).await);
(volume, balance.to_decimal())
} else {
let balance = try_s!(
check_balance_for_maker_swap(
Expand All @@ -4490,10 +4480,7 @@ pub async fn create_maker_order(ctx: &MmArc, req: SetPriceReq) -> Result<MakerOr
.or_else(|e| cancel_orders_on_error(ctx, &req, e))
.await
);
CoinVolumeInfo {
volume: req.volume.clone(),
balance,
}
(req.volume.clone(), balance)
};

let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(ctx));
Expand Down Expand Up @@ -4649,7 +4636,8 @@ pub async fn update_maker_order(ctx: &MmArc, req: MakerOrderUpdateReq) -> Result

// Calculate order volume and add to update_msg if new_volume is found in the request
let new_volume = if req.max.unwrap_or(false) {
let max_volume = try_s!(get_max_volume(ctx, &base_coin, &rel_coin).await).volume + reserved_amount.clone();
let max_volume = try_s!(get_max_maker_vol(ctx, &base_coin).await).volume + reserved_amount.clone();
try_s!(check_other_coin_balance_for_order_issue(ctx, &rel_coin, max_volume.to_decimal()).await);
update_msg.with_new_max_volume(max_volume.clone().into());
max_volume
} else if Option::is_some(&req.volume_delta) {
Expand Down
10 changes: 6 additions & 4 deletions mm2src/mm2_main/src/lp_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ use std::sync::atomic::{AtomicU64, Ordering};

#[path = "lp_swap/check_balance.rs"] mod check_balance;
#[path = "lp_swap/maker_swap.rs"] mod maker_swap;
#[path = "lp_swap/max_maker_vol_rpc.rs"] mod max_maker_vol_rpc;
#[path = "lp_swap/my_swaps_storage.rs"] mod my_swaps_storage;
#[path = "lp_swap/pubkey_banning.rs"] mod pubkey_banning;
#[path = "lp_swap/recreate_swap_data.rs"] mod recreate_swap_data;
Expand All @@ -102,13 +103,14 @@ use std::sync::atomic::{AtomicU64, Ordering};
#[path = "lp_swap/swap_wasm_db.rs"]
mod swap_wasm_db;

pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError};
pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult};
use crypto::CryptoCtx;
use keys::KeyPair;
use maker_swap::MakerSwapEvent;
pub use maker_swap::{calc_max_maker_vol, check_balance_for_maker_swap, maker_swap_trade_preimage, run_maker_swap,
MakerSavedEvent, MakerSavedSwap, MakerSwap, MakerSwapStatusChanged, MakerTradePreimage,
RunMakerSwapInput, MAKER_PAYMENT_SENT_LOG};
pub use maker_swap::{calc_max_maker_vol, check_balance_for_maker_swap, get_max_maker_vol, maker_swap_trade_preimage,
run_maker_swap, CoinVolumeInfo, MakerSavedEvent, MakerSavedSwap, MakerSwap,
MakerSwapStatusChanged, MakerTradePreimage, RunMakerSwapInput, MAKER_PAYMENT_SENT_LOG};
pub use max_maker_vol_rpc::max_maker_vol;
use my_swaps_storage::{MySwapsOps, MySwapsStorage};
use pubkey_banning::BanReason;
pub use pubkey_banning::{ban_pubkey_rpc, is_pubkey_banned, list_banned_pubkeys_rpc, unban_pubkeys_rpc};
Expand Down
3 changes: 2 additions & 1 deletion mm2src/mm2_main/src/lp_swap/check_balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ pub struct TakerFeeAdditionalInfo {
pub fee_to_send_dex_fee: TradeFee,
}

#[derive(Debug, Display)]
#[derive(Debug, Display, Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum CheckBalanceError {
#[display(
fmt = "Not enough {} for swap: available {}, required at least {}, locked by swaps {:?}",
Expand Down
41 changes: 30 additions & 11 deletions mm2src/mm2_main/src/lp_swap/maker_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2091,7 +2091,9 @@ pub async fn maker_swap_trade_preimage(
let rel_coin_ticker = rel_coin.ticker();
let volume = if req.max {
let balance = base_coin.my_spendable_balance().compat().await?;
calc_max_maker_vol(ctx, &base_coin, &balance, FeeApproxStage::TradePreimage).await?
calc_max_maker_vol(ctx, &base_coin, &balance, FeeApproxStage::TradePreimage)
.await?
.volume
} else {
let threshold = base_coin.min_trading_vol().to_decimal();
if req.volume.is_zero() {
Expand Down Expand Up @@ -2159,21 +2161,34 @@ pub async fn maker_swap_trade_preimage(
})
}

/// Calculate max Maker volume.
pub struct CoinVolumeInfo {
pub volume: MmNumber,
pub balance: MmNumber,
pub locked_by_swaps: MmNumber,
}

/// Requests the `coin` balance and calculates max Maker volume.
/// Returns [`CheckBalanceError::NotSufficientBalance`] if the balance is insufficient.
pub async fn get_max_maker_vol(ctx: &MmArc, my_coin: &MmCoinEnum) -> CheckBalanceResult<CoinVolumeInfo> {
let my_balance = my_coin.my_spendable_balance().compat().await?;
calc_max_maker_vol(ctx, my_coin, &my_balance, FeeApproxStage::OrderIssue).await
}

/// Calculates max Maker volume.
/// Returns [`CheckBalanceError::NotSufficientBalance`] if the balance is not sufficient.
/// Note the function checks base coin balance if the trade fee should be paid in base coin.
pub async fn calc_max_maker_vol(
ctx: &MmArc,
coin: &MmCoinEnum,
balance: &BigDecimal,
stage: FeeApproxStage,
) -> CheckBalanceResult<MmNumber> {
) -> CheckBalanceResult<CoinVolumeInfo> {
let ticker = coin.ticker();
let locked = get_locked_amount(ctx, ticker);
let available = &MmNumber::from(balance.clone()) - &locked;
let mut vol = available.clone();
let locked_by_swaps = get_locked_amount(ctx, ticker);
let available = &MmNumber::from(balance.clone()) - &locked_by_swaps;
let mut volume = available.clone();

let preimage_value = TradePreimageValue::UpperBound(vol.to_decimal());
let preimage_value = TradePreimageValue::UpperBound(volume.to_decimal());
let trade_fee = coin
.get_sender_trade_fee(preimage_value, stage)
.await
Expand All @@ -2182,23 +2197,27 @@ pub async fn calc_max_maker_vol(
debug!("{} trade fee {}", trade_fee.coin, trade_fee.amount.to_decimal());
let mut required_to_pay_fee = MmNumber::from(0);
if trade_fee.coin == ticker {
vol = &vol - &trade_fee.amount;
volume = &volume - &trade_fee.amount;
required_to_pay_fee = trade_fee.amount;
} else {
let base_coin_balance = coin.base_coin_balance().compat().await?;
check_base_coin_balance_for_swap(ctx, &MmNumber::from(base_coin_balance), trade_fee.clone(), None).await?;
}
let min_tx_amount = MmNumber::from(coin.min_tx_amount());
if vol < min_tx_amount {
if volume < min_tx_amount {
let required = min_tx_amount + required_to_pay_fee;
return MmError::err(CheckBalanceError::NotSufficientBalance {
coin: ticker.to_owned(),
available: available.to_decimal(),
required: required.to_decimal(),
locked_by_swaps: Some(locked.to_decimal()),
locked_by_swaps: Some(locked_by_swaps.to_decimal()),
});
}
Ok(vol)
Ok(CoinVolumeInfo {
volume,
balance: MmNumber::from(balance.clone()),
locked_by_swaps,
})
}

#[cfg(all(test, not(target_arch = "wasm32")))]
Expand Down
155 changes: 155 additions & 0 deletions mm2src/mm2_main/src/lp_swap/max_maker_vol_rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use crate::mm2::lp_swap::{get_max_maker_vol, CheckBalanceError, CoinVolumeInfo};
use coins::{lp_coinfind_or_err, CoinFindError};
use common::HttpStatusCode;
use derive_more::Display;
use http::StatusCode;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::*;
use mm2_number::{BigDecimal, MmNumberMultiRepr};
use ser_error_derive::SerializeErrorType;

#[derive(Display, Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum MaxMakerVolRpcError {
#[display(
fmt = "Not enough {} for swap: available {}, required at least {}, locked by swaps {:?}",
coin,
available,
required,
locked_by_swaps
)]
NotSufficientBalance {
coin: String,
available: BigDecimal,
required: BigDecimal,
#[serde(skip_serializing_if = "Option::is_none")]
locked_by_swaps: Option<BigDecimal>,
},
#[display(
fmt = "Not enough base coin {} balance for swap: available {}, required at least {}, locked by swaps {:?}",
coin,
available,
required,
locked_by_swaps
)]
NotSufficientBaseCoinBalance {
coin: String,
available: BigDecimal,
required: BigDecimal,
#[serde(skip_serializing_if = "Option::is_none")]
locked_by_swaps: Option<BigDecimal>,
},
#[display(
fmt = "The volume {} of the {} coin less than minimum transaction amount {}",
volume,
coin,
threshold
)]
VolumeTooLow {
coin: String,
volume: BigDecimal,
threshold: BigDecimal,
},
#[display(fmt = "No such coin {}", coin)]
NoSuchCoin { coin: String },
#[display(fmt = "Coin {} is wallet only", coin)]
CoinIsWalletOnly { coin: String },
#[display(fmt = "Transport error: {}", _0)]
Transport(String),
#[display(fmt = "Internal error: {}", _0)]
InternalError(String),
}

impl From<CoinFindError> for MaxMakerVolRpcError {
fn from(e: CoinFindError) -> Self {
match e {
CoinFindError::NoSuchCoin { coin } => MaxMakerVolRpcError::NoSuchCoin { coin },
}
}
}

impl From<CheckBalanceError> for MaxMakerVolRpcError {
fn from(e: CheckBalanceError) -> Self {
match e {
CheckBalanceError::NotSufficientBalance {
coin,
available,
required,
locked_by_swaps,
} => MaxMakerVolRpcError::NotSufficientBalance {
coin,
available,
required,
locked_by_swaps,
},
CheckBalanceError::NotSufficientBaseCoinBalance {
coin,
available,
required,
locked_by_swaps,
} => MaxMakerVolRpcError::NotSufficientBaseCoinBalance {
coin,
available,
required,
locked_by_swaps,
},
CheckBalanceError::VolumeTooLow {
coin,
volume,
threshold,
} => MaxMakerVolRpcError::VolumeTooLow {
coin,
volume,
threshold,
},
CheckBalanceError::Transport(transport) => MaxMakerVolRpcError::Transport(transport),
CheckBalanceError::InternalError(internal) => MaxMakerVolRpcError::InternalError(internal),
}
}
}

impl HttpStatusCode for MaxMakerVolRpcError {
fn status_code(&self) -> StatusCode {
match self {
MaxMakerVolRpcError::NotSufficientBalance { .. }
| MaxMakerVolRpcError::NotSufficientBaseCoinBalance { .. }
| MaxMakerVolRpcError::VolumeTooLow { .. }
| MaxMakerVolRpcError::NoSuchCoin { .. }
| MaxMakerVolRpcError::CoinIsWalletOnly { .. } => StatusCode::BAD_REQUEST,
MaxMakerVolRpcError::Transport(_) | MaxMakerVolRpcError::InternalError(_) => {
StatusCode::INTERNAL_SERVER_ERROR
},
}
}
}

#[derive(Deserialize)]
pub struct MaxMakerVolRequest {
coin: String,
}

#[derive(Debug, Serialize)]
pub struct MaxMakerVolResponse {
coin: String,
volume: MmNumberMultiRepr,
balance: MmNumberMultiRepr,
locked_by_swaps: MmNumberMultiRepr,
}

pub async fn max_maker_vol(ctx: MmArc, req: MaxMakerVolRequest) -> MmResult<MaxMakerVolResponse, MaxMakerVolRpcError> {
let coin = lp_coinfind_or_err(&ctx, &req.coin).await?;
if coin.wallet_only(&ctx) {
return MmError::err(MaxMakerVolRpcError::CoinIsWalletOnly { coin: req.coin });
}
let CoinVolumeInfo {
volume,
balance,
locked_by_swaps,
} = get_max_maker_vol(&ctx, &coin).await?;
Ok(MaxMakerVolResponse {
coin: req.coin,
volume: MmNumberMultiRepr::from(volume),
balance: MmNumberMultiRepr::from(balance),
locked_by_swaps: MmNumberMultiRepr::from(locked_by_swaps),
})
}
Loading