Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
84663a4
add LR swap rpc scaffolding
Jan 21, 2025
05c15fc
refactor LR rpcs
Jan 29, 2025
43d2bd6
fix doc comments
Jan 29, 2025
e5c73b5
fix draft lr rpc params
Jan 29, 2025
6b4124d
WiP: find_best_lr_buy_for_multiple_orders initial compiled code
Feb 3, 2025
e2f8a53
add missed lr_impl.rs
Feb 3, 2025
0ea3dd9
fix comment
Feb 3, 2025
92bf147
wip: working code for finding best swap with LR to for ask with rel=t…
Feb 16, 2025
b86f433
Merge branch 'dev' into lr-swap-wip
Feb 16, 2025
08ff400
remove unused test code
Feb 16, 2025
6e55e7e
remove debug println
Feb 17, 2025
54d13c3
remove unused derive and import
Feb 17, 2025
5553bdc
fix fmt
Feb 17, 2025
1e3a5d8
refactor: eliminate SomeError in LR code
Feb 17, 2025
7ab51fc
wrap LR test with feature
Feb 17, 2025
5e96440
fix LR error desc
Feb 17, 2025
bbb115e
add another coin to LR quote test
Feb 17, 2025
c850505
review (shamardy): eliminate dup fn, reuse get_erc20_ticker_by_contra…
Feb 26, 2025
1255141
Merge branch 'dev' into lr-swap-wip
Feb 26, 2025
f4fba47
fix doc comment
Feb 26, 2025
18e4668
fix wasm build
Feb 26, 2025
c5f5734
fix (laruh): propagate errors
Mar 21, 2025
9aee864
review (mariocynicys): add type for Vec<CrossPricesData>
Mar 21, 2025
6bc98f3
unused import
Mar 21, 2025
d097ad3
review (mariocynicys): fix using decimals in result (take from api)
Mar 21, 2025
0ecb31d
review (laruh): refactor calc_total_price
Mar 21, 2025
465fe23
review (laruh): more refactor select best swap code
Mar 21, 2025
f3bf400
review: add errors in update_with_contracts, refactor get_coin_for_on…
Mar 21, 2025
32ddfa2
review (laruh): refactor decimals error, remove extra to_string
Mar 21, 2025
204763d
Merge branch 'dev' into lr-swap-wip
Mar 21, 2025
ba1a032
refactor: avoid clone in update lr_data fn's
Mar 21, 2025
4326068
review (mariocynicys): avoid clone
Mar 21, 2025
1907beb
review: fix StatusCode for internal error
Mar 21, 2025
61641c9
review: improve doc comments
Mar 21, 2025
e930b14
review (mariocynicys): use most recent prices by CROSS_PRICES_LIMIT as 1
Mar 21, 2025
23e0d81
fix 1inch test feature placement
Mar 22, 2025
f18e57f
fix 1inch result conversion test
Mar 22, 2025
177c8de
refactor 1inch url builder
Mar 22, 2025
0238033
fix find best lr swap behaviour: skip lr provider error results (to u…
Mar 22, 2025
6505e80
add TODO
Mar 22, 2025
9ed36d2
review (laruh): add fn to get contracts from LrData
Mar 22, 2025
559b1c5
review (laruh): rename fn
Mar 22, 2025
568f364
improve bad api TokenInfo error messages
Apr 7, 2025
f11acaa
fix src_decimals var name
Apr 7, 2025
66e59d2
eliminate from_api_error fn
Apr 7, 2025
f468fcc
Merge branch 'dev' into lr-swap-wip
Apr 7, 2025
9d5c76b
fix tx value eth conversion
Apr 16, 2025
0276312
error msg improved
Apr 16, 2025
e98c1ff
added doc comments for LrData struct
Apr 16, 2025
2a668ac
review (mariocynicys): fix iterators zipping, refactor 1inch url buil…
May 4, 2025
d05697c
Merge branch 'dev' into lr-swap-wip
May 28, 2025
2a226ab
refactor (review):
Jun 3, 2025
e4ef307
Merge branch 'dev' into lr-swap-wip
Jun 3, 2025
8f13278
fix getting chain_id from protocol_data
Jun 3, 2025
37249ce
fix custom token error name
Jun 3, 2025
20364ed
refactor (review): fix namespace for 1inch and lr rpcs
Jun 20, 2025
0ccd318
improve logging in LR find quote test
Jun 20, 2025
855bf74
fix mod tron placement
Jun 20, 2025
f454d6c
review (mariocynicys): remove extra into_iter
Jun 20, 2025
c1a430c
refactor (review): use liquidity_routing rpc prefix
Jun 20, 2025
161afb8
refactor (review): rename LR rpcs to use verbs
Jun 22, 2025
be33db7
refactor: improve and extend LR RPC structures to prepare for support…
Jun 22, 2025
3c6f16b
fix doc comment for lr_find_best_quote_rpc
Jun 22, 2025
6d6fe5c
refactor: use RwLock for lr swap candidates
Jun 22, 2025
8cdddff
fix shared swap candidate lock use (avoid excessive read lock)
Jun 23, 2025
4d8bbe7
Rename find best swap path fn (to make it more general)
Jun 24, 2025
2e384a4
fix doc comment for find_best_swap_path_with_lr improved with gpt
Jun 24, 2025
161164d
add eth_utils.rs and move mm_number_to_u256 and like fn's there
Jun 24, 2025
d705741
improve doc comment for lr_find_best_quote_rpc fn
Jun 24, 2025
fafd785
Merge branch 'dev' into lr-swap-wip
shamardy Jul 2, 2025
5b24a8e
post merge fix
shamardy Jul 2, 2025
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: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

82 changes: 11 additions & 71 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ use super::{coin_conf, lp_coinfind_or_err, AsyncMutex, BalanceError, BalanceFut,
WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG,
INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG,
INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG};
#[cfg(test)]
pub(crate) use eth_utils::display_u256_with_decimal_point;
pub use eth_utils::{addr_from_pubkey_str, addr_from_raw_pubkey, mm_number_from_u256, mm_number_to_u256,
u256_to_big_decimal, wei_from_big_decimal, wei_from_coins_mm_number, wei_from_gwei_decimal,
wei_to_coins_mm_number, wei_to_eth_decimal, wei_to_gwei_decimal};
use eth_utils::{get_function_input_data, get_function_name};

pub use rlp;
cfg_native! {
use std::path::PathBuf;
Expand Down Expand Up @@ -164,8 +171,12 @@ use erc20::get_token_decimals;
pub(crate) mod eth_swap_v2;
use eth_swap_v2::{extract_id_from_tx_data, EthPaymentType, PaymentMethod, SpendTxSearchParams};

pub mod eth_utils;
pub mod tron;

pub const ETH_PROTOCOL_TYPE: &str = "ETH";
pub const ERC20_PROTOCOL_TYPE: &str = "ERC20";

/// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol
/// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852
/// Ropsten: https://ropsten.etherscan.io/address/0x7bc1bbdd6a0a722fc9bffc49c921b685ecb84b94
Expand Down Expand Up @@ -6248,77 +6259,6 @@ fn validate_fee_impl(coin: EthCoin, validate_fee_args: EthValidateFeeArgs<'_>) -
Box::new(fut.boxed().compat())
}

fn get_function_input_data(decoded: &[Token], func: &Function, index: usize) -> Result<Token, String> {
decoded.get(index).cloned().ok_or(format!(
"Missing input in function {}: No input found at index {}",
func.name.clone(),
index
))
}

fn get_function_name(name: &str, watcher_reward: bool) -> String {
if watcher_reward {
format!("{}{}", name, "Reward")
} else {
name.to_owned()
}
}

pub fn addr_from_raw_pubkey(pubkey: &[u8]) -> Result<Address, String> {
let pubkey = try_s!(PublicKey::from_slice(pubkey).map_err(|e| ERRL!("{:?}", e)));
let eth_public = Public::from_slice(&pubkey.serialize_uncompressed()[1..65]);
Ok(public_to_address(&eth_public))
}

pub fn addr_from_pubkey_str(pubkey: &str) -> Result<String, String> {
let pubkey_bytes = try_s!(hex::decode(pubkey));
let addr = try_s!(addr_from_raw_pubkey(&pubkey_bytes));
Ok(format!("{:#02x}", addr))
}

fn display_u256_with_decimal_point(number: U256, decimals: u8) -> String {
let mut string = number.to_string();
let decimals = decimals as usize;
if string.len() <= decimals {
string.insert_str(0, &"0".repeat(decimals - string.len() + 1));
}

string.insert(string.len() - decimals, '.');
string.trim_end_matches('0').into()
}

/// Converts 'number' to value with decimal point and shifts it left by 'decimals' places
pub fn u256_to_big_decimal(number: U256, decimals: u8) -> NumConversResult<BigDecimal> {
let string = display_u256_with_decimal_point(number, decimals);
Ok(string.parse::<BigDecimal>()?)
}

/// Shifts 'number' with decimal point right by 'decimals' places and converts it to U256 value
pub fn wei_from_big_decimal(amount: &BigDecimal, decimals: u8) -> NumConversResult<U256> {
let mut amount = amount.to_string();
let dot = amount.find(|c| c == '.');
let decimals = decimals as usize;
if let Some(index) = dot {
let mut fractional = amount.split_off(index);
// remove the dot from fractional part
fractional.remove(0);
if fractional.len() < decimals {
fractional.insert_str(fractional.len(), &"0".repeat(decimals - fractional.len()));
}
fractional.truncate(decimals);
amount.push_str(&fractional);
} else {
amount.insert_str(amount.len(), &"0".repeat(decimals));
}
U256::from_dec_str(&amount).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))
}

pub fn wei_from_gwei_decimal(bigdec: &BigDecimal) -> NumConversResult<U256> {
wei_from_big_decimal(bigdec, ETH_GWEI_DECIMALS)
}

pub fn wei_to_gwei_decimal(wei: U256) -> NumConversResult<BigDecimal> { u256_to_big_decimal(wei, ETH_GWEI_DECIMALS) }

impl Transaction for SignedEthTx {
fn tx_hex(&self) -> Vec<u8> { rlp::encode(self).to_vec() }

Expand Down
35 changes: 29 additions & 6 deletions mm2src/coins/eth/erc20.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use super::{ERC20_PROTOCOL_TYPE, ETH_PROTOCOL_TYPE};
use crate::eth::web3_transport::Web3Transport;
use crate::eth::{EthCoin, ERC20_CONTRACT};
use crate::{CoinsContext, MarketCoinOps, MmCoinEnum};
use crate::{CoinsContext, MarketCoinOps, MmCoinEnum, Ticker};
use ethabi::Token;
use ethereum_types::Address;
use futures_util::TryFutureExt;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::mm_error::MmResult;
use std::str::FromStr;
use web3::types::{BlockId, BlockNumber, CallRequest};
use web3::{Transport, Web3};

Expand Down Expand Up @@ -70,19 +72,40 @@ pub async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result
Ok(Erc20TokenInfo { symbol, decimals })
}

/// Finds eth platfrom coin in coins config by chain_id and returns its ticker.
pub fn get_platform_ticker(ctx: &MmArc, chain_id: u64) -> Option<Ticker> {
ctx.conf["coins"].as_array()?.iter().find_map(|coin| {
let protocol = coin.get("protocol")?;
let protocol_type = protocol.get("type")?.as_str()?;
if protocol_type != ETH_PROTOCOL_TYPE {
return None;
}
let coin_chain_id = protocol.get("protocol_data")?.get("chain_id")?.as_u64()?;
if coin_chain_id == chain_id {
coin.get("coin")?.as_str().map(|s| s.to_string())
} else {
None
}
})
}

/// Finds if an ERC20 token is in coins config by its contract address and returns its ticker.
pub fn get_erc20_ticker_by_contract_address(ctx: &MmArc, platform: &str, contract_address: &str) -> Option<String> {
pub fn get_erc20_ticker_by_contract_address(
ctx: &MmArc,
platform_ticker: &str,
contract_address: &Address,
) -> Option<String> {
ctx.conf["coins"].as_array()?.iter().find_map(|coin| {
let protocol = coin.get("protocol")?;
let protocol_type = protocol.get("type")?.as_str()?;
if protocol_type != "ERC20" {
if protocol_type != ERC20_PROTOCOL_TYPE {
return None;
}
let protocol_data = protocol.get("protocol_data")?;
let coin_platform = protocol_data.get("platform")?.as_str()?;
let coin_contract_address = protocol_data.get("contract_address")?.as_str()?;
let coin_platform_ticker = protocol_data.get("platform")?.as_str()?;
let coin_contract_address = Address::from_str(protocol_data.get("contract_address")?.as_str()?).ok()?;

if coin_platform == platform && coin_contract_address == contract_address {
if coin_platform_ticker == platform_ticker && &coin_contract_address == contract_address {
coin.get("coin")?.as_str().map(|s| s.to_string())
} else {
None
Expand Down
107 changes: 107 additions & 0 deletions mm2src/coins/eth/eth_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use super::{ETH_DECIMALS, ETH_GWEI_DECIMALS};
use crate::{NumConversError, NumConversResult};
use ethabi::{Function, Token};
use ethereum_types::{Address, FromDecStrErr, U256};
use ethkey::{public_to_address, Public};
use mm2_err_handle::prelude::MapToMmResult;
use mm2_number::{BigDecimal, MmNumber};
use secp256k1::PublicKey;

pub(crate) fn get_function_input_data(decoded: &[Token], func: &Function, index: usize) -> Result<Token, String> {
decoded.get(index).cloned().ok_or(format!(
"Missing input in function {}: No input found at index {}",
func.name.clone(),
index
))
}

pub(crate) fn get_function_name(name: &str, watcher_reward: bool) -> String {
if watcher_reward {
format!("{}{}", name, "Reward")
} else {
name.to_owned()
}
}

pub fn addr_from_raw_pubkey(pubkey: &[u8]) -> Result<Address, String> {
let pubkey = try_s!(PublicKey::from_slice(pubkey).map_err(|e| ERRL!("{:?}", e)));
let eth_public = Public::from_slice(&pubkey.serialize_uncompressed()[1..65]);
Ok(public_to_address(&eth_public))
}

pub fn addr_from_pubkey_str(pubkey: &str) -> Result<String, String> {
let pubkey_bytes = try_s!(hex::decode(pubkey));
let addr = try_s!(addr_from_raw_pubkey(&pubkey_bytes));
Ok(format!("{:#02x}", addr))
}

pub(crate) fn display_u256_with_decimal_point(number: U256, decimals: u8) -> String {
let mut string = number.to_string();
let decimals = decimals as usize;
if string.len() <= decimals {
string.insert_str(0, &"0".repeat(decimals - string.len() + 1));
}

string.insert(string.len() - decimals, '.');
string.trim_end_matches('0').into()
}

/// Converts 'number' to value with decimal point and shifts it left by 'decimals' places
pub fn u256_to_big_decimal(number: U256, decimals: u8) -> NumConversResult<BigDecimal> {
let string = display_u256_with_decimal_point(number, decimals);
Ok(string.parse::<BigDecimal>()?)
}

/// Shifts 'number' with decimal point right by 'decimals' places and converts it to U256 value
pub fn wei_from_big_decimal(amount: &BigDecimal, decimals: u8) -> NumConversResult<U256> {
let mut amount = amount.to_string();
let dot = amount.find(|c| c == '.');
let decimals = decimals as usize;
if let Some(index) = dot {
let mut fractional = amount.split_off(index);
// remove the dot from fractional part
fractional.remove(0);
if fractional.len() < decimals {
fractional.insert_str(fractional.len(), &"0".repeat(decimals - fractional.len()));
}
fractional.truncate(decimals);
amount.push_str(&fractional);
} else {
amount.insert_str(amount.len(), &"0".repeat(decimals));
}
U256::from_dec_str(&amount).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))
}

/// Converts BigDecimal gwei value to wei value as U256
#[inline(always)]
pub fn wei_from_gwei_decimal(bigdec: &BigDecimal) -> NumConversResult<U256> {
wei_from_big_decimal(bigdec, ETH_GWEI_DECIMALS)
}

/// Converts a U256 wei value to an gwei value as a BigDecimal
#[inline(always)]
pub fn wei_to_gwei_decimal(wei: U256) -> NumConversResult<BigDecimal> { u256_to_big_decimal(wei, ETH_GWEI_DECIMALS) }

/// Converts a U256 wei value to an ETH value as a BigDecimal
/// TODO: use wei_to_eth_decimal instead of u256_to_big_decimal(gas_cost_wei, ETH_DECIMALS)
#[inline(always)]
pub fn wei_to_eth_decimal(wei: U256) -> NumConversResult<BigDecimal> { u256_to_big_decimal(wei, ETH_DECIMALS) }

#[inline]
pub fn mm_number_to_u256(mm_number: &MmNumber) -> Result<U256, FromDecStrErr> {
U256::from_dec_str(mm_number.to_ratio().to_integer().to_string().as_str())
}

#[inline]
pub fn mm_number_from_u256(u256: U256) -> MmNumber { MmNumber::from(u256.to_string().as_str()) }

#[inline]
pub fn wei_from_coins_mm_number(mm_number: &MmNumber, decimals: u8) -> NumConversResult<U256> {
wei_from_big_decimal(&mm_number.to_decimal(), decimals)
}

#[inline]
#[allow(unused)]
pub fn wei_to_coins_mm_number(u256: U256, decimals: u8) -> NumConversResult<MmNumber> {
Ok(MmNumber::from(u256_to_big_decimal(u256, decimals)?))
}
23 changes: 18 additions & 5 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use crypto::{derive_secp256k1_secret, Bip32Error, Bip44Chain, CryptoCtx, CryptoC
Secp256k1ExtendedPublicKey, Secp256k1Secret, WithHwRpcError};
use derive_more::Display;
use enum_derives::{EnumFromStringify, EnumFromTrait};
use ethereum_types::{H256, H264, H520, U256};
use ethereum_types::{Address as EthAddress, H256, H264, H520, U256};
use futures::compat::Future01CompatExt;
use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard};
use futures::{FutureExt, TryFutureExt};
Expand Down Expand Up @@ -309,6 +309,7 @@ pub type ValidateTakerFundingSpendPreimageResult = MmResult<(), ValidateTakerFun
pub type ValidateTakerPaymentSpendPreimageResult = MmResult<(), ValidateTakerPaymentSpendPreimageError>;

pub type IguanaPrivKey = Secp256k1Secret;
pub type Ticker = String;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just makes things more confusing, an example, it's not used in this module.

I would create a proper type instead of creating an alias, which would also resolve this issue.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I indeed thought about creating such a new type but it would require to change a lot of code, if deploy it properly everywhere. So I decided to make a small step to that.
I am dealing with multiple various coins/tokens in this liquidity routing PR and it is easy to get lost when String is used everywhere for tickers. High level types with the 'Ticker' alias like below allow better code understanding :
inner: HashMap<(Ticker, Ticker), LrData> or fn new_with_src_token(src_token: Ticker, orders: Vec<RpcOrderbookEntryV2>)
(Ofc I agree the new type is much better)


// Constants for logs used in tests
pub const INVALID_SENDER_ERR_LOG: &str = "Invalid sender";
Expand Down Expand Up @@ -4590,18 +4591,26 @@ pub enum CustomTokenError {
fmt = "Token with the same ticker already exists in coins configs, ticker in config: {}",
ticker_in_config
)]
DuplicateTickerInConfig { ticker_in_config: String },
DuplicateTickerInConfig {
ticker_in_config: String,
},
#[display(
fmt = "Token with the same contract address already exists in coins configs, ticker in config: {}",
ticker_in_config
)]
DuplicateContractInConfig { ticker_in_config: String },
DuplicateContractInConfig {
ticker_in_config: String,
},
#[display(
fmt = "Token is already activated, ticker: {}, contract address: {}",
ticker,
contract_address
)]
TokenWithSameContractAlreadyActivated { ticker: String, contract_address: String },
TokenWithSameContractAlreadyActivated {
ticker: String,
contract_address: String,
},
InvalidTokenAddress,
}

impl CoinProtocol {
Expand Down Expand Up @@ -4667,7 +4676,11 @@ impl CoinProtocol {
// if it is duplicated in config, we will have two orderbooks one using the ticker and one using the contract address.
// Todo: We should use the contract address for orderbook topics instead of the ticker once we make custom tokens non-wallet only.
// If a coin is added to the config later, users who added it as a custom token and did not update will not see the orderbook.
if let Some(existing_ticker) = get_erc20_ticker_by_contract_address(ctx, platform, contract_address) {
if let Some(existing_ticker) = get_erc20_ticker_by_contract_address(
ctx,
platform,
&EthAddress::from_str(contract_address).map_err(|_| MmError::new(CustomTokenError::InvalidTokenAddress))?,
) {
return Err(MmError::new(CustomTokenError::DuplicateContractInConfig {
ticker_in_config: existing_ticker,
}));
Expand Down
1 change: 1 addition & 0 deletions mm2src/coins_activation/src/erc20_token_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ impl TryFromCoinProtocol for Erc20Protocol {
contract_address,
} => {
let token_addr = valid_addr_from_str(&contract_address).map_err(|_| CoinProtocol::ERC20 {
// TODO: maybe add error description to this err (we're losing 'Invalid address checksum' here)
Comment thread
shamardy marked this conversation as resolved.
platform: platform.clone(),
contract_address,
})?;
Expand Down
1 change: 1 addition & 0 deletions mm2src/mm2_main/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ coins_activation = { path = "../coins_activation", features = ["for-tests"] }
common = { path = "../common", features = ["for-tests"] }
mm2_test_helpers = { path = "../mm2_test_helpers" }
trading_api = { path = "../trading_api", features = ["for-tests"] }
env_logger.workspace = true
mocktopus.workspace = true
testcontainers.workspace = true
web3 = { workspace = true, default-features = false, features = ["http-rustls-tls"] }
Expand Down
Loading
Loading