Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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: 1 addition & 1 deletion mm2src/common/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,7 @@ macro_rules! push_if_some {
macro_rules! def_with_opt_param {
($var: ident, $var_type: ty) => {
$crate::paste! {
pub fn [<with_ $var>](&mut self, $var: Option<$var_type>) -> &mut Self {
pub fn [<with_ $var>](mut self, $var: Option<$var_type>) -> Self {
self.$var = $var;
self
}
Expand Down
4 changes: 3 additions & 1 deletion mm2src/mm2_main/src/lp_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ mod trade_preimage;
#[cfg(target_arch = "wasm32")]
mod swap_wasm_db;

pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult};
pub use check_balance::{
check_my_coin_balance_for_swap, check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult,
};
use crypto::secret_hash_algo::SecretHashAlgo;
use crypto::CryptoCtx;
use keys::{KeyPair, SECP_SIGN, SECP_VERIFY};
Expand Down
1 change: 1 addition & 0 deletions mm2src/mm2_main/src/lp_swap/taker_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2621,6 +2621,7 @@ impl AtomicSwap for TakerSwap {
}
}

#[derive(Clone)]
pub struct TakerSwapPreparedParams {
pub(super) dex_fee: MmNumber,
pub(super) fee_to_send_dex_fee: TradeFee,
Expand Down
88 changes: 88 additions & 0 deletions mm2src/mm2_main/src/lr_swap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! Code for swaps with liquidity routing (LR)

use coins::Ticker;
use ethereum_types::{Address as EthAddress, U256};
use lr_errors::LrSwapError;
use mm2_number::MmNumber;
use mm2_rpc::data::legacy::{MatchBy, OrderType, TakerAction};
use trading_api::one_inch_api::classic_swap_types::ClassicSwapData;

pub(crate) mod lr_errors;
pub(crate) mod lr_helpers;
pub(crate) mod lr_quote;

/// Liquidity routing data for the aggregated taker swap state machine
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LrSwapParams {
pub src_amount: MmNumber,
pub src: String,
pub src_contract: EthAddress,
pub src_decimals: u8,
pub dst: String,
pub dst_contract: EthAddress,
pub dst_decimals: u8,
pub from: EthAddress,
pub slippage: f32,
}

/// Atomic swap data for the aggregated taker swap state machine
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AtomicSwapParams {
pub base_volume: Option<MmNumber>,
pub base: String,
pub rel: String,
pub price: MmNumber,
pub action: TakerAction,
#[serde(default)]
pub match_by: MatchBy,
#[serde(default)]
pub order_type: OrderType,
}

impl AtomicSwapParams {
#[allow(unused)]
pub(crate) fn maker_coin(&self) -> Ticker {
match self.action {
TakerAction::Buy => self.base.clone(),
TakerAction::Sell => self.rel.clone(),
}
}

#[allow(unused)]
pub(crate) fn taker_coin(&self) -> Ticker {
match self.action {
TakerAction::Buy => self.rel.clone(),
TakerAction::Sell => self.base.clone(),
}
}

#[allow(clippy::result_large_err, unused)]
pub(crate) fn taker_volume(&self) -> Result<MmNumber, LrSwapError> {
let Some(ref volume) = self.base_volume else {
return Err(LrSwapError::InternalError("no atomic swp volume".to_owned()));
};
match self.action {
TakerAction::Buy => Ok(volume * &self.price),
TakerAction::Sell => Ok(volume.clone()),
}
}

#[allow(clippy::result_large_err, unused)]
pub(crate) fn maker_volume(&self) -> Result<MmNumber, LrSwapError> {
let Some(ref volume) = self.base_volume else {
return Err(LrSwapError::InternalError("no atomic swp volume".to_owned()));
};
match self.action {
TakerAction::Buy => Ok(volume.clone()),
TakerAction::Sell => Ok(volume * &self.price),
}
}
}

/// Struct to return extra data (src_amount) in addition to 1inch swap details
pub struct ClassicSwapDataExt {
pub api_details: ClassicSwapData,
/// Estimated source amount for a liquidity routing swap step, includes needed amount to fill the order, plus dex and trade fees (if needed)
pub src_amount: U256,
pub chain_id: u64,
}
129 changes: 129 additions & 0 deletions mm2src/mm2_main/src/lr_swap/lr_errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use crate::lp_swap::CheckBalanceError;
use coins::CoinFindError;
use derive_more::Display;
use enum_derives::EnumFromStringify;
use ethereum_types::U256;
use mm2_number::BigDecimal;
use trading_api::one_inch_api::errors::OneInchError;

#[derive(Debug, Display, EnumFromStringify)]
pub enum LrSwapError {
NoSuchCoin {
coin: String,
},
CoinTypeError,
NftProtocolNotSupported,
ChainNotSupported,
DifferentChains,
#[from_stringify("coins::UnexpectedDerivationMethod")]
MyAddressError(String),
#[from_stringify("ethereum_types::FromDecStrErr", "coins::NumConversError", "hex::FromHexError")]
ConversionError(String),
InvalidParam(String),
#[display(fmt = "Parameter {param} out of bounds, value: {value}, min: {min} max: {max}")]
OutOfBounds {
param: String,
value: String,
min: String,
max: String,
},
#[display(fmt = "allowance not enough for 1inch contract, available: {allowance}, needed: {amount}")]
OneInchAllowanceNotEnough {
allowance: U256,
amount: U256,
},
OneInchError(OneInchError), // TODO: do not attach the whole error but extract only message
#[allow(dead_code)]
StateError(String),
BestLrSwapNotFound {
candidates: u32,
},
#[allow(dead_code)]
AtomicSwapError(String),
#[from_stringify("serde_json::Error")]
ResponseParseError(String),
#[from_stringify("coins::TransactionErr")]
TransactionError(String),
#[from_stringify("coins::RawTransactionError")]
SignTransactionError(String),
InternalError(String),
#[display(
fmt = "Not enough {coin} for swap: available {available}, required at least {required}, locked by swaps {locked_by_swaps:?}"
)]
NotSufficientBalance {
coin: String,
available: BigDecimal,
required: BigDecimal,
locked_by_swaps: Option<BigDecimal>,
},
#[display(fmt = "The volume {volume} of the {coin} coin less than minimum transaction amount {threshold}")]
VolumeTooLow {
coin: String,
volume: BigDecimal,
threshold: BigDecimal,
},
TransportError(String),
}

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

impl From<OneInchError> for LrSwapError {
fn from(error: OneInchError) -> Self {
match error {
OneInchError::InvalidParam(error) => LrSwapError::InvalidParam(error),
OneInchError::OutOfBounds { param, value, min, max } => LrSwapError::OutOfBounds { param, value, min, max },
OneInchError::TransportError(_)
| OneInchError::ParseBodyError { .. }
| OneInchError::GeneralApiError { .. } => LrSwapError::OneInchError(error),
OneInchError::AllowanceNotEnough { allowance, amount, .. } => {
LrSwapError::OneInchAllowanceNotEnough { allowance, amount }
},
}
}
}

impl From<CheckBalanceError> for LrSwapError {
fn from(err: CheckBalanceError) -> Self {
match err {
CheckBalanceError::NotSufficientBalance {
coin,
available,
required,
locked_by_swaps,
} => Self::NotSufficientBalance {
coin,
available,
required,
locked_by_swaps,
},
CheckBalanceError::NotSufficientBaseCoinBalance {
coin,
available,
required,
locked_by_swaps,
} => Self::NotSufficientBalance {
coin,
available,
required,
locked_by_swaps,
},
CheckBalanceError::VolumeTooLow {
coin,
volume,
threshold,
} => Self::VolumeTooLow {
coin,
volume,
threshold,
},
CheckBalanceError::Transport(nested_err) => Self::TransportError(nested_err),
CheckBalanceError::InternalError(nested_err) => Self::InternalError(nested_err),
}
}
}
48 changes: 48 additions & 0 deletions mm2src/mm2_main/src/lr_swap/lr_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use super::lr_errors::LrSwapError;
use coins::eth::{EthCoin, EthCoinType};
use coins::{lp_coinfind_or_err, MmCoinEnum, Ticker};
use ethereum_types::Address as EthAddress;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::*;
use mm2_rpc::data::legacy::TakerAction;
use std::str::FromStr;
use trading_api::one_inch_api::client::ApiClient;

pub(crate) async fn get_coin_for_one_inch(
ctx: &MmArc,
ticker: &Ticker,
) -> MmResult<(EthCoin, EthAddress), LrSwapError> {
let coin = match lp_coinfind_or_err(ctx, ticker).await.map_mm_err()? {
MmCoinEnum::EthCoin(coin) => coin,
_ => return Err(MmError::new(LrSwapError::CoinTypeError)),
};
let contract = match coin.coin_type {
EthCoinType::Eth => EthAddress::from_str(ApiClient::eth_special_contract())
.map_to_mm(|_| LrSwapError::InternalError("invalid address".to_owned()))?,
EthCoinType::Erc20 { token_addr, .. } => token_addr,
EthCoinType::Nft { .. } => return Err(MmError::new(LrSwapError::NftProtocolNotSupported)),
};
Ok((coin, contract))
}

#[allow(clippy::result_large_err)]
pub(crate) fn check_if_one_inch_supports_pair(base_chain_id: u64, rel_chain_id: u64) -> MmResult<(), LrSwapError> {
if !ApiClient::is_chain_supported(base_chain_id) {
return MmError::err(LrSwapError::ChainNotSupported);
}
if base_chain_id != rel_chain_id {
return MmError::err(LrSwapError::DifferentChains);
}
Ok(())
}

#[allow(clippy::result_large_err)]
pub(crate) fn sell_buy_method(method: &str) -> MmResult<TakerAction, LrSwapError> {
match method {
"buy" => Ok(TakerAction::Buy),
"sell" => Ok(TakerAction::Sell),
_ => MmError::err(LrSwapError::InvalidParam(
"invalid method in sell/buy request".to_owned(),
)),
}
}
Loading
Loading