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

feat(LRAPI): add 1inch classic swap rpc #2222

Open
wants to merge 26 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
14e695d
change wei to big decimal macro to fn (for easier using in other crates)
dimxy Sep 16, 2024
aad3739
add chain_id from conf fn
dimxy Sep 16, 2024
5df753b
add code to connect 1inch api, add rpcs to use classic swap api
dimxy Sep 16, 2024
57192ca
refactor on review notes
dimxy Sep 20, 2024
1c31a7a
added doc comment for swap params
dimxy Sep 20, 2024
3610147
refactor on review notes (more)
dimxy Sep 20, 2024
23d8f1d
refactor on review notes (more)
dimxy Sep 22, 2024
18466fc
fix fmt
dimxy Sep 22, 2024
116fb0c
Merge branch 'dev' into 1inch-classic-swap
dimxy Oct 11, 2024
5ec045f
fix review notes
dimxy Oct 16, 2024
7a1282d
add 'deny unknown fields' for 1inch rpcs
dimxy Oct 17, 2024
28a645d
return api response amount as DetailedAmount
dimxy Oct 17, 2024
ef93d1a
refactor api errors as c-like structs
dimxy Oct 17, 2024
e68cfc2
add two informational 1inch rpcs (to return available protocols and t…
dimxy Oct 18, 2024
5ab3b19
more review notes
dimxy Oct 18, 2024
e040bca
add doc for strip 0x macro
dimxy Oct 18, 2024
a19063a
add test for 1inch json deserialisation
dimxy Oct 19, 2024
d457bee
add 1inch response web link validation
dimxy Oct 26, 2024
6a150a5
move approve allowance rpcs to mm2_main crate
dimxy Oct 26, 2024
c175191
add lp_command/rpc.rs source file
dimxy Oct 26, 2024
257d4b9
changed StatusCode for approve rpc errors
dimxy Oct 26, 2024
ce96d87
fix cargo fmt
dimxy Oct 26, 2024
65c4853
fix review notes (including: rename approve to approve_token rpc, add…
dimxy Nov 9, 2024
df5f045
refactor eth type import
dimxy Nov 9, 2024
57efd04
Merge branch 'dev' into 1inch-classic-swap
dimxy Nov 11, 2024
7b5bed3
fix fmt
dimxy Nov 11, 2024
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
23 changes: 23 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ members = [
"mm2src/proxy_signature",
"mm2src/rpc_task",
"mm2src/trezor",
"mm2src/trading_api",
]

exclude = [
Expand Down
33 changes: 14 additions & 19 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,20 +310,6 @@ pub type Web3RpcFut<T> = Box<dyn Future<Item = T, Error = MmError<Web3RpcError>>
pub type Web3RpcResult<T> = Result<T, MmError<Web3RpcError>>;
type EthPrivKeyPolicy = PrivKeyPolicy<KeyPair>;

#[macro_export]
macro_rules! wei_from_gwei_decimal {
($big_decimal: expr) => {
$crate::eth::wei_from_big_decimal($big_decimal, $crate::eth::ETH_GWEI_DECIMALS)
};
}

#[macro_export]
macro_rules! wei_to_gwei_decimal {
($gwei: expr) => {
$crate::eth::u256_to_big_decimal($gwei, $crate::eth::ETH_GWEI_DECIMALS)
};
}

#[derive(Clone, Debug)]
pub(crate) struct LegacyGasPrice {
pub(crate) gas_price: U256,
Expand Down Expand Up @@ -368,11 +354,11 @@ impl TryFrom<PayForGasParams> for PayForGasOption {
fn try_from(param: PayForGasParams) -> Result<Self, Self::Error> {
match param {
PayForGasParams::Legacy(legacy) => Ok(Self::Legacy(LegacyGasPrice {
gas_price: wei_from_gwei_decimal!(&legacy.gas_price)?,
gas_price: wei_from_gwei_decimal(&legacy.gas_price)?,
})),
PayForGasParams::Eip1559(eip1559) => Ok(Self::Eip1559(Eip1559FeePerGas {
max_fee_per_gas: wei_from_gwei_decimal!(&eip1559.max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal!(&eip1559.max_priority_fee_per_gas)?,
max_fee_per_gas: wei_from_gwei_decimal(&eip1559.max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal(&eip1559.max_priority_fee_per_gas)?,
})),
}
}
Expand Down Expand Up @@ -866,6 +852,9 @@ impl EthCoinImpl {
let guard = self.erc20_tokens_infos.lock().unwrap();
(*guard).clone()
}

#[inline(always)]
pub fn chain_id(&self) -> u64 { self.chain_id }
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub fn chain_id(&self) -> u64 { self.chain_id }
#[inline(always)]
pub fn chain_id(&self) -> u64 { self.chain_id }

}

async fn get_raw_transaction_impl(coin: EthCoin, req: RawTransactionRequest) -> RawTransactionResult {
Expand Down Expand Up @@ -4539,7 +4528,7 @@ impl EthCoin {
self.call(request, Some(BlockId::Number(BlockNumber::Latest))).await
}

fn allowance(&self, spender: Address) -> Web3RpcFut<U256> {
pub(crate) fn allowance(&self, spender: Address) -> Web3RpcFut<U256> {
let coin = self.clone();
let fut = async move {
match coin.coin_type {
Expand Down Expand Up @@ -4604,7 +4593,7 @@ impl EthCoin {
Box::new(fut.boxed().compat())
}

fn approve(&self, spender: Address, amount: U256) -> EthTxFut {
pub(crate) fn approve(&self, spender: Address, amount: U256) -> EthTxFut {
let coin = self.clone();
let fut = async move {
let token_addr = match coin.coin_type {
Expand Down Expand Up @@ -5963,6 +5952,12 @@ pub fn wei_from_big_decimal(amount: &BigDecimal, decimals: u8) -> NumConversResu
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
52 changes: 26 additions & 26 deletions mm2src/coins/eth/eip1559_gas_fee.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Provides estimations of base and priority fee per gas or fetch estimations from a gas api provider

use super::web3_transport::FeeHistoryResult;
use super::{Web3RpcError, Web3RpcResult};
use crate::{wei_from_gwei_decimal, wei_to_gwei_decimal, EthCoin, NumConversError};
use super::{wei_from_gwei_decimal, wei_to_gwei_decimal, Web3RpcError, Web3RpcResult};
use crate::{EthCoin, NumConversError};
use ethereum_types::U256;
use mm2_err_handle::mm_error::MmError;
use mm2_err_handle::or_mm_error::OrMmError;
Expand Down Expand Up @@ -104,24 +104,24 @@ impl TryFrom<InfuraFeePerGas> for FeePerGasEstimated {

fn try_from(infura_fees: InfuraFeePerGas) -> Result<Self, Self::Error> {
Ok(Self {
base_fee: wei_from_gwei_decimal!(&infura_fees.estimated_base_fee)?,
base_fee: wei_from_gwei_decimal(&infura_fees.estimated_base_fee)?,
low: FeePerGasLevel {
max_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.low.suggested_max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.low.suggested_max_priority_fee_per_gas)?,
max_fee_per_gas: wei_from_gwei_decimal(&infura_fees.low.suggested_max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal(&infura_fees.low.suggested_max_priority_fee_per_gas)?,
min_wait_time: Some(infura_fees.low.min_wait_time_estimate),
max_wait_time: Some(infura_fees.low.max_wait_time_estimate),
},
medium: FeePerGasLevel {
max_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.medium.suggested_max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal!(
&infura_fees.medium.suggested_max_priority_fee_per_gas
max_fee_per_gas: wei_from_gwei_decimal(&infura_fees.medium.suggested_max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal(
&infura_fees.medium.suggested_max_priority_fee_per_gas,
)?,
min_wait_time: Some(infura_fees.medium.min_wait_time_estimate),
max_wait_time: Some(infura_fees.medium.max_wait_time_estimate),
},
high: FeePerGasLevel {
max_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.high.suggested_max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal!(&infura_fees.high.suggested_max_priority_fee_per_gas)?,
max_fee_per_gas: wei_from_gwei_decimal(&infura_fees.high.suggested_max_fee_per_gas)?,
max_priority_fee_per_gas: wei_from_gwei_decimal(&infura_fees.high.suggested_max_priority_fee_per_gas)?,
min_wait_time: Some(infura_fees.high.min_wait_time_estimate),
max_wait_time: Some(infura_fees.high.max_wait_time_estimate),
},
Expand All @@ -143,33 +143,33 @@ impl TryFrom<BlocknativeBlockPricesResponse> for FeePerGasEstimated {
return Ok(FeePerGasEstimated::default());
}
Ok(Self {
base_fee: wei_from_gwei_decimal!(&block_prices.block_prices[0].base_fee_per_gas)?,
base_fee: wei_from_gwei_decimal(&block_prices.block_prices[0].base_fee_per_gas)?,
low: FeePerGasLevel {
max_fee_per_gas: wei_from_gwei_decimal!(
&block_prices.block_prices[0].estimated_prices[2].max_fee_per_gas
max_fee_per_gas: wei_from_gwei_decimal(
&block_prices.block_prices[0].estimated_prices[2].max_fee_per_gas,
)?,
max_priority_fee_per_gas: wei_from_gwei_decimal!(
&block_prices.block_prices[0].estimated_prices[2].max_priority_fee_per_gas
max_priority_fee_per_gas: wei_from_gwei_decimal(
&block_prices.block_prices[0].estimated_prices[2].max_priority_fee_per_gas,
)?,
min_wait_time: None,
max_wait_time: None,
},
medium: FeePerGasLevel {
max_fee_per_gas: wei_from_gwei_decimal!(
&block_prices.block_prices[0].estimated_prices[1].max_fee_per_gas
max_fee_per_gas: wei_from_gwei_decimal(
&block_prices.block_prices[0].estimated_prices[1].max_fee_per_gas,
)?,
max_priority_fee_per_gas: wei_from_gwei_decimal!(
&block_prices.block_prices[0].estimated_prices[1].max_priority_fee_per_gas
max_priority_fee_per_gas: wei_from_gwei_decimal(
&block_prices.block_prices[0].estimated_prices[1].max_priority_fee_per_gas,
)?,
min_wait_time: None,
max_wait_time: None,
},
high: FeePerGasLevel {
max_fee_per_gas: wei_from_gwei_decimal!(
&block_prices.block_prices[0].estimated_prices[0].max_fee_per_gas
max_fee_per_gas: wei_from_gwei_decimal(
&block_prices.block_prices[0].estimated_prices[0].max_fee_per_gas,
)?,
max_priority_fee_per_gas: wei_from_gwei_decimal!(
&block_prices.block_prices[0].estimated_prices[0].max_priority_fee_per_gas
max_priority_fee_per_gas: wei_from_gwei_decimal(
&block_prices.block_prices[0].estimated_prices[0].max_priority_fee_per_gas,
)?,
min_wait_time: None,
max_wait_time: None,
Expand Down Expand Up @@ -260,7 +260,7 @@ impl FeePerGasSimpleEstimator {
let max_priority_fee_per_gas = Self::percentile_of(&level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_index]);
// Convert the priority fee to BigDecimal gwei, falling back to 0 on error.
let max_priority_fee_per_gas_gwei =
wei_to_gwei_decimal!(max_priority_fee_per_gas).unwrap_or_else(|_| BigDecimal::from(0));
wei_to_gwei_decimal(max_priority_fee_per_gas).unwrap_or_else(|_| BigDecimal::from(0));

// Calculate the max fee per gas by adjusting the base fee and adding the priority fee.
let adjust_max_fee =
Expand All @@ -273,7 +273,7 @@ impl FeePerGasSimpleEstimator {

Ok(FeePerGasLevel {
max_priority_fee_per_gas,
max_fee_per_gas: wei_from_gwei_decimal!(&max_fee_per_gas_dec)?,
max_fee_per_gas: wei_from_gwei_decimal(&max_fee_per_gas_dec)?,
// TODO: Consider adding default wait times if applicable (and mark them as uncertain).
min_wait_time: None,
max_wait_time: None,
Expand All @@ -290,7 +290,7 @@ impl FeePerGasSimpleEstimator {
.first()
.cloned()
.unwrap_or_else(|| U256::from(0));
let latest_base_fee_dec = wei_to_gwei_decimal!(latest_base_fee).unwrap_or_else(|_| BigDecimal::from(0));
let latest_base_fee_dec = wei_to_gwei_decimal(latest_base_fee).unwrap_or_else(|_| BigDecimal::from(0));

// The predicted base fee is not used for calculating eip1559 values here and is provided for other purposes
// (f.e if the caller would like to do own estimates of max fee and max priority fee)
Expand Down
4 changes: 2 additions & 2 deletions mm2src/coins/eth/eth_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ impl EthCoin {
.and_then(|t| serde_json::from_value(t).map_err(Into::into))
}

/// Get chain id
pub(crate) async fn chain_id(&self) -> Result<U256, web3::Error> {
/// Get chain id from network
pub(crate) async fn network_chain_id(&self) -> Result<U256, web3::Error> {
self.try_rpc_send("eth_chainId", vec![])
.await
.and_then(|t| serde_json::from_value(t).map_err(Into::into))
Expand Down
83 changes: 79 additions & 4 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#[macro_use] extern crate serde_json;
#[macro_use] extern crate ser_error_derive;

use crate::eth::Web3RpcError;
shamardy marked this conversation as resolved.
Show resolved Hide resolved
use async_trait::async_trait;
use base58::FromBase58Error;
use bip32::ExtendedPrivateKey;
Expand Down Expand Up @@ -218,10 +219,9 @@ pub mod coins_tests;

pub mod eth;
use eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err};
use eth::GetValidEthWithdrawAddError;
use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails,
GetEthAddressError, SignedEthTx};
use ethereum_types::U256;
use eth::{eth_coin_from_conf_and_request, get_eth_address, u256_to_big_decimal, wei_from_big_decimal, EthCoin,
EthGasDetailsErr, EthTxFeeDetails, GetEthAddressError, GetValidEthWithdrawAddError, SignedEthTx};
use ethereum_types::{Address as EthAddress, U256};
shamardy marked this conversation as resolved.
Show resolved Hide resolved

pub mod hd_wallet;
use hd_wallet::{AccountUpdatingError, AddressDerivingError, HDAccountOps, HDAddressId, HDAddressOps, HDCoinAddress,
Expand Down Expand Up @@ -660,6 +660,10 @@ impl TransactionErr {
}
}

impl std::fmt::Display for TransactionErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.get_plain_text_format()) }
}

#[derive(Debug, PartialEq)]
pub enum FoundSwapTxSpend {
Spent(TransactionEnum),
Expand Down Expand Up @@ -5599,3 +5603,74 @@ pub mod for_tests {
}
}
}

#[derive(Debug, Deserialize)]
shamardy marked this conversation as resolved.
Show resolved Hide resolved
pub struct Erc20ApproveRequest {
coin: String,
spender: EthAddress,
amount: BigDecimal,
}

#[derive(Debug, Deserialize)]
pub struct Erc20AllowanceRequest {
coin: String,
spender: EthAddress,
}

#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum Erc20CallError {
#[display(fmt = "No such coin {}", coin)]
NoSuchCoin { coin: String },
#[display(fmt = "Coin not supported {}", coin)]
CoinNotSupported { coin: String },
#[from_stringify("NumConversError")]
#[display(fmt = "Invalid param: {}", _0)]
InvalidParam(String),
onur-ozkan marked this conversation as resolved.
Show resolved Hide resolved
#[from_stringify("TransactionErr")]
#[display(fmt = "Transaction error {}", _0)]
TransactionError(String),
#[from_stringify("Web3RpcError")]
#[display(fmt = "Web3 RPC error {}", _0)]
Web3RpcError(String),
}

impl HttpStatusCode for Erc20CallError {
fn status_code(&self) -> StatusCode {
match self {
Erc20CallError::NoSuchCoin { .. }
| Erc20CallError::CoinNotSupported { .. }
| Erc20CallError::InvalidParam(_)
| Erc20CallError::TransactionError(_)
| Erc20CallError::Web3RpcError(_) => StatusCode::BAD_REQUEST,
shamardy marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/// Call allowance method for ERC20 tokens (see https://eips.ethereum.org/EIPS/eip-20#approve).
/// Returns BigDecimal allowance value.
pub async fn allowance_rpc(ctx: MmArc, req: Erc20AllowanceRequest) -> MmResult<BigDecimal, Erc20CallError> {
let eth_coin = find_erc20_eth_coin(&ctx, &req.coin).await?;
let wei = eth_coin.allowance(req.spender).compat().await?;
let amount = u256_to_big_decimal(wei, eth_coin.decimals())?;
Ok(amount)
}

/// Call approve method for ERC20 coins (see https://eips.ethereum.org/EIPS/eip-20#allowance).
/// Returns approval transaction hash.
pub async fn approve_rpc(ctx: MmArc, req: Erc20ApproveRequest) -> MmResult<BytesJson, Erc20CallError> {
let eth_coin = find_erc20_eth_coin(&ctx, &req.coin).await?;
let amount = wei_from_big_decimal(&req.amount, eth_coin.decimals())?;
let tx = eth_coin.approve(req.spender, amount).compat().await?;
Ok(tx.tx_hash_as_bytes())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Will this be formatted in a right way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this would return a result like that

{
  "mmrpc": "2.0",
  "result": "5136701f11060010841c9708c3eb26f6606a070b8ae43f4b98b6d7b10a545258",
  "id": null
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should include the 0x prefix

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not fixed yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed in 65c4853
I can see couple places where 0x is not added though: send_raw_tx in eth and TransactionNftDetails
We apparently need a dedicated to_string conversion fn for tx_hash.

(But is it convenient for GUI when we return tx hash in different formats for different coins?)

Copy link
Collaborator

@shamardy shamardy Nov 15, 2024

Choose a reason for hiding this comment

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

But is it convenient for GUI when we return tx hash in different formats for different coins

No, we should fix those cases (in a different PR of course). tx hashes and addresses should always be displayed/sent to GUIs with 0x prefix

}

async fn find_erc20_eth_coin(ctx: &MmArc, coin: &str) -> Result<EthCoin, MmError<Erc20CallError>> {
match lp_coinfind_or_err(ctx, coin).await {
Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin),
Ok(_) => Err(MmError::new(Erc20CallError::CoinNotSupported {
coin: coin.to_string(),
})),
Err(_) => Err(MmError::new(Erc20CallError::NoSuchCoin { coin: coin.to_string() })),
}
}
Loading
Loading