From cde2dbd0fb54c3ee9c268e7383d647a9844d99ae Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 29 Dec 2023 16:16:07 +0500 Subject: [PATCH 01/71] add simple eip1559 gas price estimator --- mm2src/coins/eth.rs | 67 +++++++++++++++++++++++- mm2src/coins/eth/fee_estimator.rs | 70 ++++++++++++++++++++++++++ mm2src/coins/eth/web3_transport/mod.rs | 5 ++ 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 mm2src/coins/eth/fee_estimator.rs diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 1c068a26f8..6d1fc731a1 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -114,6 +114,9 @@ mod nonce; use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; +mod fee_estimator; +use fee_estimator::EthPriorityFeeEstimator; + /// 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 @@ -412,6 +415,42 @@ impl TryFrom for EthPrivKeyBuildPolicy { } } + +/// estimated priority level gas fee +#[derive(Clone, Serialize)] +pub struct PriorityFee { + /// estimated max priority tip fee per gas + pub max_priority_fee_per_gas: U256, + /// estimated max fee per gas + pub max_fee_per_gas: U256, + /// estimated transaction min wait time in ms for this priority level (may be not known in case of simple estimations) + pub min_wait_time: Option, + /// estimated transaction max wait time in ms for this priority level (may be not known in case of simple estimations) + pub max_wait_time: Option, +} + +/// Estimated gas price for several priority levels +/// we support low/medium/high levels as we can use api providers which normally support such levels +#[derive(Clone, Serialize)] +pub struct Eip1559EstimatedGasFees { + /// base fee for the next block + pub base_fee: U256, + /// estimated priority fees + pub priority_fees: [PriorityFee; 3], +} + +impl Default for PriorityFee { + fn default() -> Self { + Self { + max_priority_fee_per_gas: U256::from(0), + max_fee_per_gas: U256::from(0), + min_wait_time: None, + max_wait_time: None, + } + } +} + + /// pImpl idiom. pub struct EthCoinImpl { ticker: String, @@ -4618,10 +4657,17 @@ impl EthCoin { .eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]) .await { - Ok(res) => res + Ok(res) => { + println!( + "base_fee_per_gas {:?}", + res.base_fee_per_gas.first() + ); + + res .base_fee_per_gas .first() - .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)), + .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)) + }, Err(e) => { debug!("Error {} on eth_feeHistory request", e); None @@ -4638,6 +4684,23 @@ impl EthCoin { Box::new(fut.boxed().compat()) } + /// Get base fee and suggest priority tip fees (introduced in EIP1559) + pub fn get_eip1559_gas_price(&self) -> Web3RpcFut { + let coin = self.clone(); + let fut = async move { + let fee_history_namespace: EthFeeHistoryNamespace<_> = coin.web3.api(); + let res = fee_history_namespace + .eth_fee_history(U256::from(EthPriorityFeeEstimator::history_depth()), BlockNumber::Latest, EthPriorityFeeEstimator::history_percentiles()) + .await; + + match res { + Ok(fee_history) => Ok(EthPriorityFeeEstimator::estimate_fees(&fee_history)), + Err(_) => Err(MmError::new(Web3RpcError::Internal("All requests failed".into()))), + } + }; + Box::new(fut.boxed().compat()) + } + /// Checks every second till at least one ETH node recognizes that nonce is increased. /// Parity has reliable "nextNonce" method that always returns correct nonce for address. /// But we can't expect that all nodes will always be Parity. diff --git a/mm2src/coins/eth/fee_estimator.rs b/mm2src/coins/eth/fee_estimator.rs new file mode 100644 index 0000000000..2ce1c5087b --- /dev/null +++ b/mm2src/coins/eth/fee_estimator.rs @@ -0,0 +1,70 @@ +use std::convert::TryInto; + +use ethereum_types::U256; +use super::{Eip1559EstimatedGasFees, PriorityFee}; +use super::web3_transport::FeeHistoryResult; + +/// Simple fee estimator paramss for 3 priority levels +/// normally used if provider is not available + +pub struct EthPriorityFeeEstimator {} + +impl EthPriorityFeeEstimator { + + /// depth to look for fee history to estimate priority fees + const FEE_PRIORITY_DEPTH: u64 = 5u64; + + /// percentiles to pass to eth_feeHistory + const HISTORY_PERCENTILES: [f64; 3] = [ 25.0, 50.0, 75.0 ]; + + /// percentiles to calc max priority fee over historical rewards + const CALC_PERCENTILES: [f64; 3] = [ 50.0, 50.0, 50.0 ]; + + /// block depth for eth_feeHistory + pub fn history_depth() -> u64 { + Self::FEE_PRIORITY_DEPTH + } + + /// percentiles for priority rewards obtained with eth_feeHistory + pub fn history_percentiles() -> &'static [f64] { + &Self::HISTORY_PERCENTILES + } + + /// percentile for vector + fn percentile_of(v: &mut Vec, percent: f64) -> U256 { + v.sort(); + + // validate bounds: + let percent = if percent > 100.0 { 100.0 } else { percent }; + let percent = if percent < 0.0 { 0.0 } else { percent }; + + let value_pos = ((v.len() - 1) as f64 * percent / 100.0).round() as usize; + v[value_pos] + } + + /// estimate max priority fee by fee history + pub fn estimate_fees(fee_history: &FeeHistoryResult) -> Eip1559EstimatedGasFees { + + let base_fee = *fee_history.base_fee_per_gas.first().unwrap_or(&U256::from(0)); + let mut priority_fees = vec![]; + for i in 0..Self::HISTORY_PERCENTILES.len() { + let mut level_rewards = fee_history.priority_rewards + .iter() + .map(|rewards| if i < rewards.len() { rewards[i] } else { U256::from(0) }) + .collect::>(); + let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::CALC_PERCENTILES[i]); + let priority_fee = PriorityFee { + max_priority_fee_per_gas, + max_fee_per_gas: base_fee + max_priority_fee_per_gas, + min_wait_time: None, + max_wait_time: None, + }; + priority_fees.push(priority_fee); + } + Eip1559EstimatedGasFees { + base_fee, + priority_fees: priority_fees.try_into().unwrap_or_default(), + } + } + +} \ No newline at end of file diff --git a/mm2src/coins/eth/web3_transport/mod.rs b/mm2src/coins/eth/web3_transport/mod.rs index f8b7d62fbd..cf6bf8a835 100644 --- a/mm2src/coins/eth/web3_transport/mod.rs +++ b/mm2src/coins/eth/web3_transport/mod.rs @@ -111,6 +111,11 @@ pub struct FeeHistoryResult { pub oldest_block: U256, #[serde(rename = "baseFeePerGas")] pub base_fee_per_gas: Vec, + + #[serde(rename = "gasUsedRatio")] + pub gas_used_ratio: Vec, + #[serde(rename = "reward")] + pub priority_rewards: Vec>, } impl EthFeeHistoryNamespace { From 85e8fb22ee3d1cb4122adb92c4b97649535fd1fa Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 17 Jan 2024 21:18:37 +0500 Subject: [PATCH 02/71] change fn get_eip1559_gas_price to async to use as nested call --- mm2src/coins/eth.rs | 80 ++++++++----------------------- mm2src/coins/eth/fee_estimator.rs | 74 +++++++++++++++++++--------- 2 files changed, 72 insertions(+), 82 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6d1fc731a1..a189659efa 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -44,6 +44,7 @@ use ethcore_transaction::{Action, Transaction as UnSignedEthTx, UnverifiedTransa use ethereum_types::{Address, H160, H256, U256}; use ethkey::{public_to_address, KeyPair, Public, Signature}; use ethkey::{sign, verify_address}; +pub use fee_estimator::GasFeeEstimatedInternal; use futures::compat::Future01CompatExt; use futures::future::{join_all, select_ok, try_join_all, Either, FutureExt, TryFutureExt}; use futures01::Future; @@ -115,7 +116,7 @@ use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; mod fee_estimator; -use fee_estimator::EthPriorityFeeEstimator; +use fee_estimator::GasPriorityFeeEstimator; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 @@ -144,7 +145,7 @@ const DEFAULT_LOGS_BLOCK_RANGE: u64 = 1000; const DEFAULT_REQUIRED_CONFIRMATIONS: u8 = 1; -const ETH_DECIMALS: u8 = 18; +pub(crate) const ETH_DECIMALS: u8 = 18; /// Take into account that the dynamic fee may increase by 3% during the swap. const GAS_PRICE_APPROXIMATION_PERCENT_ON_START_SWAP: u64 = 3; @@ -415,42 +416,6 @@ impl TryFrom for EthPrivKeyBuildPolicy { } } - -/// estimated priority level gas fee -#[derive(Clone, Serialize)] -pub struct PriorityFee { - /// estimated max priority tip fee per gas - pub max_priority_fee_per_gas: U256, - /// estimated max fee per gas - pub max_fee_per_gas: U256, - /// estimated transaction min wait time in ms for this priority level (may be not known in case of simple estimations) - pub min_wait_time: Option, - /// estimated transaction max wait time in ms for this priority level (may be not known in case of simple estimations) - pub max_wait_time: Option, -} - -/// Estimated gas price for several priority levels -/// we support low/medium/high levels as we can use api providers which normally support such levels -#[derive(Clone, Serialize)] -pub struct Eip1559EstimatedGasFees { - /// base fee for the next block - pub base_fee: U256, - /// estimated priority fees - pub priority_fees: [PriorityFee; 3], -} - -impl Default for PriorityFee { - fn default() -> Self { - Self { - max_priority_fee_per_gas: U256::from(0), - max_fee_per_gas: U256::from(0), - min_wait_time: None, - max_wait_time: None, - } - } -} - - /// pImpl idiom. pub struct EthCoinImpl { ticker: String, @@ -4657,16 +4622,10 @@ impl EthCoin { .eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]) .await { - Ok(res) => { - println!( - "base_fee_per_gas {:?}", - res.base_fee_per_gas.first() - ); - - res - .base_fee_per_gas - .first() - .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)) + Ok(res) => { + res.base_fee_per_gas + .first() + .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)) }, Err(e) => { debug!("Error {} on eth_feeHistory request", e); @@ -4685,20 +4644,21 @@ impl EthCoin { } /// Get base fee and suggest priority tip fees (introduced in EIP1559) - pub fn get_eip1559_gas_price(&self) -> Web3RpcFut { + pub async fn get_eip1559_gas_price(&self) -> Result> { let coin = self.clone(); - let fut = async move { - let fee_history_namespace: EthFeeHistoryNamespace<_> = coin.web3.api(); - let res = fee_history_namespace - .eth_fee_history(U256::from(EthPriorityFeeEstimator::history_depth()), BlockNumber::Latest, EthPriorityFeeEstimator::history_percentiles()) - .await; + let fee_history_namespace: EthFeeHistoryNamespace<_> = coin.web3.api(); + let res = fee_history_namespace + .eth_fee_history( + U256::from(GasPriorityFeeEstimator::history_depth()), + BlockNumber::Latest, + GasPriorityFeeEstimator::history_percentiles(), + ) + .await; - match res { - Ok(fee_history) => Ok(EthPriorityFeeEstimator::estimate_fees(&fee_history)), - Err(_) => Err(MmError::new(Web3RpcError::Internal("All requests failed".into()))), - } - }; - Box::new(fut.boxed().compat()) + match res { + Ok(fee_history) => Ok(GasPriorityFeeEstimator::estimate_fees(&fee_history)), + Err(_) => Err(MmError::new(Web3RpcError::Internal("All requests failed".into()))), + } } /// Checks every second till at least one ETH node recognizes that nonce is increased. diff --git a/mm2src/coins/eth/fee_estimator.rs b/mm2src/coins/eth/fee_estimator.rs index 2ce1c5087b..11e5fc85ba 100644 --- a/mm2src/coins/eth/fee_estimator.rs +++ b/mm2src/coins/eth/fee_estimator.rs @@ -1,34 +1,65 @@ use std::convert::TryInto; -use ethereum_types::U256; -use super::{Eip1559EstimatedGasFees, PriorityFee}; use super::web3_transport::FeeHistoryResult; +use ethereum_types::U256; + +/// Simple priority fee estimator for 3 levels based on fee history +/// normally used if eth provider is not available -/// Simple fee estimator paramss for 3 priority levels -/// normally used if provider is not available +// Gas fee estimator types -pub struct EthPriorityFeeEstimator {} +/// Estimated priority gas fee +#[derive(Clone)] +pub struct PriorityFee { + /// estimated max priority tip fee per gas + pub max_priority_fee_per_gas: U256, + /// estimated max fee per gas + pub max_fee_per_gas: U256, + /// estimated transaction min wait time in mempool in ms for this priority level + pub min_wait_time: Option, + /// estimated transaction max wait time in mempool in ms for this priority level + pub max_wait_time: Option, +} -impl EthPriorityFeeEstimator { +/// Estimated gas price for several priority levels +/// we support low/medium/high levels as we can use api providers which normally support such levels +#[derive(Default, Clone)] +pub struct GasFeeEstimatedInternal { + /// base fee for the next block + pub base_fee: U256, + /// estimated priority fees + pub priority_fees: [PriorityFee; 3], +} + +impl Default for PriorityFee { + fn default() -> Self { + Self { + max_priority_fee_per_gas: U256::from(0), + max_fee_per_gas: U256::from(0), + min_wait_time: None, + max_wait_time: None, + } + } +} - /// depth to look for fee history to estimate priority fees +/// Eth gas priority fee simple estimator based on eth fee history +pub struct GasPriorityFeeEstimator {} + +impl GasPriorityFeeEstimator { + /// depth to look for fee history to estimate priority fees const FEE_PRIORITY_DEPTH: u64 = 5u64; /// percentiles to pass to eth_feeHistory - const HISTORY_PERCENTILES: [f64; 3] = [ 25.0, 50.0, 75.0 ]; + const HISTORY_PERCENTILES: [f64; 3] = [25.0, 50.0, 75.0]; /// percentiles to calc max priority fee over historical rewards - const CALC_PERCENTILES: [f64; 3] = [ 50.0, 50.0, 50.0 ]; + const CALC_PERCENTILES: [f64; 3] = [50.0, 50.0, 50.0]; /// block depth for eth_feeHistory - pub fn history_depth() -> u64 { - Self::FEE_PRIORITY_DEPTH - } + pub fn history_depth() -> u64 { Self::FEE_PRIORITY_DEPTH } /// percentiles for priority rewards obtained with eth_feeHistory - pub fn history_percentiles() -> &'static [f64] { - &Self::HISTORY_PERCENTILES - } + pub fn history_percentiles() -> &'static [f64] { &Self::HISTORY_PERCENTILES } /// percentile for vector fn percentile_of(v: &mut Vec, percent: f64) -> U256 { @@ -43,12 +74,12 @@ impl EthPriorityFeeEstimator { } /// estimate max priority fee by fee history - pub fn estimate_fees(fee_history: &FeeHistoryResult) -> Eip1559EstimatedGasFees { - + pub fn estimate_fees(fee_history: &FeeHistoryResult) -> GasFeeEstimatedInternal { let base_fee = *fee_history.base_fee_per_gas.first().unwrap_or(&U256::from(0)); let mut priority_fees = vec![]; for i in 0..Self::HISTORY_PERCENTILES.len() { - let mut level_rewards = fee_history.priority_rewards + let mut level_rewards = fee_history + .priority_rewards .iter() .map(|rewards| if i < rewards.len() { rewards[i] } else { U256::from(0) }) .collect::>(); @@ -57,14 +88,13 @@ impl EthPriorityFeeEstimator { max_priority_fee_per_gas, max_fee_per_gas: base_fee + max_priority_fee_per_gas, min_wait_time: None, - max_wait_time: None, + max_wait_time: None, // TODO: maybe fill with some default values (and mark as uncertain)? }; priority_fees.push(priority_fee); } - Eip1559EstimatedGasFees { + GasFeeEstimatedInternal { base_fee, priority_fees: priority_fees.try_into().unwrap_or_default(), } } - -} \ No newline at end of file +} From ca60f7dd1e0a5d83e85be5021e87a6eb87fcb738 Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 17 Jan 2024 21:20:03 +0500 Subject: [PATCH 03/71] add rpc to start/stop gas fee estimator loop and get estimated values --- .../rpc_command/get_gas_priority_fees.rs | 474 ++++++++++++++++++ mm2src/coins/rpc_command/mod.rs | 1 + mm2src/mm2_core/src/mm_ctx.rs | 3 + .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 5 + 4 files changed, 483 insertions(+) create mode 100644 mm2src/coins/rpc_command/get_gas_priority_fees.rs diff --git a/mm2src/coins/rpc_command/get_gas_priority_fees.rs b/mm2src/coins/rpc_command/get_gas_priority_fees.rs new file mode 100644 index 0000000000..f1370b7ffa --- /dev/null +++ b/mm2src/coins/rpc_command/get_gas_priority_fees.rs @@ -0,0 +1,474 @@ +use crate::eth::{u256_to_big_decimal, EthCoin, GasFeeEstimatedInternal, ETH_DECIMALS}; +use crate::AsyncMutex; +use crate::{from_ctx, lp_coinfind, MarketCoinOps, MmCoinEnum, NumConversError}; +use common::executor::{SpawnFuture, Timer}; +use common::log::debug; +use common::{HttpStatusCode, StatusCode}; +use futures::channel::mpsc; +use futures::{select, Future, StreamExt}; +use futures_util::FutureExt; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::mm_error::MmError; +use mm2_number::BigDecimal; +use std::collections::HashSet; +use std::pin::Pin; +use std::sync::Arc; + +/// get_gas_priority_fees.rs +/// RPCs to start/stop priority fees estimator and get estimated fees + +const PRIORITY_FEES_REFRESH_INTERVAL: u32 = 15; // in sec +const MAX_CONCURRENT_STOP_REQUESTS: usize = 10; +const GAS_FEE_ESTIMATOR_NAME: &str = "gas_fee_estimator_loop"; + +pub(crate) type GasFeeEstimatorStopListener = mpsc::Receiver; +pub(crate) type GasFeeEstimatorStopHandle = mpsc::Sender; + +/// Gas fee estimator running loop state +enum GasFeeEstimatorState { + Starting, + Running, + Stopping, + Stopped, +} + +impl Default for GasFeeEstimatorState { + fn default() -> Self { Self::Stopped } +} + +// Errors + +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GasFeeEstimatorError { + #[display(fmt = "Coin not activated or not a EVM coin")] + CoinNotFoundOrSupported, + #[display(fmt = "Coin not connected to fee estimator")] + CoinNotConnected, + #[display(fmt = "Fee estimator is already started")] + AlreadyStarted, + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Cannot start fee estimator if it's currently stopping")] + CannotStartFromStopping, + #[display(fmt = "Fee estimator is already stopping")] + AlreadyStopping, + #[display(fmt = "Fee estimator is not running")] + NotRunning, + #[display(fmt = "Internal error: {}", _0)] + InternalError(String), +} + +impl HttpStatusCode for GasFeeEstimatorError { + fn status_code(&self) -> StatusCode { + match self { + GasFeeEstimatorError::CoinNotFoundOrSupported + | GasFeeEstimatorError::CoinNotConnected + | GasFeeEstimatorError::AlreadyStarted + | GasFeeEstimatorError::AlreadyStopping + | GasFeeEstimatorError::NotRunning + | GasFeeEstimatorError::CannotStartFromStopping => StatusCode::BAD_REQUEST, + GasFeeEstimatorError::Transport(_) | GasFeeEstimatorError::InternalError(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, + } + } +} + +impl From for GasFeeEstimatorError { + fn from(e: NumConversError) -> Self { GasFeeEstimatorError::InternalError(e.to_string()) } +} + +impl From for GasFeeEstimatorError { + fn from(e: String) -> Self { GasFeeEstimatorError::InternalError(e) } +} + +/// Gas fee estimator loop context +pub struct GasFeeEstimatorContext { + estimated_fees: AsyncMutex, + run_state: AsyncMutex, + /// coins that connected to loop and can get fee estimates + using_coins: AsyncMutex>, + /// receiver of signals to stop estimator (if it is not used) + stop_listener: Arc>, + /// sender of signal to stop estimator + stop_handle: GasFeeEstimatorStopHandle, + /// mm2 shutdown listener + shutdown_listener: AsyncMutex + Send + Sync>>>, +} + +impl GasFeeEstimatorContext { + fn new(ctx: MmArc) -> Result { + let shutdown_listener = try_s!(ctx.graceful_shutdown_registry.register_listener()); + let (tx, rx) = mpsc::channel(MAX_CONCURRENT_STOP_REQUESTS); + Ok(Self { + estimated_fees: Default::default(), + run_state: AsyncMutex::new(GasFeeEstimatorState::default()), + using_coins: AsyncMutex::new(HashSet::new()), + stop_listener: Arc::new(AsyncMutex::new(rx)), + stop_handle: tx, + shutdown_listener: AsyncMutex::new(Box::pin(shutdown_listener)), + }) + } + + fn from_ctx(ctx: MmArc) -> Result, String> { + Ok(try_s!(from_ctx(&ctx.clone().gas_fee_estimator_ctx, move || { + GasFeeEstimatorContext::new(ctx) + }))) + } + + /// update period in secs + fn get_refresh_rate() -> f64 { PRIORITY_FEES_REFRESH_INTERVAL as f64 } + + async fn start_if_not_running(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { + let estimator_ctx = Self::from_ctx(ctx.clone())?; + loop { + let mut run_state = estimator_ctx.run_state.lock().await; + match *run_state { + GasFeeEstimatorState::Stopped => { + *run_state = GasFeeEstimatorState::Starting; + drop(run_state); + ctx.spawner() + .spawn(Self::gas_fee_estimator_loop(ctx.clone(), coin.clone())); + return Ok(()); + }, + GasFeeEstimatorState::Running => { + let mut using_coins = estimator_ctx.using_coins.lock().await; + using_coins.insert(coin.ticker().to_string()); + debug!("{GAS_FEE_ESTIMATOR_NAME} coin {} connected", coin.ticker()); + return Ok(()); + }, + GasFeeEstimatorState::Stopping => { + drop(run_state); + let _ = Self::wait_for_stopped(ctx.clone()).await; + }, + GasFeeEstimatorState::Starting => { + drop(run_state); + let _ = Self::wait_for_running(ctx.clone()).await; + }, + } + } + } + + async fn request_to_stop(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { + Self::check_if_coin_connected(ctx.clone(), coin).await?; + let estimator_ctx = Self::from_ctx(ctx)?; + let run_state = estimator_ctx.run_state.lock().await; + if let GasFeeEstimatorState::Running = *run_state { + let mut stop_handle = estimator_ctx.stop_handle.clone(); + stop_handle + .try_send(coin.ticker().to_owned()) + .map_err(|_| MmError::new(GasFeeEstimatorError::InternalError("could not stop".to_string())))?; + debug!("{GAS_FEE_ESTIMATOR_NAME} sent stop request for {}", coin.ticker()); + } else { + debug!( + "{GAS_FEE_ESTIMATOR_NAME} could not stop for {}: coin not connected", + coin.ticker() + ); + return MmError::err(GasFeeEstimatorError::NotRunning); + } + Ok(()) + } + + /// run listen cycle: wait for loop stop or shutdown + /// returns true if shutdown started to exist quickly + async fn listen_for_stop(&self) -> Result> { + let stop_listener = self.stop_listener.clone(); + let mut stop_listener = stop_listener.lock().await; + let mut listen_fut = stop_listener.next().fuse(); + let mut shutdown_listener = self.shutdown_listener.lock().await; + let shutdown_fut = async { shutdown_listener.as_mut().await }.fuse(); + + let (disconnected_coin, shutdown_detected) = select! { + disconnected_coin = listen_fut => (disconnected_coin, false), + _ = Box::pin(shutdown_fut) => (None, true) + }; + + if shutdown_detected { + debug!("{GAS_FEE_ESTIMATOR_NAME} received shutdown request"); + return Ok(true); + } else if let Some(disconnected_coin) = disconnected_coin { + let mut using_coins = self.using_coins.lock().await; + if using_coins.remove(&disconnected_coin) { + debug!("{GAS_FEE_ESTIMATOR_NAME} coin {} disconnected", disconnected_coin); + } + // stop loop if all coins disconnected + if using_coins.is_empty() { + let mut run_state = self.run_state.lock().await; + *run_state = GasFeeEstimatorState::Stopping; + } + } + Ok(false) + } + + async fn wait_for_running(ctx: MmArc) -> Result<(), MmError> { + let estimator_ctx = Self::from_ctx(ctx.clone())?; + loop { + let run_state = estimator_ctx.run_state.lock().await; + if let GasFeeEstimatorState::Running = *run_state { + break; + } + drop(run_state); + Timer::sleep(0.1).await; + } + Ok(()) + } + + async fn wait_for_stopped(ctx: MmArc) -> Result<(), MmError> { + let estimator_ctx = Self::from_ctx(ctx.clone())?; + loop { + let run_state = estimator_ctx.run_state.lock().await; + if let GasFeeEstimatorState::Stopped = *run_state { + break; + } + drop(run_state); + Timer::sleep(0.1).await; + } + Ok(()) + } + + async fn gas_fee_estimator_loop(ctx: MmArc, coin: EthCoin) { + let estimator_ctx = Self::from_ctx(ctx.clone()); + if let Ok(estimator_ctx) = estimator_ctx { + let _ = estimator_ctx.gas_fee_estimator_loop_inner(coin).await; + } + } + + /// Gas fee estimator loop + async fn gas_fee_estimator_loop_inner(&self, coin: EthCoin) -> Result<(), MmError> { + let mut run_state = self.run_state.lock().await; + if let GasFeeEstimatorState::Starting = *run_state { + *run_state = GasFeeEstimatorState::Running; + let mut using_coins = self.using_coins.lock().await; + using_coins.insert(coin.ticker().to_string()); + debug!("{GAS_FEE_ESTIMATOR_NAME} started and coin {} connected", coin.ticker()); + } else { + debug!("{GAS_FEE_ESTIMATOR_NAME} could not start from this state, probably already running"); + return MmError::err(GasFeeEstimatorError::InternalError("could not start".to_string())); + } + // release lock: + drop(run_state); + + loop { + let mut run_state = self.run_state.lock().await; + if let GasFeeEstimatorState::Stopping = *run_state { + *run_state = GasFeeEstimatorState::Stopped; + break; + } + drop(run_state); + + let started = common::now_float(); + let estimate_fut = coin.get_eip1559_gas_price().fuse(); + let stop_fut = self.listen_for_stop().fuse(); + let (estimated_res, shutdown_started) = select! { + estimated = Box::pin(estimate_fut) => (estimated, Ok(false)), + shutdown_started = Box::pin(stop_fut) => (Ok(GasFeeEstimatedInternal::default()), shutdown_started) + }; + // use returned bool (instead of run_state) to check if shutdown started to exit quickly + if shutdown_started.is_ok() && shutdown_started.unwrap() { + break; + } + + let mut run_state = self.run_state.lock().await; + if let GasFeeEstimatorState::Stopping = *run_state { + *run_state = GasFeeEstimatorState::Stopped; + break; + } + drop(run_state); + + let estimated = match estimated_res { + Ok(estimated) => estimated, + Err(_) => GasFeeEstimatedInternal::default(), // TODO: if fee estimates could not be obtained should we clear values or use previous? + }; + let mut estimated_fees = self.estimated_fees.lock().await; + *estimated_fees = estimated; + drop(estimated_fees); + + let elapsed = common::now_float() - started; + debug!( + "{GAS_FEE_ESTIMATOR_NAME} getting estimated values processed in {} seconds", + elapsed + ); + + let wait_secs = GasFeeEstimatorContext::get_refresh_rate() - elapsed; + let wait_secs = if wait_secs < 0.0 { 0.0 } else { wait_secs }; + let sleep_fut = Timer::sleep(wait_secs).fuse(); + let stop_fut = self.listen_for_stop().fuse(); + let shutdown_started = select! { + _ = Box::pin(sleep_fut) => Ok(false), + shutdown_started = Box::pin(stop_fut) => shutdown_started + }; + if shutdown_started.is_ok() && shutdown_started.unwrap() { + break; + } + } + debug!("{GAS_FEE_ESTIMATOR_NAME} stopped"); + Ok(()) + } + + async fn get_estimated_fees( + ctx: MmArc, + coin: &EthCoin, + ) -> Result> { + Self::check_if_coin_connected(ctx.clone(), coin).await?; + let estimator_ctx = Self::from_ctx(ctx.clone())?; + let estimated_fees = estimator_ctx.estimated_fees.lock().await; + Ok(estimated_fees.clone()) + } + + async fn check_if_coin_connected(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { + let estimator_ctx = Self::from_ctx(ctx.clone())?; + let using_coins = estimator_ctx.using_coins.lock().await; + if using_coins.get(&coin.ticker().to_string()).is_none() { + return MmError::err(GasFeeEstimatorError::CoinNotConnected); + } + Ok(()) + } +} + +// Rpc request/response/result + +#[derive(Deserialize)] +pub struct GasFeeEstimatorStartStopRequest { + coin: String, +} + +#[derive(Serialize)] +pub struct GasFeeEstimatorStartStopResponse { + result: String, +} + +impl GasFeeEstimatorStartStopResponse { + #[allow(dead_code)] + pub fn get_result(&self) -> String { self.result.clone() } +} + +pub type GasFeeEstimatorStartStopResult = Result>; + +#[derive(Deserialize)] +pub struct GasFeeEstimatedRequest { + coin: String, +} + +/// Estimated priority gas fee +#[derive(Serialize)] +pub struct GasPriorityFee { + /// estimated max priority tip fee per gas + pub max_priority_fee_per_gas: BigDecimal, + /// estimated max fee per gas + pub max_fee_per_gas: BigDecimal, + /// estimated transaction min wait time in mempool in ms for this priority level + pub min_wait_time: Option, + /// estimated transaction max wait time in mempool in ms for this priority level + pub max_wait_time: Option, +} + +#[derive(Serialize)] +pub struct GasFeeEstimatedResponse { + pub base_fee: BigDecimal, + pub low_fee: GasPriorityFee, + pub medium_fee: GasPriorityFee, + pub high_fee: GasPriorityFee, +} + +pub type GasFeeEstimatedResult = Result>; + +/// Start gas priority fee estimator loop +pub async fn start_gas_fee_estimator( + ctx: MmArc, + req: GasFeeEstimatorStartStopRequest, +) -> GasFeeEstimatorStartStopResult { + let coin = match lp_coinfind(&ctx, &req.coin).await { + Ok(Some(coin)) => coin, + Ok(None) | Err(_) => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + }; + let coin = match coin { + MmCoinEnum::EthCoin(eth) => eth, + _ => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + }; + + GasFeeEstimatorContext::start_if_not_running(ctx, &coin).await?; + Ok(GasFeeEstimatorStartStopResponse { + result: "Success".to_string(), + }) +} + +/// Stop gas priority fee estimator loop +pub async fn stop_gas_fee_estimator( + ctx: MmArc, + req: GasFeeEstimatorStartStopRequest, +) -> GasFeeEstimatorStartStopResult { + let coin = match lp_coinfind(&ctx, &req.coin).await { + Ok(Some(coin)) => coin, + Ok(None) | Err(_) => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + }; + let coin = match coin { + MmCoinEnum::EthCoin(eth) => eth, + _ => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + }; + + GasFeeEstimatorContext::request_to_stop(ctx, &coin).await?; + Ok(GasFeeEstimatorStartStopResponse { + result: "Success".to_string(), + }) +} + +/// Stop gas priority fee estimator loop +pub async fn get_gas_priority_fees(ctx: MmArc, req: GasFeeEstimatedRequest) -> GasFeeEstimatedResult { + let coin = match lp_coinfind(&ctx, &req.coin).await { + Ok(Some(coin)) => coin, + Ok(None) | Err(_) => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + }; + + // just check this is a eth-like coin. + // we will return data if estimator is running, for any eth-like coin + let coin = match coin { + MmCoinEnum::EthCoin(eth) => eth, + _ => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + }; + + let estimated_fees = GasFeeEstimatorContext::get_estimated_fees(ctx, &coin).await?; + let eth_decimals = ETH_DECIMALS; + Ok(GasFeeEstimatedResponse { + base_fee: u256_to_big_decimal(estimated_fees.base_fee, eth_decimals)?, + + low_fee: GasPriorityFee { + max_priority_fee_per_gas: u256_to_big_decimal( + estimated_fees.priority_fees[0].max_priority_fee_per_gas, + eth_decimals, + )?, + max_fee_per_gas: u256_to_big_decimal( + estimated_fees.priority_fees[0].max_priority_fee_per_gas, + eth_decimals, + )?, + min_wait_time: estimated_fees.priority_fees[0].min_wait_time, + max_wait_time: estimated_fees.priority_fees[0].min_wait_time, + }, + + medium_fee: GasPriorityFee { + max_priority_fee_per_gas: u256_to_big_decimal( + estimated_fees.priority_fees[1].max_priority_fee_per_gas, + eth_decimals, + )?, + max_fee_per_gas: u256_to_big_decimal( + estimated_fees.priority_fees[1].max_priority_fee_per_gas, + eth_decimals, + )?, + min_wait_time: estimated_fees.priority_fees[1].min_wait_time, + max_wait_time: estimated_fees.priority_fees[1].min_wait_time, + }, + + high_fee: GasPriorityFee { + max_priority_fee_per_gas: u256_to_big_decimal( + estimated_fees.priority_fees[2].max_priority_fee_per_gas, + eth_decimals, + )?, + max_fee_per_gas: u256_to_big_decimal( + estimated_fees.priority_fees[2].max_priority_fee_per_gas, + eth_decimals, + )?, + min_wait_time: estimated_fees.priority_fees[2].min_wait_time, + max_wait_time: estimated_fees.priority_fees[2].min_wait_time, + }, + }) +} diff --git a/mm2src/coins/rpc_command/mod.rs b/mm2src/coins/rpc_command/mod.rs index c401853b2d..9a4cef0c2c 100644 --- a/mm2src/coins/rpc_command/mod.rs +++ b/mm2src/coins/rpc_command/mod.rs @@ -1,6 +1,7 @@ pub mod account_balance; pub mod get_current_mtp; pub mod get_enabled_coins; +pub mod get_gas_priority_fees; pub mod get_new_address; pub mod hd_account_balance_rpc_error; pub mod init_account_balance; diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index 04de2e4d87..1c63301a89 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -135,6 +135,8 @@ pub struct MmCtx { /// asynchronous handle for rusqlite connection. #[cfg(not(target_arch = "wasm32"))] pub async_sqlite_connection: Constructible>>, + /// Context for eth gas priority fee estimator loop + pub gas_fee_estimator_ctx: Mutex>>, } impl MmCtx { @@ -181,6 +183,7 @@ impl MmCtx { nft_ctx: Mutex::new(None), #[cfg(not(target_arch = "wasm32"))] async_sqlite_connection: Constructible::default(), + gas_fee_estimator_ctx: Mutex::new(None), } } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 7512829efc..b3f87026e2 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -16,6 +16,8 @@ use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels, ibc_with use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, get_enabled_coins::get_enabled_coins, + get_gas_priority_fees::{get_gas_priority_fees, start_gas_fee_estimator, + stop_gas_fee_estimator}, get_new_address::{cancel_get_new_address, get_new_address, init_get_new_address, init_get_new_address_status, init_get_new_address_user_action}, init_account_balance::{cancel_account_balance, init_account_balance, @@ -202,6 +204,9 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, ibc_chains).await, "ibc_transfer_channels" => handle_mmrpc(ctx, request, ibc_transfer_channels).await, "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, + "start_gas_fee_estimator" => handle_mmrpc(ctx, request, start_gas_fee_estimator).await, + "stop_gas_fee_estimator" => handle_mmrpc(ctx, request, stop_gas_fee_estimator).await, + "get_gas_priority_fees" => handle_mmrpc(ctx, request, get_gas_priority_fees).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] From 6788529ddae560c3ba63194cfdfee8e707691428 Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 17 Jan 2024 21:44:12 +0500 Subject: [PATCH 04/71] fix fmt --- mm2src/coins/eth.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index a189659efa..62357bf096 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4622,11 +4622,10 @@ impl EthCoin { .eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]) .await { - Ok(res) => { - res.base_fee_per_gas - .first() - .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)) - }, + Ok(res) => res + .base_fee_per_gas + .first() + .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)), Err(e) => { debug!("Error {} on eth_feeHistory request", e); None From 976628b79c7b33301e03cb01f79ea4f4102a48df Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 11 Feb 2024 18:27:26 +0500 Subject: [PATCH 05/71] add test calls to infura and blocknative gas price prediction api --- mm2src/coins/eth.rs | 35 +- mm2src/coins/eth/eip1559_gas_fee.rs | 469 ++++++++++++++++++ mm2src/coins/eth/fee_estimator.rs | 100 ---- ...priority_fees.rs => get_estimated_fees.rs} | 289 +++++------ mm2src/coins/rpc_command/mod.rs | 2 +- mm2src/mm2_core/src/mm_ctx.rs | 6 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 10 +- 7 files changed, 610 insertions(+), 301 deletions(-) create mode 100644 mm2src/coins/eth/eip1559_gas_fee.rs delete mode 100644 mm2src/coins/eth/fee_estimator.rs rename mm2src/coins/rpc_command/{get_gas_priority_fees.rs => get_estimated_fees.rs} (50%) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 62357bf096..70708b3018 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -37,6 +37,7 @@ use common::{now_ms, wait_until_ms}; use crypto::privkey::key_pair_from_secret; use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy, StandardHDCoinAddress}; use derive_more::Display; +pub use eip1559_gas_fee::FeePerGasEstimated; use enum_from::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; @@ -44,9 +45,8 @@ use ethcore_transaction::{Action, Transaction as UnSignedEthTx, UnverifiedTransa use ethereum_types::{Address, H160, H256, U256}; use ethkey::{public_to_address, KeyPair, Public, Signature}; use ethkey::{sign, verify_address}; -pub use fee_estimator::GasFeeEstimatedInternal; use futures::compat::Future01CompatExt; -use futures::future::{join_all, select_ok, try_join_all, Either, FutureExt, TryFutureExt}; +use futures::future::{join, join_all, select_ok, try_join_all, Either, FutureExt, TryFutureExt}; use futures01::Future; use http::StatusCode; use mm2_core::mm_ctx::{MmArc, MmWeak}; @@ -115,8 +115,8 @@ mod nonce; use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; -mod fee_estimator; -use fee_estimator::GasPriorityFeeEstimator; +mod eip1559_gas_fee; +use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator}; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 @@ -147,6 +147,8 @@ const DEFAULT_REQUIRED_CONFIRMATIONS: u8 = 1; pub(crate) const ETH_DECIMALS: u8 = 18; +pub(crate) const ETH_GWEI_DECIMALS: u8 = 9; + /// Take into account that the dynamic fee may increase by 3% during the swap. const GAS_PRICE_APPROXIMATION_PERCENT_ON_START_SWAP: u64 = 3; /// Take into account that the dynamic fee may increase until the locktime is expired @@ -4642,21 +4644,20 @@ impl EthCoin { Box::new(fut.boxed().compat()) } - /// Get base fee and suggest priority tip fees (introduced in EIP1559) - pub async fn get_eip1559_gas_price(&self) -> Result> { + /// Get base gas fee and suggest priority tip fees for the next block (see EIP1559) + pub async fn get_eip1559_gas_price(&self) -> Result> { let coin = self.clone(); let fee_history_namespace: EthFeeHistoryNamespace<_> = coin.web3.api(); - let res = fee_history_namespace - .eth_fee_history( - U256::from(GasPriorityFeeEstimator::history_depth()), - BlockNumber::Latest, - GasPriorityFeeEstimator::history_percentiles(), - ) - .await; - - match res { - Ok(fee_history) => Ok(GasPriorityFeeEstimator::estimate_fees(&fee_history)), - Err(_) => Err(MmError::new(Web3RpcError::Internal("All requests failed".into()))), + let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(fee_history_namespace); + let provider_estimator_fut = BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(); + // To call infura use: + // let provider_estimator_fut = InfuraGasApiCaller::fetch_infura_fee_estimation(); + + let (res_history, res_provider) = join(history_estimator_fut, provider_estimator_fut).await; + match (res_history, res_provider) { + (Ok(ref history_est), Err(_)) => Ok(history_est.clone()), + (_, Ok(ref provider_est)) => Ok(provider_est.clone()), + (_, _) => MmError::err(Web3RpcError::Internal("All gas api requests failed".into())), // TODO: send errors } } diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs new file mode 100644 index 0000000000..ad5aa19495 --- /dev/null +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -0,0 +1,469 @@ +use super::web3_transport::{EthFeeHistoryNamespace, FeeHistoryResult}; +use super::{u256_to_big_decimal, Web3RpcError, Web3RpcResult, ETH_GWEI_DECIMALS}; +use common::log::info; +use ethereum_types::U256; +use http::StatusCode; +use mm2_err_handle::mm_error::MmError; +use mm2_err_handle::prelude::*; +use mm2_net::transport::slurp_url_with_headers; +use mm2_number::BigDecimal; +use num_traits::FromPrimitive; +use serde::de::{Deserializer, SeqAccess, Visitor}; +use serde_json::{self as json}; +use std::collections::HashMap; +use std::fmt; +use web3::{types::BlockNumber, Transport}; + +// Estimate base and priority fee per gas +// or fetch estimations from a gas api provider + +const FEE_PER_GAS_LEVELS: usize = 3; + +#[derive(Clone, Debug, Serialize)] +pub enum EstimationSource { + /// filled by default values + Empty, + Simple, + Infura, + Blocknative, +} + +impl Default for EstimationSource { + fn default() -> Self { Self::Empty } +} + +#[derive(Clone, Debug, Serialize)] +pub enum EstimationUnits { + Gwei, +} + +impl Default for EstimationUnits { + fn default() -> Self { Self::Gwei } +} + +/// One priority level estimated max fees +#[derive(Clone, Debug, Serialize)] +pub struct FeePerGasLevel { + /// estimated max priority tip fee per gas in gwei + pub max_priority_fee_per_gas: BigDecimal, + /// estimated max fee per gas in gwei + pub max_fee_per_gas: BigDecimal, + /// estimated transaction min wait time in mempool in ms for this priority level + pub min_wait_time: Option, + /// estimated transaction max wait time in mempool in ms for this priority level + pub max_wait_time: Option, +} + +/// Estimated gas price for several priority levels +/// we support low/medium/high levels as we can use api providers which normally support such levels +#[derive(Default, Debug, Clone, Serialize)] +pub struct FeePerGasEstimated { + /// base fee for the next block in gwei + pub base_fee: BigDecimal, + /// estimated low priority fee + pub low: FeePerGasLevel, + /// estimated medium priority fee + pub medium: FeePerGasLevel, + /// estimated high priority fee + pub high: FeePerGasLevel, + /// which estimator used + pub source: EstimationSource, + /// fee units + pub units: EstimationUnits, + /// base trend (up or down) + pub base_fee_trend: String, + /// priority trend (up or down) + pub priority_fee_trend: String, +} + +impl Default for FeePerGasLevel { + fn default() -> Self { + Self { + max_priority_fee_per_gas: BigDecimal::from(0), + max_fee_per_gas: BigDecimal::from(0), + min_wait_time: None, + max_wait_time: None, + } + } +} + +impl From for FeePerGasEstimated { + fn from(infura_fees: InfuraFeePerGas) -> Self { + Self { + base_fee: infura_fees.estimated_base_fee, + low: FeePerGasLevel { + max_fee_per_gas: infura_fees.low.suggested_max_fee_per_gas, + max_priority_fee_per_gas: 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: infura_fees.medium.suggested_max_fee_per_gas, + max_priority_fee_per_gas: 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: infura_fees.high.suggested_max_fee_per_gas, + max_priority_fee_per_gas: 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), + }, + source: EstimationSource::Infura, + units: EstimationUnits::Gwei, + base_fee_trend: infura_fees.base_fee_trend, + priority_fee_trend: infura_fees.priority_fee_trend, + } + } +} + +impl From for FeePerGasEstimated { + fn from(block_prices: BlocknativeBlockPricesResponse) -> Self { + if block_prices.block_prices.is_empty() { + return FeePerGasEstimated::default(); + } + if block_prices.block_prices[0].estimated_prices.len() < 3 { + return FeePerGasEstimated::default(); + } + Self { + base_fee: block_prices.block_prices[0].base_fee_per_gas.clone(), + low: FeePerGasLevel { + max_fee_per_gas: block_prices.block_prices[0].estimated_prices[2].max_fee_per_gas.clone(), + max_priority_fee_per_gas: block_prices.block_prices[0].estimated_prices[2] + .max_priority_fee_per_gas + .clone(), + min_wait_time: None, + max_wait_time: None, + }, + medium: FeePerGasLevel { + max_fee_per_gas: block_prices.block_prices[0].estimated_prices[1].max_fee_per_gas.clone(), + max_priority_fee_per_gas: block_prices.block_prices[0].estimated_prices[1] + .max_priority_fee_per_gas + .clone(), + min_wait_time: None, + max_wait_time: None, + }, + high: FeePerGasLevel { + max_fee_per_gas: block_prices.block_prices[0].estimated_prices[0].max_fee_per_gas.clone(), + max_priority_fee_per_gas: block_prices.block_prices[0].estimated_prices[0] + .max_priority_fee_per_gas + .clone(), + min_wait_time: None, + max_wait_time: None, + }, + source: EstimationSource::Blocknative, + units: EstimationUnits::Gwei, + base_fee_trend: String::default(), + priority_fee_trend: String::default(), + } + } +} + +/// Simple priority fee per gas estimator based on fee history +/// normally used if gas api provider is not available +pub(super) struct FeePerGasSimpleEstimator {} + +impl FeePerGasSimpleEstimator { + // TODO: add minimal max fee and priority fee + /// depth to look for fee history to estimate priority fees + const FEE_PRIORITY_DEPTH: u64 = 5u64; + + /// percentiles to pass to eth_feeHistory + const HISTORY_PERCENTILES: [f64; FEE_PER_GAS_LEVELS] = [25.0, 50.0, 75.0]; + + /// percentiles to calc max priority fee over historical rewards + const CALC_PERCENTILES: [f64; FEE_PER_GAS_LEVELS] = [50.0, 50.0, 50.0]; + + /// adjustment for max priority fee picked up by sampling + const ADJUST_MAX_FEE: [f64; FEE_PER_GAS_LEVELS] = [1.0, 1.0, 1.0]; + + /// adjustment for max priority fee picked up by sampling + const ADJUST_MAX_PRIORITY_FEE: [f64; FEE_PER_GAS_LEVELS] = [1.0, 1.0, 1.0]; + + /// block depth for eth_feeHistory + pub fn history_depth() -> u64 { Self::FEE_PRIORITY_DEPTH } + + /// percentiles for priority rewards obtained with eth_feeHistory + pub fn history_percentiles() -> &'static [f64] { &Self::HISTORY_PERCENTILES } + + /// percentile for vector + fn percentile_of(v: &mut Vec, percent: f64) -> U256 { + v.sort(); + + // validate bounds: + let percent = if percent > 100.0 { 100.0 } else { percent }; + let percent = if percent < 0.0 { 0.0 } else { percent }; + + let value_pos = ((v.len() - 1) as f64 * percent / 100.0).round() as usize; + v[value_pos] + } + + /// Estimate simplified gas priority fees based on fee history + pub async fn estimate_fee_by_history( + fee_history_namespace: EthFeeHistoryNamespace, + ) -> Web3RpcResult { + let res = fee_history_namespace + .eth_fee_history( + U256::from(Self::history_depth()), + BlockNumber::Latest, + Self::history_percentiles(), + ) + .await; + + match res { + Ok(fee_history) => Ok(Self::calculate_with_history(&fee_history)), + Err(_) => MmError::err(Web3RpcError::Internal("Eth requests failed".into())), + } + } + + /// estimate priority fees by fee history + fn calculate_with_history(fee_history: &FeeHistoryResult) -> FeePerGasEstimated { + let base_fee = *fee_history.base_fee_per_gas.first().unwrap_or(&U256::from(0)); + let base_fee = u256_to_big_decimal(base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); + let mut priority_fees = vec![]; + for i in 0..Self::HISTORY_PERCENTILES.len() { + let mut level_rewards = fee_history + .priority_rewards + .iter() + .map(|rewards| if i < rewards.len() { rewards[i] } else { U256::from(0) }) + .collect::>(); + + let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::CALC_PERCENTILES[i]); + let max_priority_fee_per_gas = u256_to_big_decimal(max_priority_fee_per_gas, ETH_GWEI_DECIMALS) + .unwrap_or_else(|_| BigDecimal::from(0)); + let max_fee_per_gas = base_fee.clone() + * BigDecimal::from_f64(Self::ADJUST_MAX_FEE[i]).unwrap_or_else(|| BigDecimal::from(0)) + + max_priority_fee_per_gas.clone() + * BigDecimal::from_f64(Self::ADJUST_MAX_PRIORITY_FEE[i]).unwrap_or_else(|| BigDecimal::from(0)); // TODO maybe use checked ops + let priority_fee = FeePerGasLevel { + max_priority_fee_per_gas, + max_fee_per_gas, + min_wait_time: None, + max_wait_time: None, // TODO: maybe fill with some default values (and mark as uncertain)? + }; + priority_fees.push(priority_fee); + } + drop_mutability!(priority_fees); + FeePerGasEstimated { + base_fee, + low: priority_fees[0].clone(), + medium: priority_fees[1].clone(), + high: priority_fees[2].clone(), + source: EstimationSource::Simple, + units: EstimationUnits::Gwei, + base_fee_trend: String::default(), + priority_fee_trend: String::default(), + } + } +} + +// Infura provider caller: + +#[derive(Clone, Debug, Deserialize)] +struct InfuraFeePerGasLevel { + #[serde(rename = "suggestedMaxPriorityFeePerGas")] + suggested_max_priority_fee_per_gas: BigDecimal, + #[serde(rename = "suggestedMaxFeePerGas")] + suggested_max_fee_per_gas: BigDecimal, + #[serde(rename = "minWaitTimeEstimate")] + min_wait_time_estimate: u32, + #[serde(rename = "maxWaitTimeEstimate")] + max_wait_time_estimate: u32, +} + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +struct InfuraFeePerGas { + low: InfuraFeePerGasLevel, + medium: InfuraFeePerGasLevel, + high: InfuraFeePerGasLevel, + #[serde(rename = "estimatedBaseFee")] + estimated_base_fee: BigDecimal, + #[serde(rename = "networkCongestion")] + network_congestion: BigDecimal, + #[serde(rename = "latestPriorityFeeRange")] + latest_priority_fee_range: Vec, + #[serde(rename = "historicalPriorityFeeRange")] + historical_priority_fee_range: Vec, + #[serde(rename = "historicalBaseFeeRange")] + historical_base_fee_range: Vec, + #[serde(rename = "priorityFeeTrend")] + priority_fee_trend: String, // we are not using enum here bcz values not mentioned in docs could be received + #[serde(rename = "baseFeeTrend")] + base_fee_trend: String, +} + +lazy_static! { + static ref INFURA_GAS_API_AUTH_TEST: String = std::env::var("INFURA_GAS_API_AUTH_TEST").unwrap_or_default(); +} + +#[allow(dead_code)] +pub(super) struct InfuraGasApiCaller {} + +#[allow(dead_code)] +impl InfuraGasApiCaller { + const INFURA_GAS_API_URL: &'static str = "https://gas.api.infura.io/networks/1"; + const INFURA_GAS_FEES_CALL: &'static str = "suggestedGasFees"; + + fn get_infura_gas_api_url() -> (String, Vec<(&'static str, &'static str)>) { + let url = format!("{}/{}", Self::INFURA_GAS_API_URL, Self::INFURA_GAS_FEES_CALL); + let headers = vec![("Authorization", INFURA_GAS_API_AUTH_TEST.as_str())]; + (url, headers) + } + + async fn make_infura_gas_api_request() -> Result> { + let (url, headers) = Self::get_infura_gas_api_url(); + let resp = slurp_url_with_headers(&url, headers).await.mm_err(|e| e.to_string())?; + if resp.0 != StatusCode::OK { + let error = format!("{} failed with status code {}", Self::INFURA_GAS_FEES_CALL, resp.0); + info!("gas api error: {}", error); + return MmError::err(error); + } + let estimated_fees = json::from_slice(&resp.2).map_err(|e| e.to_string())?; + Ok(estimated_fees) + } + + /// Fetch api provider gas fee estimations + pub async fn fetch_infura_fee_estimation() -> Web3RpcResult { + let infura_estimated_fees = Self::make_infura_gas_api_request() + .await + .mm_err(Web3RpcError::Transport)?; + Ok(infura_estimated_fees.into()) + } +} + +// Blocknative provider caller + +#[allow(dead_code)] +#[derive(Clone, Debug, Deserialize)] +struct BlocknativeBlockPrices { + #[serde(rename = "blockNumber")] + block_number: u32, + #[serde(rename = "estimatedTransactionCount")] + estimated_transaction_count: u32, + #[serde(rename = "baseFeePerGas")] + base_fee_per_gas: BigDecimal, + #[serde(rename = "estimatedPrices")] + estimated_prices: Vec, +} + +#[allow(dead_code)] +#[derive(Clone, Debug, Deserialize)] +struct BlocknativeEstimatedPrices { + confidence: u32, + price: u64, + #[serde(rename = "maxPriorityFeePerGas")] + max_priority_fee_per_gas: BigDecimal, + #[serde(rename = "maxFeePerGas")] + max_fee_per_gas: BigDecimal, +} + +#[allow(dead_code)] +#[derive(Clone, Debug, Deserialize)] +struct BlocknativeBaseFee { + confidence: u32, + #[serde(rename = "baseFee")] + base_fee: BigDecimal, +} + +struct BlocknativeEstimatedBaseFees {} + +impl BlocknativeEstimatedBaseFees { + /// Parse blocknative's base_fees in pending blocks : '[ "pending+1" : {}, "pending+2" : {}, ..]' removing 'pending+n' + fn parse_pending<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + struct PendingBlockFeeParser; + impl<'de> Visitor<'de> for PendingBlockFeeParser { + type Value = Vec>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("[u32, BigDecimal]") + } + + fn visit_seq>(self, mut seq: A) -> Result { + let mut pending_block_fees = Vec::>::new(); + while let Some(fees) = seq.next_element::>>()? { + if let Some(fees) = fees.iter().next() { + pending_block_fees.push(fees.1.clone()); + } + } + Ok(pending_block_fees) + } + } + deserializer.deserialize_any(PendingBlockFeeParser {}) + } +} + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +struct BlocknativeBlockPricesResponse { + system: String, + network: String, + unit: String, + #[serde(rename = "maxPrice")] + max_price: u32, + #[serde(rename = "currentBlockNumber")] + current_block_number: u32, + #[serde(rename = "msSinceLastBlock")] + ms_since_last_block: u32, + #[serde(rename = "blockPrices")] + block_prices: Vec, + #[serde( + rename = "estimatedBaseFees", + deserialize_with = "BlocknativeEstimatedBaseFees::parse_pending" + )] + estimated_base_fees: Vec>, +} + +lazy_static! { + static ref BLOCKNATIVE_GAS_API_AUTH_TEST: String = + std::env::var("BLOCKNATIVE_GAS_API_AUTH_TEST").unwrap_or_default(); +} + +#[allow(dead_code)] +pub(super) struct BlocknativeGasApiCaller {} + +#[allow(dead_code)] +impl BlocknativeGasApiCaller { + const BLOCKNATIVE_GAS_API_URL: &'static str = "https://api.blocknative.com/gasprices"; + const BLOCKNATIVE_GAS_PRICES_CALL: &'static str = "blockprices"; + const BLOCKNATIVE_GAS_PRICES_PARAMS: &'static str = + "confidenceLevels=10&confidenceLevels=50&confidenceLevels=90&withBaseFees=true"; + + fn get_blocknative_gas_api_url() -> (String, Vec<(&'static str, &'static str)>) { + let url = format!( + "{}/{}?{}", + Self::BLOCKNATIVE_GAS_API_URL, + Self::BLOCKNATIVE_GAS_PRICES_CALL, + Self::BLOCKNATIVE_GAS_PRICES_PARAMS + ); + let headers = vec![("Authorization", BLOCKNATIVE_GAS_API_AUTH_TEST.as_str())]; + (url, headers) + } + + async fn make_blocknative_gas_api_request() -> Result> { + let (url, headers) = Self::get_blocknative_gas_api_url(); + let resp = slurp_url_with_headers(&url, headers).await.mm_err(|e| e.to_string())?; + if resp.0 != StatusCode::OK { + let error = format!( + "{} failed with status code {}", + Self::BLOCKNATIVE_GAS_PRICES_CALL, + resp.0 + ); + info!("gas api error: {}", error); + return MmError::err(error); + } + let block_prices = json::from_slice(&resp.2).map_err(|e| e.to_string())?; + Ok(block_prices) + } + + /// Fetch api provider gas fee estimations + pub async fn fetch_blocknative_fee_estimation() -> Web3RpcResult { + let block_prices = Self::make_blocknative_gas_api_request() + .await + .mm_err(Web3RpcError::Transport)?; + Ok(block_prices.into()) + } +} diff --git a/mm2src/coins/eth/fee_estimator.rs b/mm2src/coins/eth/fee_estimator.rs deleted file mode 100644 index 11e5fc85ba..0000000000 --- a/mm2src/coins/eth/fee_estimator.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::convert::TryInto; - -use super::web3_transport::FeeHistoryResult; -use ethereum_types::U256; - -/// Simple priority fee estimator for 3 levels based on fee history -/// normally used if eth provider is not available - -// Gas fee estimator types - -/// Estimated priority gas fee -#[derive(Clone)] -pub struct PriorityFee { - /// estimated max priority tip fee per gas - pub max_priority_fee_per_gas: U256, - /// estimated max fee per gas - pub max_fee_per_gas: U256, - /// estimated transaction min wait time in mempool in ms for this priority level - pub min_wait_time: Option, - /// estimated transaction max wait time in mempool in ms for this priority level - pub max_wait_time: Option, -} - -/// Estimated gas price for several priority levels -/// we support low/medium/high levels as we can use api providers which normally support such levels -#[derive(Default, Clone)] -pub struct GasFeeEstimatedInternal { - /// base fee for the next block - pub base_fee: U256, - /// estimated priority fees - pub priority_fees: [PriorityFee; 3], -} - -impl Default for PriorityFee { - fn default() -> Self { - Self { - max_priority_fee_per_gas: U256::from(0), - max_fee_per_gas: U256::from(0), - min_wait_time: None, - max_wait_time: None, - } - } -} - -/// Eth gas priority fee simple estimator based on eth fee history -pub struct GasPriorityFeeEstimator {} - -impl GasPriorityFeeEstimator { - /// depth to look for fee history to estimate priority fees - const FEE_PRIORITY_DEPTH: u64 = 5u64; - - /// percentiles to pass to eth_feeHistory - const HISTORY_PERCENTILES: [f64; 3] = [25.0, 50.0, 75.0]; - - /// percentiles to calc max priority fee over historical rewards - const CALC_PERCENTILES: [f64; 3] = [50.0, 50.0, 50.0]; - - /// block depth for eth_feeHistory - pub fn history_depth() -> u64 { Self::FEE_PRIORITY_DEPTH } - - /// percentiles for priority rewards obtained with eth_feeHistory - pub fn history_percentiles() -> &'static [f64] { &Self::HISTORY_PERCENTILES } - - /// percentile for vector - fn percentile_of(v: &mut Vec, percent: f64) -> U256 { - v.sort(); - - // validate bounds: - let percent = if percent > 100.0 { 100.0 } else { percent }; - let percent = if percent < 0.0 { 0.0 } else { percent }; - - let value_pos = ((v.len() - 1) as f64 * percent / 100.0).round() as usize; - v[value_pos] - } - - /// estimate max priority fee by fee history - pub fn estimate_fees(fee_history: &FeeHistoryResult) -> GasFeeEstimatedInternal { - let base_fee = *fee_history.base_fee_per_gas.first().unwrap_or(&U256::from(0)); - let mut priority_fees = vec![]; - for i in 0..Self::HISTORY_PERCENTILES.len() { - let mut level_rewards = fee_history - .priority_rewards - .iter() - .map(|rewards| if i < rewards.len() { rewards[i] } else { U256::from(0) }) - .collect::>(); - let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::CALC_PERCENTILES[i]); - let priority_fee = PriorityFee { - max_priority_fee_per_gas, - max_fee_per_gas: base_fee + max_priority_fee_per_gas, - min_wait_time: None, - max_wait_time: None, // TODO: maybe fill with some default values (and mark as uncertain)? - }; - priority_fees.push(priority_fee); - } - GasFeeEstimatedInternal { - base_fee, - priority_fees: priority_fees.try_into().unwrap_or_default(), - } - } -} diff --git a/mm2src/coins/rpc_command/get_gas_priority_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs similarity index 50% rename from mm2src/coins/rpc_command/get_gas_priority_fees.rs rename to mm2src/coins/rpc_command/get_estimated_fees.rs index f1370b7ffa..e421751d79 100644 --- a/mm2src/coins/rpc_command/get_gas_priority_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,4 +1,4 @@ -use crate::eth::{u256_to_big_decimal, EthCoin, GasFeeEstimatedInternal, ETH_DECIMALS}; +use crate::eth::{EthCoin, FeePerGasEstimated}; use crate::AsyncMutex; use crate::{from_ctx, lp_coinfind, MarketCoinOps, MmCoinEnum, NumConversError}; use common::executor::{SpawnFuture, Timer}; @@ -9,30 +9,28 @@ use futures::{select, Future, StreamExt}; use futures_util::FutureExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::mm_error::MmError; -use mm2_number::BigDecimal; use std::collections::HashSet; use std::pin::Pin; use std::sync::Arc; -/// get_gas_priority_fees.rs -/// RPCs to start/stop priority fees estimator and get estimated fees +/// RPCs to start/stop gas price estimator and get estimated base and priority fee per gas -const PRIORITY_FEES_REFRESH_INTERVAL: u32 = 15; // in sec +const FEE_ESTIMATOR_REFRESH_INTERVAL: u32 = 15; // in sec +const FEE_ESTIMATOR_NAME: &str = "eth_fee_estimator_loop"; const MAX_CONCURRENT_STOP_REQUESTS: usize = 10; -const GAS_FEE_ESTIMATOR_NAME: &str = "gas_fee_estimator_loop"; -pub(crate) type GasFeeEstimatorStopListener = mpsc::Receiver; -pub(crate) type GasFeeEstimatorStopHandle = mpsc::Sender; +pub(crate) type FeeEstimatorStopListener = mpsc::Receiver; +pub(crate) type FeeEstimatorStopHandle = mpsc::Sender; /// Gas fee estimator running loop state -enum GasFeeEstimatorState { +enum FeeEstimatorState { Starting, Running, Stopping, Stopped, } -impl Default for GasFeeEstimatorState { +impl Default for FeeEstimatorState { fn default() -> Self { Self::Stopped } } @@ -40,7 +38,7 @@ impl Default for GasFeeEstimatorState { #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] -pub enum GasFeeEstimatorError { +pub enum FeeEstimatorError { #[display(fmt = "Coin not activated or not a EVM coin")] CoinNotFoundOrSupported, #[display(fmt = "Coin not connected to fee estimator")] @@ -59,51 +57,55 @@ pub enum GasFeeEstimatorError { InternalError(String), } -impl HttpStatusCode for GasFeeEstimatorError { +impl HttpStatusCode for FeeEstimatorError { fn status_code(&self) -> StatusCode { match self { - GasFeeEstimatorError::CoinNotFoundOrSupported - | GasFeeEstimatorError::CoinNotConnected - | GasFeeEstimatorError::AlreadyStarted - | GasFeeEstimatorError::AlreadyStopping - | GasFeeEstimatorError::NotRunning - | GasFeeEstimatorError::CannotStartFromStopping => StatusCode::BAD_REQUEST, - GasFeeEstimatorError::Transport(_) | GasFeeEstimatorError::InternalError(_) => { - StatusCode::INTERNAL_SERVER_ERROR - }, + FeeEstimatorError::CoinNotFoundOrSupported + | FeeEstimatorError::CoinNotConnected + | FeeEstimatorError::AlreadyStarted + | FeeEstimatorError::AlreadyStopping + | FeeEstimatorError::NotRunning + | FeeEstimatorError::CannotStartFromStopping => StatusCode::BAD_REQUEST, + FeeEstimatorError::Transport(_) | FeeEstimatorError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } -impl From for GasFeeEstimatorError { - fn from(e: NumConversError) -> Self { GasFeeEstimatorError::InternalError(e.to_string()) } +impl From for FeeEstimatorError { + fn from(e: NumConversError) -> Self { FeeEstimatorError::InternalError(e.to_string()) } } -impl From for GasFeeEstimatorError { - fn from(e: String) -> Self { GasFeeEstimatorError::InternalError(e) } +impl From for FeeEstimatorError { + fn from(e: String) -> Self { FeeEstimatorError::InternalError(e) } } -/// Gas fee estimator loop context -pub struct GasFeeEstimatorContext { - estimated_fees: AsyncMutex, - run_state: AsyncMutex, +/// Gas fee estimator loop context, +/// runs the fee per gas estimation loop according to EIP-1559 +/// +/// This FeeEstimatorContext handles rpc requests which start and stop gas fee estimation loop and handles the loop itself. +/// FeeEstimatorContext maintains a set of eth coins or tokens using the estimator. +/// The loop estimation starts when any eth coin or token calls the start rpc and stops when the last using coin or token calls the stop rpc. +/// FeeEstimatorContext keeps the latest estimated gas fees and returns them as response to a requesting rpc +pub struct FeeEstimatorContext { + estimated_fees: AsyncMutex, + run_state: AsyncMutex, /// coins that connected to loop and can get fee estimates using_coins: AsyncMutex>, /// receiver of signals to stop estimator (if it is not used) - stop_listener: Arc>, + stop_listener: Arc>, /// sender of signal to stop estimator - stop_handle: GasFeeEstimatorStopHandle, + stop_handle: FeeEstimatorStopHandle, /// mm2 shutdown listener shutdown_listener: AsyncMutex + Send + Sync>>>, } -impl GasFeeEstimatorContext { +impl FeeEstimatorContext { fn new(ctx: MmArc) -> Result { let shutdown_listener = try_s!(ctx.graceful_shutdown_registry.register_listener()); let (tx, rx) = mpsc::channel(MAX_CONCURRENT_STOP_REQUESTS); Ok(Self { estimated_fees: Default::default(), - run_state: AsyncMutex::new(GasFeeEstimatorState::default()), + run_state: AsyncMutex::new(FeeEstimatorState::default()), using_coins: AsyncMutex::new(HashSet::new()), stop_listener: Arc::new(AsyncMutex::new(rx)), stop_handle: tx, @@ -111,38 +113,37 @@ impl GasFeeEstimatorContext { }) } - fn from_ctx(ctx: MmArc) -> Result, String> { - Ok(try_s!(from_ctx(&ctx.clone().gas_fee_estimator_ctx, move || { - GasFeeEstimatorContext::new(ctx) + fn from_ctx(ctx: MmArc) -> Result, String> { + Ok(try_s!(from_ctx(&ctx.clone().fee_estimator_ctx, move || { + FeeEstimatorContext::new(ctx) }))) } - /// update period in secs - fn get_refresh_rate() -> f64 { PRIORITY_FEES_REFRESH_INTERVAL as f64 } + /// Fee estimation update period in secs, basically equals to eth blocktime + fn get_refresh_interval() -> f64 { FEE_ESTIMATOR_REFRESH_INTERVAL as f64 } - async fn start_if_not_running(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { + async fn start_if_not_running(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { let estimator_ctx = Self::from_ctx(ctx.clone())?; loop { let mut run_state = estimator_ctx.run_state.lock().await; match *run_state { - GasFeeEstimatorState::Stopped => { - *run_state = GasFeeEstimatorState::Starting; + FeeEstimatorState::Stopped => { + *run_state = FeeEstimatorState::Starting; drop(run_state); - ctx.spawner() - .spawn(Self::gas_fee_estimator_loop(ctx.clone(), coin.clone())); + ctx.spawner().spawn(Self::fee_estimator_loop(ctx.clone(), coin.clone())); return Ok(()); }, - GasFeeEstimatorState::Running => { + FeeEstimatorState::Running => { let mut using_coins = estimator_ctx.using_coins.lock().await; using_coins.insert(coin.ticker().to_string()); - debug!("{GAS_FEE_ESTIMATOR_NAME} coin {} connected", coin.ticker()); + debug!("{FEE_ESTIMATOR_NAME} coin {} connected", coin.ticker()); return Ok(()); }, - GasFeeEstimatorState::Stopping => { + FeeEstimatorState::Stopping => { drop(run_state); let _ = Self::wait_for_stopped(ctx.clone()).await; }, - GasFeeEstimatorState::Starting => { + FeeEstimatorState::Starting => { drop(run_state); let _ = Self::wait_for_running(ctx.clone()).await; }, @@ -150,29 +151,29 @@ impl GasFeeEstimatorContext { } } - async fn request_to_stop(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { + async fn request_to_stop(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { Self::check_if_coin_connected(ctx.clone(), coin).await?; let estimator_ctx = Self::from_ctx(ctx)?; let run_state = estimator_ctx.run_state.lock().await; - if let GasFeeEstimatorState::Running = *run_state { + if let FeeEstimatorState::Running = *run_state { let mut stop_handle = estimator_ctx.stop_handle.clone(); stop_handle .try_send(coin.ticker().to_owned()) - .map_err(|_| MmError::new(GasFeeEstimatorError::InternalError("could not stop".to_string())))?; - debug!("{GAS_FEE_ESTIMATOR_NAME} sent stop request for {}", coin.ticker()); + .map_err(|_| MmError::new(FeeEstimatorError::InternalError("could not stop".to_string())))?; + debug!("{FEE_ESTIMATOR_NAME} sent stop request for {}", coin.ticker()); } else { debug!( - "{GAS_FEE_ESTIMATOR_NAME} could not stop for {}: coin not connected", + "{FEE_ESTIMATOR_NAME} could not stop for {}: coin not connected", coin.ticker() ); - return MmError::err(GasFeeEstimatorError::NotRunning); + return MmError::err(FeeEstimatorError::NotRunning); } Ok(()) } /// run listen cycle: wait for loop stop or shutdown /// returns true if shutdown started to exist quickly - async fn listen_for_stop(&self) -> Result> { + async fn listen_for_stop(&self) -> Result> { let stop_listener = self.stop_listener.clone(); let mut stop_listener = stop_listener.lock().await; let mut listen_fut = stop_listener.next().fuse(); @@ -185,27 +186,28 @@ impl GasFeeEstimatorContext { }; if shutdown_detected { - debug!("{GAS_FEE_ESTIMATOR_NAME} received shutdown request"); + debug!("{FEE_ESTIMATOR_NAME} received shutdown request"); return Ok(true); } else if let Some(disconnected_coin) = disconnected_coin { let mut using_coins = self.using_coins.lock().await; if using_coins.remove(&disconnected_coin) { - debug!("{GAS_FEE_ESTIMATOR_NAME} coin {} disconnected", disconnected_coin); + debug!("{FEE_ESTIMATOR_NAME} coin {} disconnected", disconnected_coin); } // stop loop if all coins disconnected if using_coins.is_empty() { let mut run_state = self.run_state.lock().await; - *run_state = GasFeeEstimatorState::Stopping; + *run_state = FeeEstimatorState::Stopping; } } Ok(false) } - async fn wait_for_running(ctx: MmArc) -> Result<(), MmError> { + /// wait until the estimator loop state becomes Running + async fn wait_for_running(ctx: MmArc) -> Result<(), MmError> { let estimator_ctx = Self::from_ctx(ctx.clone())?; loop { let run_state = estimator_ctx.run_state.lock().await; - if let GasFeeEstimatorState::Running = *run_state { + if let FeeEstimatorState::Running = *run_state { break; } drop(run_state); @@ -214,11 +216,12 @@ impl GasFeeEstimatorContext { Ok(()) } - async fn wait_for_stopped(ctx: MmArc) -> Result<(), MmError> { + /// wait until the estimator loop state becomes Stopped + async fn wait_for_stopped(ctx: MmArc) -> Result<(), MmError> { let estimator_ctx = Self::from_ctx(ctx.clone())?; loop { let run_state = estimator_ctx.run_state.lock().await; - if let GasFeeEstimatorState::Stopped = *run_state { + if let FeeEstimatorState::Stopped = *run_state { break; } drop(run_state); @@ -227,32 +230,33 @@ impl GasFeeEstimatorContext { Ok(()) } - async fn gas_fee_estimator_loop(ctx: MmArc, coin: EthCoin) { + /// Gas fee estimator loop wrapper + async fn fee_estimator_loop(ctx: MmArc, coin: EthCoin) { let estimator_ctx = Self::from_ctx(ctx.clone()); if let Ok(estimator_ctx) = estimator_ctx { - let _ = estimator_ctx.gas_fee_estimator_loop_inner(coin).await; + let _ = estimator_ctx.fee_estimator_loop_inner(coin).await; } } /// Gas fee estimator loop - async fn gas_fee_estimator_loop_inner(&self, coin: EthCoin) -> Result<(), MmError> { + async fn fee_estimator_loop_inner(&self, coin: EthCoin) -> Result<(), MmError> { let mut run_state = self.run_state.lock().await; - if let GasFeeEstimatorState::Starting = *run_state { - *run_state = GasFeeEstimatorState::Running; + if let FeeEstimatorState::Starting = *run_state { + *run_state = FeeEstimatorState::Running; let mut using_coins = self.using_coins.lock().await; using_coins.insert(coin.ticker().to_string()); - debug!("{GAS_FEE_ESTIMATOR_NAME} started and coin {} connected", coin.ticker()); + debug!("{FEE_ESTIMATOR_NAME} started and coin {} connected", coin.ticker()); } else { - debug!("{GAS_FEE_ESTIMATOR_NAME} could not start from this state, probably already running"); - return MmError::err(GasFeeEstimatorError::InternalError("could not start".to_string())); + debug!("{FEE_ESTIMATOR_NAME} could not start from this state, probably already running"); + return MmError::err(FeeEstimatorError::InternalError("could not start".to_string())); } // release lock: drop(run_state); loop { let mut run_state = self.run_state.lock().await; - if let GasFeeEstimatorState::Stopping = *run_state { - *run_state = GasFeeEstimatorState::Stopped; + if let FeeEstimatorState::Stopping = *run_state { + *run_state = FeeEstimatorState::Stopped; break; } drop(run_state); @@ -262,7 +266,7 @@ impl GasFeeEstimatorContext { let stop_fut = self.listen_for_stop().fuse(); let (estimated_res, shutdown_started) = select! { estimated = Box::pin(estimate_fut) => (estimated, Ok(false)), - shutdown_started = Box::pin(stop_fut) => (Ok(GasFeeEstimatedInternal::default()), shutdown_started) + shutdown_started = Box::pin(stop_fut) => (Ok(FeePerGasEstimated::default()), shutdown_started) }; // use returned bool (instead of run_state) to check if shutdown started to exit quickly if shutdown_started.is_ok() && shutdown_started.unwrap() { @@ -270,15 +274,15 @@ impl GasFeeEstimatorContext { } let mut run_state = self.run_state.lock().await; - if let GasFeeEstimatorState::Stopping = *run_state { - *run_state = GasFeeEstimatorState::Stopped; + if let FeeEstimatorState::Stopping = *run_state { + *run_state = FeeEstimatorState::Stopped; break; } drop(run_state); let estimated = match estimated_res { Ok(estimated) => estimated, - Err(_) => GasFeeEstimatedInternal::default(), // TODO: if fee estimates could not be obtained should we clear values or use previous? + Err(_) => FeePerGasEstimated::default(), // TODO: if fee estimates could not be obtained should we clear values or use previous? }; let mut estimated_fees = self.estimated_fees.lock().await; *estimated_fees = estimated; @@ -286,11 +290,11 @@ impl GasFeeEstimatorContext { let elapsed = common::now_float() - started; debug!( - "{GAS_FEE_ESTIMATOR_NAME} getting estimated values processed in {} seconds", + "{FEE_ESTIMATOR_NAME} getting estimated values processed in {} seconds", elapsed ); - let wait_secs = GasFeeEstimatorContext::get_refresh_rate() - elapsed; + let wait_secs = FeeEstimatorContext::get_refresh_interval() - elapsed; let wait_secs = if wait_secs < 0.0 { 0.0 } else { wait_secs }; let sleep_fut = Timer::sleep(wait_secs).fuse(); let stop_fut = self.listen_for_stop().fuse(); @@ -302,25 +306,22 @@ impl GasFeeEstimatorContext { break; } } - debug!("{GAS_FEE_ESTIMATOR_NAME} stopped"); + debug!("{FEE_ESTIMATOR_NAME} stopped"); Ok(()) } - async fn get_estimated_fees( - ctx: MmArc, - coin: &EthCoin, - ) -> Result> { + async fn get_estimated_fees(ctx: MmArc, coin: &EthCoin) -> Result> { Self::check_if_coin_connected(ctx.clone(), coin).await?; let estimator_ctx = Self::from_ctx(ctx.clone())?; let estimated_fees = estimator_ctx.estimated_fees.lock().await; Ok(estimated_fees.clone()) } - async fn check_if_coin_connected(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { + async fn check_if_coin_connected(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { let estimator_ctx = Self::from_ctx(ctx.clone())?; let using_coins = estimator_ctx.using_coins.lock().await; if using_coins.get(&coin.ticker().to_string()).is_none() { - return MmError::err(GasFeeEstimatorError::CoinNotConnected); + return MmError::err(FeeEstimatorError::CoinNotConnected); } Ok(()) } @@ -329,146 +330,84 @@ impl GasFeeEstimatorContext { // Rpc request/response/result #[derive(Deserialize)] -pub struct GasFeeEstimatorStartStopRequest { +pub struct FeeEstimatorStartStopRequest { coin: String, } #[derive(Serialize)] -pub struct GasFeeEstimatorStartStopResponse { +pub struct FeeEstimatorStartStopResponse { result: String, } -impl GasFeeEstimatorStartStopResponse { +impl FeeEstimatorStartStopResponse { #[allow(dead_code)] pub fn get_result(&self) -> String { self.result.clone() } } -pub type GasFeeEstimatorStartStopResult = Result>; +pub type FeeEstimatorStartStopResult = Result>; #[derive(Deserialize)] -pub struct GasFeeEstimatedRequest { +pub struct FeeEstimatorRequest { coin: String, } -/// Estimated priority gas fee -#[derive(Serialize)] -pub struct GasPriorityFee { - /// estimated max priority tip fee per gas - pub max_priority_fee_per_gas: BigDecimal, - /// estimated max fee per gas - pub max_fee_per_gas: BigDecimal, - /// estimated transaction min wait time in mempool in ms for this priority level - pub min_wait_time: Option, - /// estimated transaction max wait time in mempool in ms for this priority level - pub max_wait_time: Option, -} - -#[derive(Serialize)] -pub struct GasFeeEstimatedResponse { - pub base_fee: BigDecimal, - pub low_fee: GasPriorityFee, - pub medium_fee: GasPriorityFee, - pub high_fee: GasPriorityFee, -} - -pub type GasFeeEstimatedResult = Result>; +pub type FeeEstimatorResult = Result>; /// Start gas priority fee estimator loop -pub async fn start_gas_fee_estimator( - ctx: MmArc, - req: GasFeeEstimatorStartStopRequest, -) -> GasFeeEstimatorStartStopResult { +/// +/// Param: coin ticker +pub async fn start_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopRequest) -> FeeEstimatorStartStopResult { let coin = match lp_coinfind(&ctx, &req.coin).await { Ok(Some(coin)) => coin, - Ok(None) | Err(_) => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), }; let coin = match coin { MmCoinEnum::EthCoin(eth) => eth, - _ => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + _ => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), }; - GasFeeEstimatorContext::start_if_not_running(ctx, &coin).await?; - Ok(GasFeeEstimatorStartStopResponse { + FeeEstimatorContext::start_if_not_running(ctx, &coin).await?; + Ok(FeeEstimatorStartStopResponse { result: "Success".to_string(), }) } /// Stop gas priority fee estimator loop -pub async fn stop_gas_fee_estimator( - ctx: MmArc, - req: GasFeeEstimatorStartStopRequest, -) -> GasFeeEstimatorStartStopResult { +/// +/// Param: coin ticker +pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopRequest) -> FeeEstimatorStartStopResult { let coin = match lp_coinfind(&ctx, &req.coin).await { Ok(Some(coin)) => coin, - Ok(None) | Err(_) => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), }; let coin = match coin { MmCoinEnum::EthCoin(eth) => eth, - _ => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + _ => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), }; - GasFeeEstimatorContext::request_to_stop(ctx, &coin).await?; - Ok(GasFeeEstimatorStartStopResponse { + FeeEstimatorContext::request_to_stop(ctx, &coin).await?; + Ok(FeeEstimatorStartStopResponse { result: "Success".to_string(), }) } -/// Stop gas priority fee estimator loop -pub async fn get_gas_priority_fees(ctx: MmArc, req: GasFeeEstimatedRequest) -> GasFeeEstimatedResult { +/// Get latest estimated fee per gas +/// +/// Param: coin ticker +/// Returns estimated base and priority fees +pub async fn get_eth_gas_price_estimated(ctx: MmArc, req: FeeEstimatorRequest) -> FeeEstimatorResult { let coin = match lp_coinfind(&ctx, &req.coin).await { Ok(Some(coin)) => coin, - Ok(None) | Err(_) => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), }; // just check this is a eth-like coin. // we will return data if estimator is running, for any eth-like coin let coin = match coin { MmCoinEnum::EthCoin(eth) => eth, - _ => return MmError::err(GasFeeEstimatorError::CoinNotFoundOrSupported), + _ => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), }; - let estimated_fees = GasFeeEstimatorContext::get_estimated_fees(ctx, &coin).await?; - let eth_decimals = ETH_DECIMALS; - Ok(GasFeeEstimatedResponse { - base_fee: u256_to_big_decimal(estimated_fees.base_fee, eth_decimals)?, - - low_fee: GasPriorityFee { - max_priority_fee_per_gas: u256_to_big_decimal( - estimated_fees.priority_fees[0].max_priority_fee_per_gas, - eth_decimals, - )?, - max_fee_per_gas: u256_to_big_decimal( - estimated_fees.priority_fees[0].max_priority_fee_per_gas, - eth_decimals, - )?, - min_wait_time: estimated_fees.priority_fees[0].min_wait_time, - max_wait_time: estimated_fees.priority_fees[0].min_wait_time, - }, - - medium_fee: GasPriorityFee { - max_priority_fee_per_gas: u256_to_big_decimal( - estimated_fees.priority_fees[1].max_priority_fee_per_gas, - eth_decimals, - )?, - max_fee_per_gas: u256_to_big_decimal( - estimated_fees.priority_fees[1].max_priority_fee_per_gas, - eth_decimals, - )?, - min_wait_time: estimated_fees.priority_fees[1].min_wait_time, - max_wait_time: estimated_fees.priority_fees[1].min_wait_time, - }, - - high_fee: GasPriorityFee { - max_priority_fee_per_gas: u256_to_big_decimal( - estimated_fees.priority_fees[2].max_priority_fee_per_gas, - eth_decimals, - )?, - max_fee_per_gas: u256_to_big_decimal( - estimated_fees.priority_fees[2].max_priority_fee_per_gas, - eth_decimals, - )?, - min_wait_time: estimated_fees.priority_fees[2].min_wait_time, - max_wait_time: estimated_fees.priority_fees[2].min_wait_time, - }, - }) + let estimated_fees = FeeEstimatorContext::get_estimated_fees(ctx, &coin).await?; + Ok(estimated_fees) } diff --git a/mm2src/coins/rpc_command/mod.rs b/mm2src/coins/rpc_command/mod.rs index 9a4cef0c2c..0bec5ef493 100644 --- a/mm2src/coins/rpc_command/mod.rs +++ b/mm2src/coins/rpc_command/mod.rs @@ -1,7 +1,7 @@ pub mod account_balance; pub mod get_current_mtp; pub mod get_enabled_coins; -pub mod get_gas_priority_fees; +pub mod get_estimated_fees; pub mod get_new_address; pub mod hd_account_balance_rpc_error; pub mod init_account_balance; diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index 1c63301a89..76a50f3f0d 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -135,8 +135,8 @@ pub struct MmCtx { /// asynchronous handle for rusqlite connection. #[cfg(not(target_arch = "wasm32"))] pub async_sqlite_connection: Constructible>>, - /// Context for eth gas priority fee estimator loop - pub gas_fee_estimator_ctx: Mutex>>, + /// Context for eth fee per gas estimator loop + pub fee_estimator_ctx: Mutex>>, } impl MmCtx { @@ -183,7 +183,7 @@ impl MmCtx { nft_ctx: Mutex::new(None), #[cfg(not(target_arch = "wasm32"))] async_sqlite_connection: Constructible::default(), - gas_fee_estimator_ctx: Mutex::new(None), + fee_estimator_ctx: Mutex::new(None), } } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index b3f87026e2..5a18522b02 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -16,8 +16,8 @@ use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels, ibc_with use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, get_enabled_coins::get_enabled_coins, - get_gas_priority_fees::{get_gas_priority_fees, start_gas_fee_estimator, - stop_gas_fee_estimator}, + get_estimated_fees::{get_eth_gas_price_estimated, start_eth_fee_estimator, + stop_eth_fee_estimator}, get_new_address::{cancel_get_new_address, get_new_address, init_get_new_address, init_get_new_address_status, init_get_new_address_user_action}, init_account_balance::{cancel_account_balance, init_account_balance, @@ -204,9 +204,9 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, ibc_chains).await, "ibc_transfer_channels" => handle_mmrpc(ctx, request, ibc_transfer_channels).await, "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, - "start_gas_fee_estimator" => handle_mmrpc(ctx, request, start_gas_fee_estimator).await, - "stop_gas_fee_estimator" => handle_mmrpc(ctx, request, stop_gas_fee_estimator).await, - "get_gas_priority_fees" => handle_mmrpc(ctx, request, get_gas_priority_fees).await, + "start_eth_fee_estimator" => handle_mmrpc(ctx, request, start_eth_fee_estimator).await, + "stop_eth_fee_estimator" => handle_mmrpc(ctx, request, stop_eth_fee_estimator).await, + "get_eth_gas_price_estimated" => handle_mmrpc(ctx, request, get_eth_gas_price_estimated).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] From ee85d20d4d74fb6f10c600e1d4a4f74d1209b3f3 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 24 Feb 2024 18:29:42 +0500 Subject: [PATCH 06/71] refactor on review notes: added gas api config in mm2.conf use platform coin for web3 conn check chain id --- mm2src/coins/coin_errors.rs | 6 +- mm2src/coins/eth.rs | 69 ++- mm2src/coins/eth/eip1559_gas_fee.rs | 502 ++++++++++-------- .../coins/rpc_command/get_estimated_fees.rs | 138 ++--- 4 files changed, 407 insertions(+), 308 deletions(-) diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index c9672082c7..d596e42be3 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -74,9 +74,9 @@ impl From for ValidatePaymentError { match e { Web3RpcError::Transport(tr) => ValidatePaymentError::Transport(tr), Web3RpcError::InvalidResponse(resp) => ValidatePaymentError::InvalidRpcResponse(resp), - Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { - ValidatePaymentError::InternalError(internal) - }, + Web3RpcError::Internal(internal) + | Web3RpcError::Timeout(internal) + | Web3RpcError::InvalidGasApiConfig(internal) => ValidatePaymentError::InternalError(internal), } } } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 70708b3018..af30cab9b8 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -37,7 +37,7 @@ use common::{now_ms, wait_until_ms}; use crypto::privkey::key_pair_from_secret; use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy, StandardHDCoinAddress}; use derive_more::Display; -pub use eip1559_gas_fee::FeePerGasEstimated; +pub(crate) use eip1559_gas_fee::FeePerGasEstimated; use enum_from::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; @@ -116,7 +116,8 @@ use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; mod eip1559_gas_fee; -use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator}; +use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, + InfuraGasApiCaller}; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 @@ -220,6 +221,8 @@ pub enum Web3RpcError { Timeout(String), #[display(fmt = "Internal: {}", _0)] Internal(String), + #[display(fmt = "Invalid gas api provider config: {}", _0)] + InvalidGasApiConfig(String), } impl From for Web3RpcError { @@ -259,9 +262,9 @@ impl From for RawTransactionError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => RawTransactionError::Transport(tr), - Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { - RawTransactionError::InternalError(internal) - }, + Web3RpcError::Internal(internal) + | Web3RpcError::Timeout(internal) + | Web3RpcError::InvalidGasApiConfig(internal) => RawTransactionError::InternalError(internal), } } } @@ -300,9 +303,9 @@ impl From for WithdrawError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(err) | Web3RpcError::InvalidResponse(err) => WithdrawError::Transport(err), - Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { - WithdrawError::InternalError(internal) - }, + Web3RpcError::Internal(internal) + | Web3RpcError::Timeout(internal) + | Web3RpcError::InvalidGasApiConfig(internal) => WithdrawError::InternalError(internal), } } } @@ -315,9 +318,9 @@ impl From for TradePreimageError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(err) | Web3RpcError::InvalidResponse(err) => TradePreimageError::Transport(err), - Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { - TradePreimageError::InternalError(internal) - }, + Web3RpcError::Internal(internal) + | Web3RpcError::Timeout(internal) + | Web3RpcError::InvalidGasApiConfig(internal) => TradePreimageError::InternalError(internal), } } } @@ -346,7 +349,9 @@ impl From for BalanceError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => BalanceError::Transport(tr), - Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => BalanceError::Internal(internal), + Web3RpcError::Internal(internal) + | Web3RpcError::Timeout(internal) + | Web3RpcError::InvalidGasApiConfig(internal) => BalanceError::Internal(internal), } } } @@ -440,7 +445,7 @@ pub struct EthCoinImpl { /// Coin needs access to the context in order to reuse the logging and shutdown facilities. /// Using a weak reference by default in order to avoid circular references and leaks. pub ctx: MmWeak, - chain_id: Option, + pub(crate) chain_id: Option, /// the block range used for eth_getLogs logs_block_range: u64, nonce_lock: Arc>, @@ -4649,15 +4654,31 @@ impl EthCoin { let coin = self.clone(); let fee_history_namespace: EthFeeHistoryNamespace<_> = coin.web3.api(); let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(fee_history_namespace); - let provider_estimator_fut = BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(); - // To call infura use: - // let provider_estimator_fut = InfuraGasApiCaller::fetch_infura_fee_estimation(); - - let (res_history, res_provider) = join(history_estimator_fut, provider_estimator_fut).await; - match (res_history, res_provider) { - (Ok(ref history_est), Err(_)) => Ok(history_est.clone()), - (_, Ok(ref provider_est)) => Ok(provider_est.clone()), - (_, _) => MmError::err(Web3RpcError::Internal("All gas api requests failed".into())), // TODO: send errors + let ctx = MmArc::from_weak(&self.ctx) + .ok_or("!ctx") + .map_to_mm(|err| Web3RpcError::Internal(err.to_string()))?; + let gas_api_conf = ctx.conf["gas_api"].clone(); + if !gas_api_conf.is_null() { + let gas_api_conf: GasApiConfig = + json::from_value(gas_api_conf).map_to_mm(|e| Web3RpcError::InvalidGasApiConfig(e.to_string()))?; + let provider_estimator_fut = match gas_api_conf.provider { + GasApiProvider::Infura => InfuraGasApiCaller::fetch_infura_fee_estimation(&gas_api_conf.url).boxed(), + GasApiProvider::Blocknative => { + BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(&gas_api_conf.url).boxed() + }, + }; + + let (res_history, res_provider) = join(history_estimator_fut, provider_estimator_fut).await; + match (res_history, res_provider) { + (Ok(ref history_est), Err(_)) => Ok(history_est.clone()), + (_, Ok(ref provider_est)) => Ok(provider_est.clone()), + (_, _) => MmError::err(Web3RpcError::Internal("All gas api requests failed".into())), // TODO: send errors + } + } else { + // use only internal fee per gas estimator + history_estimator_fut + .await + .mm_err(|e| Web3RpcError::Internal(e.to_string())) } } @@ -5840,7 +5861,9 @@ impl From for EthGasDetailsErr { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => EthGasDetailsErr::Transport(tr), - Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => EthGasDetailsErr::Internal(internal), + Web3RpcError::Internal(internal) + | Web3RpcError::Timeout(internal) + | Web3RpcError::InvalidGasApiConfig(internal) => EthGasDetailsErr::Internal(internal), } } } diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index ad5aa19495..23851c4854 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -1,28 +1,28 @@ +//! Provides estimations of base and priority fee per gas or fetch estimations from a gas api provider + use super::web3_transport::{EthFeeHistoryNamespace, FeeHistoryResult}; use super::{u256_to_big_decimal, Web3RpcError, Web3RpcResult, ETH_GWEI_DECIMALS}; -use common::log::info; use ethereum_types::U256; -use http::StatusCode; use mm2_err_handle::mm_error::MmError; -use mm2_err_handle::prelude::*; -use mm2_net::transport::slurp_url_with_headers; use mm2_number::BigDecimal; use num_traits::FromPrimitive; -use serde::de::{Deserializer, SeqAccess, Visitor}; -use serde_json::{self as json}; -use std::collections::HashMap; -use std::fmt; +use url::Url; use web3::{types::BlockNumber, Transport}; -// Estimate base and priority fee per gas -// or fetch estimations from a gas api provider +pub(crate) use gas_api::BlocknativeGasApiCaller; +#[allow(unused_imports)] +pub(crate) use gas_api::InfuraGasApiCaller; + +use gas_api::{BlocknativeBlockPricesResponse, InfuraFeePerGas}; const FEE_PER_GAS_LEVELS: usize = 3; +/// Indicates which provider was used to get fee per gas estimations #[derive(Clone, Debug, Serialize)] pub enum EstimationSource { /// filled by default values Empty, + /// internal simple estimator Simple, Infura, Blocknative, @@ -32,6 +32,7 @@ impl Default for EstimationSource { fn default() -> Self { Self::Empty } } +/// Estimated fee per gas units #[derive(Clone, Debug, Serialize)] pub enum EstimationUnits { Gwei, @@ -41,7 +42,28 @@ impl Default for EstimationUnits { fn default() -> Self { Self::Gwei } } -/// One priority level estimated max fees +enum PriorityLevelId { + Low = 0, + Medium = 1, + High = 2, +} + +/// Supported gas api providers +#[derive(Deserialize)] +pub enum GasApiProvider { + Infura, + Blocknative, +} + +#[derive(Deserialize)] +pub struct GasApiConfig { + /// gas api provider name to use + pub provider: GasApiProvider, + /// gas api provider or proxy base url (scheme, host and port without the relative part) + pub url: Url, +} + +/// Priority level estimated max fee per gas #[derive(Clone, Debug, Serialize)] pub struct FeePerGasLevel { /// estimated max priority tip fee per gas in gwei @@ -122,7 +144,7 @@ impl From for FeePerGasEstimated { if block_prices.block_prices.is_empty() { return FeePerGasEstimated::default(); } - if block_prices.block_prices[0].estimated_prices.len() < 3 { + if block_prices.block_prices[0].estimated_prices.len() < FEE_PER_GAS_LEVELS { return FeePerGasEstimated::default(); } Self { @@ -161,7 +183,7 @@ impl From for FeePerGasEstimated { /// Simple priority fee per gas estimator based on fee history /// normally used if gas api provider is not available -pub(super) struct FeePerGasSimpleEstimator {} +pub(crate) struct FeePerGasSimpleEstimator {} impl FeePerGasSimpleEstimator { // TODO: add minimal max fee and priority fee @@ -216,39 +238,48 @@ impl FeePerGasSimpleEstimator { } } + fn priority_fee_for_level( + level: PriorityLevelId, + base_fee: &BigDecimal, + fee_history: &FeeHistoryResult, + ) -> FeePerGasLevel { + let level_i = level as usize; + let mut level_rewards = fee_history + .priority_rewards + .iter() + .map(|rewards| { + if level_i < rewards.len() { + rewards[level_i] + } else { + U256::from(0) + } + }) + .collect::>(); + + let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::CALC_PERCENTILES[level_i]); + let max_priority_fee_per_gas = + u256_to_big_decimal(max_priority_fee_per_gas, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); + let max_fee_per_gas = base_fee + * BigDecimal::from_f64(Self::ADJUST_MAX_FEE[level_i]).unwrap_or_else(|| BigDecimal::from(0)) + + max_priority_fee_per_gas.clone() + * BigDecimal::from_f64(Self::ADJUST_MAX_PRIORITY_FEE[level_i]).unwrap_or_else(|| BigDecimal::from(0)); // TODO maybe use checked ops + FeePerGasLevel { + max_priority_fee_per_gas, + max_fee_per_gas, + min_wait_time: None, + max_wait_time: None, // TODO: maybe fill with some default values (and mark as uncertain)? + } + } + /// estimate priority fees by fee history fn calculate_with_history(fee_history: &FeeHistoryResult) -> FeePerGasEstimated { let base_fee = *fee_history.base_fee_per_gas.first().unwrap_or(&U256::from(0)); let base_fee = u256_to_big_decimal(base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); - let mut priority_fees = vec![]; - for i in 0..Self::HISTORY_PERCENTILES.len() { - let mut level_rewards = fee_history - .priority_rewards - .iter() - .map(|rewards| if i < rewards.len() { rewards[i] } else { U256::from(0) }) - .collect::>(); - - let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::CALC_PERCENTILES[i]); - let max_priority_fee_per_gas = u256_to_big_decimal(max_priority_fee_per_gas, ETH_GWEI_DECIMALS) - .unwrap_or_else(|_| BigDecimal::from(0)); - let max_fee_per_gas = base_fee.clone() - * BigDecimal::from_f64(Self::ADJUST_MAX_FEE[i]).unwrap_or_else(|| BigDecimal::from(0)) - + max_priority_fee_per_gas.clone() - * BigDecimal::from_f64(Self::ADJUST_MAX_PRIORITY_FEE[i]).unwrap_or_else(|| BigDecimal::from(0)); // TODO maybe use checked ops - let priority_fee = FeePerGasLevel { - max_priority_fee_per_gas, - max_fee_per_gas, - min_wait_time: None, - max_wait_time: None, // TODO: maybe fill with some default values (and mark as uncertain)? - }; - priority_fees.push(priority_fee); - } - drop_mutability!(priority_fees); FeePerGasEstimated { - base_fee, - low: priority_fees[0].clone(), - medium: priority_fees[1].clone(), - high: priority_fees[2].clone(), + base_fee: base_fee.clone(), + low: Self::priority_fee_for_level(PriorityLevelId::Low, &base_fee, fee_history), + medium: Self::priority_fee_for_level(PriorityLevelId::Medium, &base_fee, fee_history), + high: Self::priority_fee_for_level(PriorityLevelId::High, &base_fee, fee_history), source: EstimationSource::Simple, units: EstimationUnits::Gwei, base_fee_trend: String::default(), @@ -257,213 +288,240 @@ impl FeePerGasSimpleEstimator { } } -// Infura provider caller: - -#[derive(Clone, Debug, Deserialize)] -struct InfuraFeePerGasLevel { - #[serde(rename = "suggestedMaxPriorityFeePerGas")] - suggested_max_priority_fee_per_gas: BigDecimal, - #[serde(rename = "suggestedMaxFeePerGas")] - suggested_max_fee_per_gas: BigDecimal, - #[serde(rename = "minWaitTimeEstimate")] - min_wait_time_estimate: u32, - #[serde(rename = "maxWaitTimeEstimate")] - max_wait_time_estimate: u32, -} +mod gas_api { + use super::FeePerGasEstimated; + use crate::eth::{Web3RpcError, Web3RpcResult}; + use common::log::debug; + use http::StatusCode; + use mm2_err_handle::mm_error::MmError; + use mm2_err_handle::prelude::*; + use mm2_net::transport::slurp_url_with_headers; + use mm2_number::BigDecimal; + use serde::de::{Deserializer, SeqAccess, Visitor}; + use serde_json::{self as json}; + use std::collections::HashMap; + use std::fmt; + use url::Url; + + lazy_static! { + /// API key for testing + static ref INFURA_GAS_API_AUTH_TEST: String = std::env::var("INFURA_GAS_API_AUTH_TEST").unwrap_or_default(); + } -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct InfuraFeePerGas { - low: InfuraFeePerGasLevel, - medium: InfuraFeePerGasLevel, - high: InfuraFeePerGasLevel, - #[serde(rename = "estimatedBaseFee")] - estimated_base_fee: BigDecimal, - #[serde(rename = "networkCongestion")] - network_congestion: BigDecimal, - #[serde(rename = "latestPriorityFeeRange")] - latest_priority_fee_range: Vec, - #[serde(rename = "historicalPriorityFeeRange")] - historical_priority_fee_range: Vec, - #[serde(rename = "historicalBaseFeeRange")] - historical_base_fee_range: Vec, - #[serde(rename = "priorityFeeTrend")] - priority_fee_trend: String, // we are not using enum here bcz values not mentioned in docs could be received - #[serde(rename = "baseFeeTrend")] - base_fee_trend: String, -} + #[derive(Clone, Debug, Deserialize)] + pub(crate) struct InfuraFeePerGasLevel { + #[serde(rename = "suggestedMaxPriorityFeePerGas")] + pub suggested_max_priority_fee_per_gas: BigDecimal, + #[serde(rename = "suggestedMaxFeePerGas")] + pub suggested_max_fee_per_gas: BigDecimal, + #[serde(rename = "minWaitTimeEstimate")] + pub min_wait_time_estimate: u32, + #[serde(rename = "maxWaitTimeEstimate")] + pub max_wait_time_estimate: u32, + } -lazy_static! { - static ref INFURA_GAS_API_AUTH_TEST: String = std::env::var("INFURA_GAS_API_AUTH_TEST").unwrap_or_default(); -} + /// Infura gas api response + /// see https://docs.infura.io/api/infura-expansion-apis/gas-api/api-reference/gasprices-type2 + #[allow(dead_code)] + #[derive(Debug, Deserialize)] + pub(crate) struct InfuraFeePerGas { + pub low: InfuraFeePerGasLevel, + pub medium: InfuraFeePerGasLevel, + pub high: InfuraFeePerGasLevel, + #[serde(rename = "estimatedBaseFee")] + pub estimated_base_fee: BigDecimal, + #[serde(rename = "networkCongestion")] + pub network_congestion: BigDecimal, + #[serde(rename = "latestPriorityFeeRange")] + pub latest_priority_fee_range: Vec, + #[serde(rename = "historicalPriorityFeeRange")] + pub historical_priority_fee_range: Vec, + #[serde(rename = "historicalBaseFeeRange")] + pub historical_base_fee_range: Vec, + #[serde(rename = "priorityFeeTrend")] + pub priority_fee_trend: String, // we are not using enum here bcz values not mentioned in docs could be received + #[serde(rename = "baseFeeTrend")] + pub base_fee_trend: String, + } -#[allow(dead_code)] -pub(super) struct InfuraGasApiCaller {} + /// Infura gas api provider caller + #[allow(dead_code)] + pub(crate) struct InfuraGasApiCaller {} -#[allow(dead_code)] -impl InfuraGasApiCaller { - const INFURA_GAS_API_URL: &'static str = "https://gas.api.infura.io/networks/1"; - const INFURA_GAS_FEES_CALL: &'static str = "suggestedGasFees"; + #[allow(dead_code)] + impl InfuraGasApiCaller { + const INFURA_GAS_FEES_ENDPOINT: &'static str = "networks/1/suggestedGasFees"; // Support only main chain - fn get_infura_gas_api_url() -> (String, Vec<(&'static str, &'static str)>) { - let url = format!("{}/{}", Self::INFURA_GAS_API_URL, Self::INFURA_GAS_FEES_CALL); - let headers = vec![("Authorization", INFURA_GAS_API_AUTH_TEST.as_str())]; - (url, headers) - } + fn get_infura_gas_api_url(base_url: &Url) -> (Url, Vec<(&'static str, &'static str)>) { + let mut url = base_url.clone(); + url.set_path(Self::INFURA_GAS_FEES_ENDPOINT); + let headers = vec![("Authorization", INFURA_GAS_API_AUTH_TEST.as_str())]; + (url, headers) + } + + async fn make_infura_gas_api_request( + url: &Url, + headers: Vec<(&'static str, &'static str)>, + ) -> Result> { + let resp = slurp_url_with_headers(url.as_str(), headers) + .await + .mm_err(|e| e.to_string())?; + if resp.0 != StatusCode::OK { + let error = format!("{} failed with status code {}", url, resp.0); + debug!("infura gas api error: {}", error); + return MmError::err(error); + } + let estimated_fees = json::from_slice(&resp.2).map_to_mm(|e| e.to_string())?; + Ok(estimated_fees) + } - async fn make_infura_gas_api_request() -> Result> { - let (url, headers) = Self::get_infura_gas_api_url(); - let resp = slurp_url_with_headers(&url, headers).await.mm_err(|e| e.to_string())?; - if resp.0 != StatusCode::OK { - let error = format!("{} failed with status code {}", Self::INFURA_GAS_FEES_CALL, resp.0); - info!("gas api error: {}", error); - return MmError::err(error); + /// Fetch fee per gas estimations from infura provider + pub async fn fetch_infura_fee_estimation(base_url: &Url) -> Web3RpcResult { + let (url, headers) = Self::get_infura_gas_api_url(base_url); + let infura_estimated_fees = Self::make_infura_gas_api_request(&url, headers) + .await + .mm_err(Web3RpcError::Transport)?; + Ok(infura_estimated_fees.into()) } - let estimated_fees = json::from_slice(&resp.2).map_err(|e| e.to_string())?; - Ok(estimated_fees) } - /// Fetch api provider gas fee estimations - pub async fn fetch_infura_fee_estimation() -> Web3RpcResult { - let infura_estimated_fees = Self::make_infura_gas_api_request() - .await - .mm_err(Web3RpcError::Transport)?; - Ok(infura_estimated_fees.into()) + lazy_static! { + /// API key for testing + static ref BLOCKNATIVE_GAS_API_AUTH_TEST: String = std::env::var("BLOCKNATIVE_GAS_API_AUTH_TEST").unwrap_or_default(); } -} -// Blocknative provider caller - -#[allow(dead_code)] -#[derive(Clone, Debug, Deserialize)] -struct BlocknativeBlockPrices { - #[serde(rename = "blockNumber")] - block_number: u32, - #[serde(rename = "estimatedTransactionCount")] - estimated_transaction_count: u32, - #[serde(rename = "baseFeePerGas")] - base_fee_per_gas: BigDecimal, - #[serde(rename = "estimatedPrices")] - estimated_prices: Vec, -} + #[allow(dead_code)] + #[derive(Clone, Debug, Deserialize)] + pub(crate) struct BlocknativeBlockPrices { + #[serde(rename = "blockNumber")] + pub block_number: u32, + #[serde(rename = "estimatedTransactionCount")] + pub estimated_transaction_count: u32, + #[serde(rename = "baseFeePerGas")] + pub base_fee_per_gas: BigDecimal, + #[serde(rename = "estimatedPrices")] + pub estimated_prices: Vec, + } -#[allow(dead_code)] -#[derive(Clone, Debug, Deserialize)] -struct BlocknativeEstimatedPrices { - confidence: u32, - price: u64, - #[serde(rename = "maxPriorityFeePerGas")] - max_priority_fee_per_gas: BigDecimal, - #[serde(rename = "maxFeePerGas")] - max_fee_per_gas: BigDecimal, -} + #[allow(dead_code)] + #[derive(Clone, Debug, Deserialize)] + pub(crate) struct BlocknativeEstimatedPrices { + pub confidence: u32, + pub price: u64, + #[serde(rename = "maxPriorityFeePerGas")] + pub max_priority_fee_per_gas: BigDecimal, + #[serde(rename = "maxFeePerGas")] + pub max_fee_per_gas: BigDecimal, + } -#[allow(dead_code)] -#[derive(Clone, Debug, Deserialize)] -struct BlocknativeBaseFee { - confidence: u32, - #[serde(rename = "baseFee")] - base_fee: BigDecimal, -} + #[allow(dead_code)] + #[derive(Clone, Debug, Deserialize)] + pub(crate) struct BlocknativeBaseFee { + pub confidence: u32, + #[serde(rename = "baseFee")] + pub base_fee: BigDecimal, + } -struct BlocknativeEstimatedBaseFees {} + struct BlocknativeEstimatedBaseFees {} -impl BlocknativeEstimatedBaseFees { - /// Parse blocknative's base_fees in pending blocks : '[ "pending+1" : {}, "pending+2" : {}, ..]' removing 'pending+n' - fn parse_pending<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - struct PendingBlockFeeParser; - impl<'de> Visitor<'de> for PendingBlockFeeParser { - type Value = Vec>; + impl BlocknativeEstimatedBaseFees { + /// Parse blocknative's base_fees in pending blocks : '[ "pending+1" : {}, "pending+2" : {}, ..]' removing 'pending+n' + fn parse_pending<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + struct PendingBlockFeeParser; + impl<'de> Visitor<'de> for PendingBlockFeeParser { + type Value = Vec>; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("[u32, BigDecimal]") - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("[u32, BigDecimal]") + } - fn visit_seq>(self, mut seq: A) -> Result { - let mut pending_block_fees = Vec::>::new(); - while let Some(fees) = seq.next_element::>>()? { - if let Some(fees) = fees.iter().next() { - pending_block_fees.push(fees.1.clone()); + fn visit_seq>(self, mut seq: A) -> Result { + let mut pending_block_fees = Vec::>::new(); + while let Some(fees) = seq.next_element::>>()? { + if let Some(fees) = fees.iter().next() { + pending_block_fees.push(fees.1.clone()); + } } + Ok(pending_block_fees) } - Ok(pending_block_fees) } + deserializer.deserialize_any(PendingBlockFeeParser {}) } - deserializer.deserialize_any(PendingBlockFeeParser {}) } -} - -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct BlocknativeBlockPricesResponse { - system: String, - network: String, - unit: String, - #[serde(rename = "maxPrice")] - max_price: u32, - #[serde(rename = "currentBlockNumber")] - current_block_number: u32, - #[serde(rename = "msSinceLastBlock")] - ms_since_last_block: u32, - #[serde(rename = "blockPrices")] - block_prices: Vec, - #[serde( - rename = "estimatedBaseFees", - deserialize_with = "BlocknativeEstimatedBaseFees::parse_pending" - )] - estimated_base_fees: Vec>, -} - -lazy_static! { - static ref BLOCKNATIVE_GAS_API_AUTH_TEST: String = - std::env::var("BLOCKNATIVE_GAS_API_AUTH_TEST").unwrap_or_default(); -} -#[allow(dead_code)] -pub(super) struct BlocknativeGasApiCaller {} - -#[allow(dead_code)] -impl BlocknativeGasApiCaller { - const BLOCKNATIVE_GAS_API_URL: &'static str = "https://api.blocknative.com/gasprices"; - const BLOCKNATIVE_GAS_PRICES_CALL: &'static str = "blockprices"; - const BLOCKNATIVE_GAS_PRICES_PARAMS: &'static str = - "confidenceLevels=10&confidenceLevels=50&confidenceLevels=90&withBaseFees=true"; - - fn get_blocknative_gas_api_url() -> (String, Vec<(&'static str, &'static str)>) { - let url = format!( - "{}/{}?{}", - Self::BLOCKNATIVE_GAS_API_URL, - Self::BLOCKNATIVE_GAS_PRICES_CALL, - Self::BLOCKNATIVE_GAS_PRICES_PARAMS - ); - let headers = vec![("Authorization", BLOCKNATIVE_GAS_API_AUTH_TEST.as_str())]; - (url, headers) + /// Blocknative gas prices response + /// see https://docs.blocknative.com/gas-prediction/gas-platform + #[allow(dead_code)] + #[derive(Debug, Deserialize)] + pub(crate) struct BlocknativeBlockPricesResponse { + pub system: String, + pub network: String, + pub unit: String, + #[serde(rename = "maxPrice")] + pub max_price: u32, + #[serde(rename = "currentBlockNumber")] + pub current_block_number: u32, + #[serde(rename = "msSinceLastBlock")] + pub ms_since_last_block: u32, + #[serde(rename = "blockPrices")] + pub block_prices: Vec, + #[serde( + rename = "estimatedBaseFees", + deserialize_with = "BlocknativeEstimatedBaseFees::parse_pending" + )] + pub estimated_base_fees: Vec>, } - async fn make_blocknative_gas_api_request() -> Result> { - let (url, headers) = Self::get_blocknative_gas_api_url(); - let resp = slurp_url_with_headers(&url, headers).await.mm_err(|e| e.to_string())?; - if resp.0 != StatusCode::OK { - let error = format!( - "{} failed with status code {}", - Self::BLOCKNATIVE_GAS_PRICES_CALL, - resp.0 - ); - info!("gas api error: {}", error); - return MmError::err(error); + /// Blocknative gas api provider caller + #[allow(dead_code)] + pub(crate) struct BlocknativeGasApiCaller {} + + #[allow(dead_code)] + impl BlocknativeGasApiCaller { + const BLOCKNATIVE_GAS_PRICES_ENDPOINT: &'static str = "gasprices/blockprices"; + const BLOCKNATIVE_GAS_PRICES_LOW: &'static str = "10"; + const BLOCKNATIVE_GAS_PRICES_MEDIUM: &'static str = "50"; + const BLOCKNATIVE_GAS_PRICES_HIGH: &'static str = "90"; + + fn get_blocknative_gas_api_url(base_url: &Url) -> (Url, Vec<(&'static str, &'static str)>) { + let mut url = base_url.clone(); + url.set_path(Self::BLOCKNATIVE_GAS_PRICES_ENDPOINT); + url.query_pairs_mut() + .append_pair("confidenceLevels", Self::BLOCKNATIVE_GAS_PRICES_LOW) + .append_pair("confidenceLevels", Self::BLOCKNATIVE_GAS_PRICES_MEDIUM) + .append_pair("confidenceLevels", Self::BLOCKNATIVE_GAS_PRICES_HIGH) + .append_pair("withBaseFees", "true"); + + let headers = vec![("Authorization", BLOCKNATIVE_GAS_API_AUTH_TEST.as_str())]; + (url, headers) } - let block_prices = json::from_slice(&resp.2).map_err(|e| e.to_string())?; - Ok(block_prices) - } - /// Fetch api provider gas fee estimations - pub async fn fetch_blocknative_fee_estimation() -> Web3RpcResult { - let block_prices = Self::make_blocknative_gas_api_request() - .await - .mm_err(Web3RpcError::Transport)?; - Ok(block_prices.into()) + async fn make_blocknative_gas_api_request( + url: &Url, + headers: Vec<(&'static str, &'static str)>, + ) -> Result> { + let resp = slurp_url_with_headers(url.as_str(), headers) + .await + .mm_err(|e| e.to_string())?; + if resp.0 != StatusCode::OK { + let error = format!("{} failed with status code {}", url, resp.0); + debug!("blocknative gas api error: {}", error); + return MmError::err(error); + } + let block_prices = json::from_slice(&resp.2).map_err(|e| e.to_string())?; + Ok(block_prices) + } + + /// Fetch fee per gas estimations from blocknative provider + pub async fn fetch_blocknative_fee_estimation(base_url: &Url) -> Web3RpcResult { + let (url, headers) = Self::get_blocknative_gas_api_url(base_url); + let block_prices = Self::make_blocknative_gas_api_request(&url, headers) + .await + .mm_err(Web3RpcError::Transport)?; + Ok(block_prices.into()) + } } } diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index e421751d79..7fd1f786ee 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,3 +1,5 @@ +//! RPCs to start/stop gas price estimator and get estimated base and priority fee per gas + use crate::eth::{EthCoin, FeePerGasEstimated}; use crate::AsyncMutex; use crate::{from_ctx, lp_coinfind, MarketCoinOps, MmCoinEnum, NumConversError}; @@ -13,11 +15,10 @@ use std::collections::HashSet; use std::pin::Pin; use std::sync::Arc; -/// RPCs to start/stop gas price estimator and get estimated base and priority fee per gas - -const FEE_ESTIMATOR_REFRESH_INTERVAL: u32 = 15; // in sec const FEE_ESTIMATOR_NAME: &str = "eth_fee_estimator_loop"; const MAX_CONCURRENT_STOP_REQUESTS: usize = 10; +const ETH_PLATFORM_COIN: &str = "ETH"; +const ETH_SUPPORTED_CHAIN_ID: u64 = 1; pub(crate) type FeeEstimatorStopListener = mpsc::Receiver; pub(crate) type FeeEstimatorStopHandle = mpsc::Sender; @@ -34,15 +35,19 @@ impl Default for FeeEstimatorState { fn default() -> Self { Self::Stopped } } -// Errors - #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum FeeEstimatorError { - #[display(fmt = "Coin not activated or not a EVM coin")] - CoinNotFoundOrSupported, + #[display(fmt = "Coin not activated")] + CoinNotActivated, + #[display(fmt = "Gas estimation not supported for this coin")] + CoinNotSupported, #[display(fmt = "Coin not connected to fee estimator")] CoinNotConnected, + #[display(fmt = "Platform coin ETH must be activated")] + PlatformCoinNotActivated, + #[display(fmt = "Chain id not supported")] + ChainNotSupported, #[display(fmt = "Fee estimator is already started")] AlreadyStarted, #[display(fmt = "Transport error: {}", _0)] @@ -60,8 +65,11 @@ pub enum FeeEstimatorError { impl HttpStatusCode for FeeEstimatorError { fn status_code(&self) -> StatusCode { match self { - FeeEstimatorError::CoinNotFoundOrSupported + FeeEstimatorError::CoinNotActivated + | FeeEstimatorError::CoinNotSupported | FeeEstimatorError::CoinNotConnected + | FeeEstimatorError::PlatformCoinNotActivated + | FeeEstimatorError::ChainNotSupported | FeeEstimatorError::AlreadyStarted | FeeEstimatorError::AlreadyStopping | FeeEstimatorError::NotRunning @@ -80,12 +88,12 @@ impl From for FeeEstimatorError { } /// Gas fee estimator loop context, -/// runs the fee per gas estimation loop according to EIP-1559 +/// runs a loop to estimate max fee and max priority fee per gas according to EIP-1559 for the next block /// /// This FeeEstimatorContext handles rpc requests which start and stop gas fee estimation loop and handles the loop itself. -/// FeeEstimatorContext maintains a set of eth coins or tokens using the estimator. -/// The loop estimation starts when any eth coin or token calls the start rpc and stops when the last using coin or token calls the stop rpc. -/// FeeEstimatorContext keeps the latest estimated gas fees and returns them as response to a requesting rpc +/// FeeEstimatorContext maintains a list of eth coins or tokens which connected and use the estimator. +/// The loop estimation starts when first eth coin or token calls the start rpc and stops when the last coin or token, using it, calls the stop rpc. +/// FeeEstimatorContext keeps the latest estimated gas fees in the context and returns them as rpc response pub struct FeeEstimatorContext { estimated_fees: AsyncMutex, run_state: AsyncMutex, @@ -120,7 +128,7 @@ impl FeeEstimatorContext { } /// Fee estimation update period in secs, basically equals to eth blocktime - fn get_refresh_interval() -> f64 { FEE_ESTIMATOR_REFRESH_INTERVAL as f64 } + const fn get_refresh_interval() -> f64 { 15.0 } async fn start_if_not_running(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { let estimator_ctx = Self::from_ctx(ctx.clone())?; @@ -128,9 +136,15 @@ impl FeeEstimatorContext { let mut run_state = estimator_ctx.run_state.lock().await; match *run_state { FeeEstimatorState::Stopped => { + let platform_coin = Self::get_eth_platform_coin(&ctx).await?; *run_state = FeeEstimatorState::Starting; drop(run_state); - ctx.spawner().spawn(Self::fee_estimator_loop(ctx.clone(), coin.clone())); + // we use platform coin to access its web3 connection, so it must be available + ctx.spawner() + .spawn(Self::fee_estimator_loop(ctx.clone(), platform_coin)); + let mut using_coins = estimator_ctx.using_coins.lock().await; + using_coins.insert(coin.ticker().to_string()); + debug!("{FEE_ESTIMATOR_NAME} coin {} connected", coin.ticker()); return Ok(()); }, FeeEstimatorState::Running => { @@ -238,14 +252,17 @@ impl FeeEstimatorContext { } } - /// Gas fee estimator loop + /// Loop polling gas fee estimator + /// + /// This loop periodically calls get_eip1559_gas_price which fetches fee per gas estimations from a gas api provider or calculates them internally + /// The retrieved data are stored in the fee estimator context + /// To connect to the gas api provider the web3 instances from the platform coin are used so ETH coin must be enabled + /// TODO: assumed that once the plaform coin is enabled it is always available and never can be disabled. Should we track it disabled? async fn fee_estimator_loop_inner(&self, coin: EthCoin) -> Result<(), MmError> { let mut run_state = self.run_state.lock().await; if let FeeEstimatorState::Starting = *run_state { *run_state = FeeEstimatorState::Running; - let mut using_coins = self.using_coins.lock().await; - using_coins.insert(coin.ticker().to_string()); - debug!("{FEE_ESTIMATOR_NAME} started and coin {} connected", coin.ticker()); + debug!("{FEE_ESTIMATOR_NAME} started"); } else { debug!("{FEE_ESTIMATOR_NAME} could not start from this state, probably already running"); return MmError::err(FeeEstimatorError::InternalError("could not start".to_string())); @@ -268,7 +285,7 @@ impl FeeEstimatorContext { estimated = Box::pin(estimate_fut) => (estimated, Ok(false)), shutdown_started = Box::pin(stop_fut) => (Ok(FeePerGasEstimated::default()), shutdown_started) }; - // use returned bool (instead of run_state) to check if shutdown started to exit quickly + // use returned bool (instead of run_state) to check if shutdown has just started and exit quickly if shutdown_started.is_ok() && shutdown_started.unwrap() { break; } @@ -282,7 +299,7 @@ impl FeeEstimatorContext { let estimated = match estimated_res { Ok(estimated) => estimated, - Err(_) => FeePerGasEstimated::default(), // TODO: if fee estimates could not be obtained should we clear values or use previous? + Err(_) => FeePerGasEstimated::default(), // TODO: if fee estimates could not be obtained I guess we should set a error? }; let mut estimated_fees = self.estimated_fees.lock().await; *estimated_fees = estimated; @@ -325,15 +342,43 @@ impl FeeEstimatorContext { } Ok(()) } -} -// Rpc request/response/result + fn check_if_chain_id_supported(coin: &EthCoin) -> Result<(), MmError> { + if let Some(chain_id) = coin.chain_id { + if chain_id != ETH_SUPPORTED_CHAIN_ID { + return MmError::err(FeeEstimatorError::ChainNotSupported); + } + } + Ok(()) + } + async fn check_if_coin_supported(ctx: &MmArc, ticker: &str) -> Result> { + let coin = match lp_coinfind(ctx, ticker).await { + Ok(Some(MmCoinEnum::EthCoin(eth))) => eth, + Ok(Some(_)) => return MmError::err(FeeEstimatorError::CoinNotSupported), + Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotActivated), + }; + Self::check_if_chain_id_supported(&coin)?; + Ok(coin) + } + + async fn get_eth_platform_coin(ctx: &MmArc) -> Result> { + let coin = match lp_coinfind(ctx, ETH_PLATFORM_COIN).await { + Ok(Some(MmCoinEnum::EthCoin(eth))) => eth, + _ => return MmError::err(FeeEstimatorError::PlatformCoinNotActivated), + }; + Self::check_if_chain_id_supported(&coin)?; + Ok(coin) + } +} + +/// Rpc request to start or stop gas fee estimator #[derive(Deserialize)] pub struct FeeEstimatorStartStopRequest { coin: String, } +/// Rpc response to request to start or stop gas fee estimator #[derive(Serialize)] pub struct FeeEstimatorStartStopResponse { result: String, @@ -341,31 +386,23 @@ pub struct FeeEstimatorStartStopResponse { impl FeeEstimatorStartStopResponse { #[allow(dead_code)] - pub fn get_result(&self) -> String { self.result.clone() } + pub fn get_result(&self) -> &str { &self.result } } pub type FeeEstimatorStartStopResult = Result>; +/// Rpc request to get latest estimated fee per gas #[derive(Deserialize)] pub struct FeeEstimatorRequest { + /// coin ticker coin: String, } pub type FeeEstimatorResult = Result>; /// Start gas priority fee estimator loop -/// -/// Param: coin ticker pub async fn start_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopRequest) -> FeeEstimatorStartStopResult { - let coin = match lp_coinfind(&ctx, &req.coin).await { - Ok(Some(coin)) => coin, - Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), - }; - let coin = match coin { - MmCoinEnum::EthCoin(eth) => eth, - _ => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), - }; - + let coin = FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; FeeEstimatorContext::start_if_not_running(ctx, &coin).await?; Ok(FeeEstimatorStartStopResponse { result: "Success".to_string(), @@ -373,41 +410,22 @@ pub async fn start_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopReque } /// Stop gas priority fee estimator loop -/// -/// Param: coin ticker pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopRequest) -> FeeEstimatorStartStopResult { - let coin = match lp_coinfind(&ctx, &req.coin).await { - Ok(Some(coin)) => coin, - Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), - }; - let coin = match coin { - MmCoinEnum::EthCoin(eth) => eth, - _ => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), - }; - + let coin = FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; FeeEstimatorContext::request_to_stop(ctx, &coin).await?; Ok(FeeEstimatorStartStopResponse { result: "Success".to_string(), }) } -/// Get latest estimated fee per gas +/// Get latest estimated fee per gas for a eth coin +/// +/// Estimation loop for this coin must be stated. +/// Only main chain is supported /// -/// Param: coin ticker -/// Returns estimated base and priority fees +/// Returns latest estimated fee per gas for the next block pub async fn get_eth_gas_price_estimated(ctx: MmArc, req: FeeEstimatorRequest) -> FeeEstimatorResult { - let coin = match lp_coinfind(&ctx, &req.coin).await { - Ok(Some(coin)) => coin, - Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), - }; - - // just check this is a eth-like coin. - // we will return data if estimator is running, for any eth-like coin - let coin = match coin { - MmCoinEnum::EthCoin(eth) => eth, - _ => return MmError::err(FeeEstimatorError::CoinNotFoundOrSupported), - }; - + let coin = FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; let estimated_fees = FeeEstimatorContext::get_estimated_fees(ctx, &coin).await?; Ok(estimated_fees) } From 42316c14bb3fbe42653744dbcc04951df9c27ba9 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 25 Feb 2024 12:02:52 +0500 Subject: [PATCH 07/71] fix fmt --- mm2src/coins/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 5402fbabe4..ddbb582e53 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -119,9 +119,9 @@ use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; mod eip1559_gas_fee; +pub(crate) use eip1559_gas_fee::FeePerGasEstimated; use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, InfuraGasApiCaller}; -pub(crate) use eip1559_gas_fee::FeePerGasEstimated; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 From aa8660c10f264bf398d1207dd0503f218a6e87e1 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 27 Feb 2024 18:43:25 +0700 Subject: [PATCH 08/71] Implement proposal. --- .../coins/rpc_command/get_estimated_fees.rs | 255 ++---------------- 1 file changed, 27 insertions(+), 228 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 7fd1f786ee..aed3d35bf8 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -2,39 +2,17 @@ use crate::eth::{EthCoin, FeePerGasEstimated}; use crate::AsyncMutex; -use crate::{from_ctx, lp_coinfind, MarketCoinOps, MmCoinEnum, NumConversError}; -use common::executor::{SpawnFuture, Timer}; +use crate::{from_ctx, lp_coinfind, MmCoinEnum, NumConversError}; +use common::executor::{spawn_abortable, AbortOnDropHandle, Timer}; use common::log::debug; use common::{HttpStatusCode, StatusCode}; -use futures::channel::mpsc; -use futures::{select, Future, StreamExt}; -use futures_util::FutureExt; use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::mm_error::MmError; -use std::collections::HashSet; -use std::pin::Pin; +use mm2_err_handle::prelude::*; use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_fee_estimator_loop"; -const MAX_CONCURRENT_STOP_REQUESTS: usize = 10; -const ETH_PLATFORM_COIN: &str = "ETH"; const ETH_SUPPORTED_CHAIN_ID: u64 = 1; -pub(crate) type FeeEstimatorStopListener = mpsc::Receiver; -pub(crate) type FeeEstimatorStopHandle = mpsc::Sender; - -/// Gas fee estimator running loop state -enum FeeEstimatorState { - Starting, - Running, - Stopping, - Stopped, -} - -impl Default for FeeEstimatorState { - fn default() -> Self { Self::Stopped } -} - #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum FeeEstimatorError { @@ -96,34 +74,20 @@ impl From for FeeEstimatorError { /// FeeEstimatorContext keeps the latest estimated gas fees in the context and returns them as rpc response pub struct FeeEstimatorContext { estimated_fees: AsyncMutex, - run_state: AsyncMutex, - /// coins that connected to loop and can get fee estimates - using_coins: AsyncMutex>, - /// receiver of signals to stop estimator (if it is not used) - stop_listener: Arc>, - /// sender of signal to stop estimator - stop_handle: FeeEstimatorStopHandle, - /// mm2 shutdown listener - shutdown_listener: AsyncMutex + Send + Sync>>>, + abort_handler: AsyncMutex>, } impl FeeEstimatorContext { - fn new(ctx: MmArc) -> Result { - let shutdown_listener = try_s!(ctx.graceful_shutdown_registry.register_listener()); - let (tx, rx) = mpsc::channel(MAX_CONCURRENT_STOP_REQUESTS); + fn new() -> Result { Ok(Self { estimated_fees: Default::default(), - run_state: AsyncMutex::new(FeeEstimatorState::default()), - using_coins: AsyncMutex::new(HashSet::new()), - stop_listener: Arc::new(AsyncMutex::new(rx)), - stop_handle: tx, - shutdown_listener: AsyncMutex::new(Box::pin(shutdown_listener)), + abort_handler: AsyncMutex::new(None), }) } fn from_ctx(ctx: MmArc) -> Result, String> { - Ok(try_s!(from_ctx(&ctx.clone().fee_estimator_ctx, move || { - FeeEstimatorContext::new(ctx) + Ok(try_s!(from_ctx(&ctx.fee_estimator_ctx, move || { + FeeEstimatorContext::new() }))) } @@ -132,116 +96,22 @@ impl FeeEstimatorContext { async fn start_if_not_running(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { let estimator_ctx = Self::from_ctx(ctx.clone())?; - loop { - let mut run_state = estimator_ctx.run_state.lock().await; - match *run_state { - FeeEstimatorState::Stopped => { - let platform_coin = Self::get_eth_platform_coin(&ctx).await?; - *run_state = FeeEstimatorState::Starting; - drop(run_state); - // we use platform coin to access its web3 connection, so it must be available - ctx.spawner() - .spawn(Self::fee_estimator_loop(ctx.clone(), platform_coin)); - let mut using_coins = estimator_ctx.using_coins.lock().await; - using_coins.insert(coin.ticker().to_string()); - debug!("{FEE_ESTIMATOR_NAME} coin {} connected", coin.ticker()); - return Ok(()); - }, - FeeEstimatorState::Running => { - let mut using_coins = estimator_ctx.using_coins.lock().await; - using_coins.insert(coin.ticker().to_string()); - debug!("{FEE_ESTIMATOR_NAME} coin {} connected", coin.ticker()); - return Ok(()); - }, - FeeEstimatorState::Stopping => { - drop(run_state); - let _ = Self::wait_for_stopped(ctx.clone()).await; - }, - FeeEstimatorState::Starting => { - drop(run_state); - let _ = Self::wait_for_running(ctx.clone()).await; - }, - } - } - } - - async fn request_to_stop(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { - Self::check_if_coin_connected(ctx.clone(), coin).await?; - let estimator_ctx = Self::from_ctx(ctx)?; - let run_state = estimator_ctx.run_state.lock().await; - if let FeeEstimatorState::Running = *run_state { - let mut stop_handle = estimator_ctx.stop_handle.clone(); - stop_handle - .try_send(coin.ticker().to_owned()) - .map_err(|_| MmError::new(FeeEstimatorError::InternalError("could not stop".to_string())))?; - debug!("{FEE_ESTIMATOR_NAME} sent stop request for {}", coin.ticker()); - } else { - debug!( - "{FEE_ESTIMATOR_NAME} could not stop for {}: coin not connected", - coin.ticker() - ); - return MmError::err(FeeEstimatorError::NotRunning); + let mut handler = estimator_ctx.abort_handler.lock().await; + if handler.is_some() { + return MmError::err(FeeEstimatorError::AlreadyStarted); } + *handler = Some(spawn_abortable(Self::fee_estimator_loop(ctx, coin.clone()))); Ok(()) } - /// run listen cycle: wait for loop stop or shutdown - /// returns true if shutdown started to exist quickly - async fn listen_for_stop(&self) -> Result> { - let stop_listener = self.stop_listener.clone(); - let mut stop_listener = stop_listener.lock().await; - let mut listen_fut = stop_listener.next().fuse(); - let mut shutdown_listener = self.shutdown_listener.lock().await; - let shutdown_fut = async { shutdown_listener.as_mut().await }.fuse(); - - let (disconnected_coin, shutdown_detected) = select! { - disconnected_coin = listen_fut => (disconnected_coin, false), - _ = Box::pin(shutdown_fut) => (None, true) - }; - - if shutdown_detected { - debug!("{FEE_ESTIMATOR_NAME} received shutdown request"); - return Ok(true); - } else if let Some(disconnected_coin) = disconnected_coin { - let mut using_coins = self.using_coins.lock().await; - if using_coins.remove(&disconnected_coin) { - debug!("{FEE_ESTIMATOR_NAME} coin {} disconnected", disconnected_coin); - } - // stop loop if all coins disconnected - if using_coins.is_empty() { - let mut run_state = self.run_state.lock().await; - *run_state = FeeEstimatorState::Stopping; - } - } - Ok(false) - } - - /// wait until the estimator loop state becomes Running - async fn wait_for_running(ctx: MmArc) -> Result<(), MmError> { - let estimator_ctx = Self::from_ctx(ctx.clone())?; - loop { - let run_state = estimator_ctx.run_state.lock().await; - if let FeeEstimatorState::Running = *run_state { - break; - } - drop(run_state); - Timer::sleep(0.1).await; - } - Ok(()) - } - - /// wait until the estimator loop state becomes Stopped - async fn wait_for_stopped(ctx: MmArc) -> Result<(), MmError> { - let estimator_ctx = Self::from_ctx(ctx.clone())?; - loop { - let run_state = estimator_ctx.run_state.lock().await; - if let FeeEstimatorState::Stopped = *run_state { - break; - } - drop(run_state); - Timer::sleep(0.1).await; - } - Ok(()) + async fn request_to_stop(ctx: MmArc) -> Result<(), MmError> { + let estimator_ctx = Self::from_ctx(ctx)?; + let mut handle_guard = estimator_ctx.abort_handler.lock().await; + // Handler will be dropped here, stopping the spawned loop immediately + handle_guard + .take() + .map(|_| ()) + .or_mm_err(|| FeeEstimatorError::AlreadyStopping) } /// Gas fee estimator loop wrapper @@ -259,51 +129,9 @@ impl FeeEstimatorContext { /// To connect to the gas api provider the web3 instances from the platform coin are used so ETH coin must be enabled /// TODO: assumed that once the plaform coin is enabled it is always available and never can be disabled. Should we track it disabled? async fn fee_estimator_loop_inner(&self, coin: EthCoin) -> Result<(), MmError> { - let mut run_state = self.run_state.lock().await; - if let FeeEstimatorState::Starting = *run_state { - *run_state = FeeEstimatorState::Running; - debug!("{FEE_ESTIMATOR_NAME} started"); - } else { - debug!("{FEE_ESTIMATOR_NAME} could not start from this state, probably already running"); - return MmError::err(FeeEstimatorError::InternalError("could not start".to_string())); - } - // release lock: - drop(run_state); - loop { - let mut run_state = self.run_state.lock().await; - if let FeeEstimatorState::Stopping = *run_state { - *run_state = FeeEstimatorState::Stopped; - break; - } - drop(run_state); - let started = common::now_float(); - let estimate_fut = coin.get_eip1559_gas_price().fuse(); - let stop_fut = self.listen_for_stop().fuse(); - let (estimated_res, shutdown_started) = select! { - estimated = Box::pin(estimate_fut) => (estimated, Ok(false)), - shutdown_started = Box::pin(stop_fut) => (Ok(FeePerGasEstimated::default()), shutdown_started) - }; - // use returned bool (instead of run_state) to check if shutdown has just started and exit quickly - if shutdown_started.is_ok() && shutdown_started.unwrap() { - break; - } - - let mut run_state = self.run_state.lock().await; - if let FeeEstimatorState::Stopping = *run_state { - *run_state = FeeEstimatorState::Stopped; - break; - } - drop(run_state); - - let estimated = match estimated_res { - Ok(estimated) => estimated, - Err(_) => FeePerGasEstimated::default(), // TODO: if fee estimates could not be obtained I guess we should set a error? - }; - let mut estimated_fees = self.estimated_fees.lock().await; - *estimated_fees = estimated; - drop(estimated_fees); + *self.estimated_fees.lock().await = coin.get_eip1559_gas_price().await.unwrap_or_default(); let elapsed = common::now_float() - started; debug!( @@ -313,36 +141,16 @@ impl FeeEstimatorContext { let wait_secs = FeeEstimatorContext::get_refresh_interval() - elapsed; let wait_secs = if wait_secs < 0.0 { 0.0 } else { wait_secs }; - let sleep_fut = Timer::sleep(wait_secs).fuse(); - let stop_fut = self.listen_for_stop().fuse(); - let shutdown_started = select! { - _ = Box::pin(sleep_fut) => Ok(false), - shutdown_started = Box::pin(stop_fut) => shutdown_started - }; - if shutdown_started.is_ok() && shutdown_started.unwrap() { - break; - } + Timer::sleep(wait_secs).await; } - debug!("{FEE_ESTIMATOR_NAME} stopped"); - Ok(()) } - async fn get_estimated_fees(ctx: MmArc, coin: &EthCoin) -> Result> { - Self::check_if_coin_connected(ctx.clone(), coin).await?; + async fn get_estimated_fees(ctx: MmArc) -> Result> { let estimator_ctx = Self::from_ctx(ctx.clone())?; let estimated_fees = estimator_ctx.estimated_fees.lock().await; Ok(estimated_fees.clone()) } - async fn check_if_coin_connected(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { - let estimator_ctx = Self::from_ctx(ctx.clone())?; - let using_coins = estimator_ctx.using_coins.lock().await; - if using_coins.get(&coin.ticker().to_string()).is_none() { - return MmError::err(FeeEstimatorError::CoinNotConnected); - } - Ok(()) - } - fn check_if_chain_id_supported(coin: &EthCoin) -> Result<(), MmError> { if let Some(chain_id) = coin.chain_id { if chain_id != ETH_SUPPORTED_CHAIN_ID { @@ -361,15 +169,6 @@ impl FeeEstimatorContext { Self::check_if_chain_id_supported(&coin)?; Ok(coin) } - - async fn get_eth_platform_coin(ctx: &MmArc) -> Result> { - let coin = match lp_coinfind(ctx, ETH_PLATFORM_COIN).await { - Ok(Some(MmCoinEnum::EthCoin(eth))) => eth, - _ => return MmError::err(FeeEstimatorError::PlatformCoinNotActivated), - }; - Self::check_if_chain_id_supported(&coin)?; - Ok(coin) - } } /// Rpc request to start or stop gas fee estimator @@ -411,8 +210,8 @@ pub async fn start_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopReque /// Stop gas priority fee estimator loop pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopRequest) -> FeeEstimatorStartStopResult { - let coin = FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; - FeeEstimatorContext::request_to_stop(ctx, &coin).await?; + FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; + FeeEstimatorContext::request_to_stop(ctx).await?; Ok(FeeEstimatorStartStopResponse { result: "Success".to_string(), }) @@ -425,7 +224,7 @@ pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopReques /// /// Returns latest estimated fee per gas for the next block pub async fn get_eth_gas_price_estimated(ctx: MmArc, req: FeeEstimatorRequest) -> FeeEstimatorResult { - let coin = FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; - let estimated_fees = FeeEstimatorContext::get_estimated_fees(ctx, &coin).await?; + FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; + let estimated_fees = FeeEstimatorContext::get_estimated_fees(ctx).await?; Ok(estimated_fees) } From 3e69a31ee2f90992d40e987c7e525e5842b74c92 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 27 Feb 2024 18:58:18 +0700 Subject: [PATCH 09/71] Refactor a bit. --- .../coins/rpc_command/get_estimated_fees.rs | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index aed3d35bf8..7f7b051992 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -73,7 +73,7 @@ impl From for FeeEstimatorError { /// The loop estimation starts when first eth coin or token calls the start rpc and stops when the last coin or token, using it, calls the stop rpc. /// FeeEstimatorContext keeps the latest estimated gas fees in the context and returns them as rpc response pub struct FeeEstimatorContext { - estimated_fees: AsyncMutex, + estimated_fees: Arc>, abort_handler: AsyncMutex>, } @@ -100,7 +100,10 @@ impl FeeEstimatorContext { if handler.is_some() { return MmError::err(FeeEstimatorError::AlreadyStarted); } - *handler = Some(spawn_abortable(Self::fee_estimator_loop(ctx, coin.clone()))); + *handler = Some(spawn_abortable(fee_estimator_loop( + estimator_ctx.estimated_fees.clone(), + coin.clone(), + ))); Ok(()) } @@ -114,37 +117,6 @@ impl FeeEstimatorContext { .or_mm_err(|| FeeEstimatorError::AlreadyStopping) } - /// Gas fee estimator loop wrapper - async fn fee_estimator_loop(ctx: MmArc, coin: EthCoin) { - let estimator_ctx = Self::from_ctx(ctx.clone()); - if let Ok(estimator_ctx) = estimator_ctx { - let _ = estimator_ctx.fee_estimator_loop_inner(coin).await; - } - } - - /// Loop polling gas fee estimator - /// - /// This loop periodically calls get_eip1559_gas_price which fetches fee per gas estimations from a gas api provider or calculates them internally - /// The retrieved data are stored in the fee estimator context - /// To connect to the gas api provider the web3 instances from the platform coin are used so ETH coin must be enabled - /// TODO: assumed that once the plaform coin is enabled it is always available and never can be disabled. Should we track it disabled? - async fn fee_estimator_loop_inner(&self, coin: EthCoin) -> Result<(), MmError> { - loop { - let started = common::now_float(); - *self.estimated_fees.lock().await = coin.get_eip1559_gas_price().await.unwrap_or_default(); - - let elapsed = common::now_float() - started; - debug!( - "{FEE_ESTIMATOR_NAME} getting estimated values processed in {} seconds", - elapsed - ); - - let wait_secs = FeeEstimatorContext::get_refresh_interval() - elapsed; - let wait_secs = if wait_secs < 0.0 { 0.0 } else { wait_secs }; - Timer::sleep(wait_secs).await; - } - } - async fn get_estimated_fees(ctx: MmArc) -> Result> { let estimator_ctx = Self::from_ctx(ctx.clone())?; let estimated_fees = estimator_ctx.estimated_fees.lock().await; @@ -171,6 +143,29 @@ impl FeeEstimatorContext { } } +/// Loop polling gas fee estimator +/// +/// This loop periodically calls get_eip1559_gas_price which fetches fee per gas estimations from a gas api provider or calculates them internally +/// The retrieved data are stored in the fee estimator context +/// To connect to the gas api provider the web3 instances from the platform coin are used so ETH coin must be enabled +/// TODO: assumed that once the plaform coin is enabled it is always available and never can be disabled. Should we track it disabled? +async fn fee_estimator_loop(estimated_fees: Arc>, coin: EthCoin) { + loop { + let started = common::now_float(); + *estimated_fees.lock().await = coin.get_eip1559_gas_price().await.unwrap_or_default(); + + let elapsed = common::now_float() - started; + debug!( + "{FEE_ESTIMATOR_NAME} getting estimated values processed in {} seconds", + elapsed + ); + + let wait_secs = FeeEstimatorContext::get_refresh_interval() - elapsed; + let wait_secs = if wait_secs < 0.0 { 0.0 } else { wait_secs }; + Timer::sleep(wait_secs).await; + } +} + /// Rpc request to start or stop gas fee estimator #[derive(Deserialize)] pub struct FeeEstimatorStartStopRequest { From 2031b73750f301c3191789be96c393d573bbacd9 Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 7 Mar 2024 21:12:31 +0500 Subject: [PATCH 10/71] remove unused fee estimator errors --- mm2src/coins/rpc_command/get_estimated_fees.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 7f7b051992..7079cf2796 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -20,20 +20,12 @@ pub enum FeeEstimatorError { CoinNotActivated, #[display(fmt = "Gas estimation not supported for this coin")] CoinNotSupported, - #[display(fmt = "Coin not connected to fee estimator")] - CoinNotConnected, - #[display(fmt = "Platform coin ETH must be activated")] - PlatformCoinNotActivated, #[display(fmt = "Chain id not supported")] ChainNotSupported, #[display(fmt = "Fee estimator is already started")] AlreadyStarted, #[display(fmt = "Transport error: {}", _0)] Transport(String), - #[display(fmt = "Cannot start fee estimator if it's currently stopping")] - CannotStartFromStopping, - #[display(fmt = "Fee estimator is already stopping")] - AlreadyStopping, #[display(fmt = "Fee estimator is not running")] NotRunning, #[display(fmt = "Internal error: {}", _0)] @@ -45,13 +37,9 @@ impl HttpStatusCode for FeeEstimatorError { match self { FeeEstimatorError::CoinNotActivated | FeeEstimatorError::CoinNotSupported - | FeeEstimatorError::CoinNotConnected - | FeeEstimatorError::PlatformCoinNotActivated | FeeEstimatorError::ChainNotSupported | FeeEstimatorError::AlreadyStarted - | FeeEstimatorError::AlreadyStopping - | FeeEstimatorError::NotRunning - | FeeEstimatorError::CannotStartFromStopping => StatusCode::BAD_REQUEST, + | FeeEstimatorError::NotRunning => StatusCode::BAD_REQUEST, FeeEstimatorError::Transport(_) | FeeEstimatorError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } @@ -114,7 +102,7 @@ impl FeeEstimatorContext { handle_guard .take() .map(|_| ()) - .or_mm_err(|| FeeEstimatorError::AlreadyStopping) + .or_mm_err(|| FeeEstimatorError::NotRunning) } async fn get_estimated_fees(ctx: MmArc) -> Result> { From c87658aa128dba3ef040d81cb20cce8af7d89f73 Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 7 Mar 2024 21:13:05 +0500 Subject: [PATCH 11/71] rename get esimated fee rpc --- mm2src/coins/rpc_command/get_estimated_fees.rs | 2 +- mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 7079cf2796..5a58d3557e 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -206,7 +206,7 @@ pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopReques /// Only main chain is supported /// /// Returns latest estimated fee per gas for the next block -pub async fn get_eth_gas_price_estimated(ctx: MmArc, req: FeeEstimatorRequest) -> FeeEstimatorResult { +pub async fn get_eth_estimated_fee_per_gas(ctx: MmArc, req: FeeEstimatorRequest) -> FeeEstimatorResult { FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; let estimated_fees = FeeEstimatorContext::get_estimated_fees(ctx).await?; Ok(estimated_fees) diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 17afc8f2d6..3a3eb18f16 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -16,7 +16,7 @@ use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels, ibc_with use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, get_enabled_coins::get_enabled_coins, - get_estimated_fees::{get_eth_gas_price_estimated, start_eth_fee_estimator, + get_estimated_fees::{get_eth_estimated_fee_per_gas, start_eth_fee_estimator, stop_eth_fee_estimator}, get_new_address::{cancel_get_new_address, get_new_address, init_get_new_address, init_get_new_address_status, init_get_new_address_user_action}, @@ -209,7 +209,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, withdraw_nft).await, "start_eth_fee_estimator" => handle_mmrpc(ctx, request, start_eth_fee_estimator).await, "stop_eth_fee_estimator" => handle_mmrpc(ctx, request, stop_eth_fee_estimator).await, - "get_eth_gas_price_estimated" => handle_mmrpc(ctx, request, get_eth_gas_price_estimated).await, + "get_eth_estimated_fee_per_gas" => handle_mmrpc(ctx, request, get_eth_estimated_fee_per_gas).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] From 61a80a91e4dc9966141c3e04fdd5e182bec66415 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 8 Mar 2024 17:48:05 +0500 Subject: [PATCH 12/71] add doc comments --- mm2src/coins/rpc_command/get_estimated_fees.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 5a58d3557e..a0787028ea 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -11,7 +11,7 @@ use mm2_err_handle::prelude::*; use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_fee_estimator_loop"; -const ETH_SUPPORTED_CHAIN_ID: u64 = 1; +const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported. To support other chains add a FeeEstimatorContext for each chain #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] @@ -135,8 +135,11 @@ impl FeeEstimatorContext { /// /// This loop periodically calls get_eip1559_gas_price which fetches fee per gas estimations from a gas api provider or calculates them internally /// The retrieved data are stored in the fee estimator context -/// To connect to the gas api provider the web3 instances from the platform coin are used so ETH coin must be enabled -/// TODO: assumed that once the plaform coin is enabled it is always available and never can be disabled. Should we track it disabled? +/// To connect to the chain and gas api provider the web3 instances are used from an EthCoin coin passed in the start rpc param, +/// so this coin must be enabled first. +/// Once the loop started any other EthCoin in mainnet may request fee estimations. +/// It is up to GUI to start and stop the loop when it needs it (considering that the data in context may be used +/// for any coin with Eth or Erc20 type from the mainnet). async fn fee_estimator_loop(estimated_fees: Arc>, coin: EthCoin) { loop { let started = common::now_float(); From 46ceaccf13f1a4356f813ff49a8522bc1bed21ba Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 8 Mar 2024 20:28:12 +0500 Subject: [PATCH 13/71] rename gas fee estimation fn like in eip1559 terms --- mm2src/coins/eth.rs | 4 ++-- mm2src/coins/rpc_command/get_estimated_fees.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index ddbb582e53..8dd635c33a 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4752,8 +4752,8 @@ impl EthCoin { Box::new(fut.boxed().compat()) } - /// Get base gas fee and suggest priority tip fees for the next block (see EIP1559) - pub async fn get_eip1559_gas_price(&self) -> Result> { + /// Get gas base fee and suggest priority tip fees for the next block (see EIP-1559) + pub async fn get_eip1559_gas_fee(&self) -> Result> { let coin = self.clone(); let fee_history_namespace: EthFeeHistoryNamespace<_> = coin.web3.api(); let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(fee_history_namespace); diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index a0787028ea..6de5ab6cb3 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,4 +1,4 @@ -//! RPCs to start/stop gas price estimator and get estimated base and priority fee per gas +//! RPCs to start/stop gas fee estimator and get estimated base and priority fee per gas use crate::eth::{EthCoin, FeePerGasEstimated}; use crate::AsyncMutex; @@ -11,7 +11,8 @@ use mm2_err_handle::prelude::*; use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_fee_estimator_loop"; -const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported. To support other chains add a FeeEstimatorContext for each chain +const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) + // To support fee estimations for other chains add a FeeEstimatorContext for a new chain #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] @@ -133,7 +134,7 @@ impl FeeEstimatorContext { /// Loop polling gas fee estimator /// -/// This loop periodically calls get_eip1559_gas_price which fetches fee per gas estimations from a gas api provider or calculates them internally +/// This loop periodically calls get_eip1559_gas_fee which fetches fee per gas estimations from a gas api provider or calculates them internally /// The retrieved data are stored in the fee estimator context /// To connect to the chain and gas api provider the web3 instances are used from an EthCoin coin passed in the start rpc param, /// so this coin must be enabled first. @@ -143,7 +144,7 @@ impl FeeEstimatorContext { async fn fee_estimator_loop(estimated_fees: Arc>, coin: EthCoin) { loop { let started = common::now_float(); - *estimated_fees.lock().await = coin.get_eip1559_gas_price().await.unwrap_or_default(); + *estimated_fees.lock().await = coin.get_eip1559_gas_fee().await.unwrap_or_default(); let elapsed = common::now_float() - started; debug!( From 34134475fe01c6bf4797117cc6cd4e3c096911d3 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 8 Mar 2024 20:32:03 +0500 Subject: [PATCH 14/71] refactor fee estimator error dsc --- mm2src/coins/rpc_command/get_estimated_fees.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 6de5ab6cb3..abfa493f99 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -10,7 +10,7 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use std::sync::Arc; -const FEE_ESTIMATOR_NAME: &str = "eth_fee_estimator_loop"; +const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) // To support fee estimations for other chains add a FeeEstimatorContext for a new chain @@ -19,15 +19,15 @@ const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported (Blockn pub enum FeeEstimatorError { #[display(fmt = "Coin not activated")] CoinNotActivated, - #[display(fmt = "Gas estimation not supported for this coin")] + #[display(fmt = "Gas fee estimation not supported for this coin")] CoinNotSupported, #[display(fmt = "Chain id not supported")] ChainNotSupported, - #[display(fmt = "Fee estimator is already started")] + #[display(fmt = "Gas fee estimator is already started")] AlreadyStarted, #[display(fmt = "Transport error: {}", _0)] Transport(String), - #[display(fmt = "Fee estimator is not running")] + #[display(fmt = "Gas fee estimator is not running")] NotRunning, #[display(fmt = "Internal error: {}", _0)] InternalError(String), From 1b4180f3a9814562127926e1785d2bcb78d71fe2 Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 13 Mar 2024 14:48:51 +0500 Subject: [PATCH 15/71] remove discontinued gas station call, use join to call alternate gas price rpcs, fix 'priority_rewards' in FeeHistory --- mm2src/coins/eth.rs | 109 ++++++------------------- mm2src/coins/eth/eip1559_gas_fee.rs | 24 +++--- mm2src/coins/eth/eth_tests.rs | 46 +++-------- mm2src/coins/eth/web3_transport/mod.rs | 2 +- 4 files changed, 53 insertions(+), 128 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index f2c9d4b84b..d1accf4b4f 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -51,12 +51,12 @@ use ethkey::{sign, verify_address}; use futures::compat::Future01CompatExt; use futures::future::{join, join_all, select_ok, try_join_all, Either, FutureExt, TryFutureExt}; use futures01::Future; -use http::{StatusCode, Uri}; +use http::Uri; use instant::Instant; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; -use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; +use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::bigdecimal_custom::CheckedDivision; use mm2_number::{BigDecimal, MmNumber}; use mm2_rpc::data::legacy::GasStationPricePolicy; @@ -150,7 +150,6 @@ pub enum PaymentState { } // Ethgasstation API returns response in 10^8 wei units. So 10 from their API mean 1 gwei const ETH_GAS_STATION_DECIMALS: u8 = 8; -const GAS_PRICE_PERCENT: u64 = 10; /// It can change 12.5% max each block according to https://www.blocknative.com/blog/eip-1559-fees const BASE_BLOCK_FEE_DIFF_PCT: u64 = 13; const DEFAULT_LOGS_BLOCK_RANGE: u64 = 1000; @@ -189,7 +188,6 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; -pub type GasStationResult = Result>; type EthPrivKeyPolicy = PrivKeyPolicy; type GasDetails = (U256, U256); @@ -508,20 +506,6 @@ pub enum EthAddressFormat { MixedCase, } -#[cfg_attr(test, mockable)] -async fn make_gas_station_request(url: &str) -> GasStationResult { - let resp = slurp_url(url).await?; - if resp.0 != StatusCode::OK { - let error = format!("Gas price request failed with status code {}", resp.0); - return MmError::err(GasStationReqErr::Transport { - uri: url.to_owned(), - error, - }); - } - let result: GasStationData = json::from_slice(&resp.2)?; - Ok(result) -} - impl EthCoinImpl { #[cfg(not(target_arch = "wasm32"))] fn eth_traces_path(&self, ctx: &MmArc) -> PathBuf { @@ -4853,45 +4837,33 @@ impl EthCoin { pub fn get_gas_price(&self) -> Web3RpcFut { let coin = self.clone(); let fut = async move { - // TODO refactor to error_log_passthrough once simple maker bot is merged - let gas_station_price = match &coin.gas_station_url { - Some(url) => { - match GasStationData::get_gas_price(url, coin.gas_station_decimals, coin.gas_station_policy.clone()) - .compat() - .await - { - Ok(from_station) => Some(increase_by_percent_one_gwei(from_station, GAS_PRICE_PERCENT)), - Err(e) => { - error!("Error {} on request to gas station url {}", e, url); - None - }, - } - }, - None => None, - }; - - let eth_gas_price = match coin.gas_price().await { - Ok(eth_gas) => Some(eth_gas), - Err(e) => { - error!("Error {} on eth_gasPrice request", e); - None - }, - }; - - let eth_fee_history_price = match coin.eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]).await { - Ok(res) => res - .base_fee_per_gas - .first() - .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)), - Err(e) => { - debug!("Error {} on eth_feeHistory request", e); - None - }, - }; + let eth_gas_price_fut = async { + match coin.gas_price().await { + Ok(eth_gas) => Some(eth_gas), + Err(e) => { + error!("Error {} on eth_gasPrice request", e); + None + }, + } + }.boxed(); + + let eth_fee_history_price_fut = async { + match coin.eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]).await { + Ok(res) => res + .base_fee_per_gas + .first() + .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)), + Err(e) => { + debug!("Error {} on eth_feeHistory request", e); + None + }, + } + }.boxed(); + let (eth_gas_price, eth_fee_history_price) = join(eth_gas_price_fut, eth_fee_history_price_fut).await; // on editions < 2021 the compiler will resolve array.into_iter() as (&array).into_iter() // https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterator-for-arrays.html#details - IntoIterator::into_iter([gas_station_price, eth_gas_price, eth_fee_history_price]) + IntoIterator::into_iter([eth_gas_price, eth_fee_history_price]) .flatten() .max() .or_mm_err(|| Web3RpcError::Internal("All requests failed".into())) @@ -5712,35 +5684,6 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result NumConversResult { - let gas_price = match gas_price_policy { - GasStationPricePolicy::MeanAverageFast => ((&self.average + &self.fast) / MmNumber::from(2)).into(), - GasStationPricePolicy::Average => self.average.to_decimal(), - }; - wei_from_big_decimal(&gas_price, decimals) - } - - fn get_gas_price(uri: &str, decimals: u8, gas_price_policy: GasStationPricePolicy) -> Web3RpcFut { - let uri = uri.to_owned(); - let fut = async move { - make_gas_station_request(&uri) - .await? - .average_gwei(decimals, gas_price_policy) - .mm_err(|e| Web3RpcError::Internal(e.0)) - }; - Box::new(fut.boxed().compat()) - } -} - async fn get_token_decimals(web3: &Web3, token_addr: Address) -> Result { let function = try_s!(ERC20_CONTRACT.function("decimals")); let data = try_s!(function.encode_input(&[])); diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index 369b6735fc..bea693b010 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -5,6 +5,7 @@ use super::web3_transport::FeeHistoryResult; use super::{u256_to_big_decimal, Web3RpcError, Web3RpcResult, ETH_GWEI_DECIMALS}; use ethereum_types::U256; use mm2_err_handle::mm_error::MmError; +use mm2_err_handle::or_mm_error::OrMmError; use mm2_number::BigDecimal; use num_traits::FromPrimitive; use url::Url; @@ -234,7 +235,7 @@ impl FeePerGasSimpleEstimator { .await; match res { - Ok(fee_history) => Ok(Self::calculate_with_history(&fee_history)), + Ok(fee_history) => Ok(Self::calculate_with_history(&fee_history)?), Err(_) => MmError::err(Web3RpcError::Internal("Eth requests failed".into())), } } @@ -243,10 +244,11 @@ impl FeePerGasSimpleEstimator { level: PriorityLevelId, base_fee: &BigDecimal, fee_history: &FeeHistoryResult, - ) -> FeePerGasLevel { + ) -> Web3RpcResult { let level_i = level as usize; let mut level_rewards = fee_history - .priority_rewards + .priority_rewards.as_ref() + .or_mm_err(|| Web3RpcError::Internal("expected reward in eth_feeHistory".into()))? .iter() .map(|rewards| { if level_i < rewards.len() { @@ -264,28 +266,28 @@ impl FeePerGasSimpleEstimator { * BigDecimal::from_f64(Self::ADJUST_MAX_FEE[level_i]).unwrap_or_else(|| BigDecimal::from(0)) + max_priority_fee_per_gas.clone() * BigDecimal::from_f64(Self::ADJUST_MAX_PRIORITY_FEE[level_i]).unwrap_or_else(|| BigDecimal::from(0)); // TODO maybe use checked ops - FeePerGasLevel { + Ok(FeePerGasLevel { max_priority_fee_per_gas, max_fee_per_gas, min_wait_time: None, max_wait_time: None, // TODO: maybe fill with some default values (and mark as uncertain)? - } + }) } /// estimate priority fees by fee history - fn calculate_with_history(fee_history: &FeeHistoryResult) -> FeePerGasEstimated { + fn calculate_with_history(fee_history: &FeeHistoryResult) -> Web3RpcResult { let base_fee = *fee_history.base_fee_per_gas.first().unwrap_or(&U256::from(0)); let base_fee = u256_to_big_decimal(base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); - FeePerGasEstimated { + Ok(FeePerGasEstimated { base_fee: base_fee.clone(), - low: Self::priority_fee_for_level(PriorityLevelId::Low, &base_fee, fee_history), - medium: Self::priority_fee_for_level(PriorityLevelId::Medium, &base_fee, fee_history), - high: Self::priority_fee_for_level(PriorityLevelId::High, &base_fee, fee_history), + low: Self::priority_fee_for_level(PriorityLevelId::Low, &base_fee, fee_history)?, + medium: Self::priority_fee_for_level(PriorityLevelId::Medium, &base_fee, fee_history)?, + high: Self::priority_fee_for_level(PriorityLevelId::High, &base_fee, fee_history)?, source: EstimationSource::Simple, units: EstimationUnits::Gwei, base_fee_trend: String::default(), priority_fee_trend: String::default(), - } + }) } } diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index c7e32a78ce..f9a5dc64e7 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -18,6 +18,8 @@ const GAS_PRICE_APPROXIMATION_ON_START_SWAP: u64 = 51_500_000_000; const GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE: u64 = 52_500_000_000; // `GAS_PRICE` increased by 7% const GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE: u64 = 53_500_000_000; +// old way to add some extra gas to the returned value from gas station (non-existent now), still used in tests +const GAS_PRICE_PERCENT: u64 = 10; const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 1.; @@ -453,39 +455,6 @@ fn test_wait_for_payment_spend_timeout() { .is_err()); } -#[test] -fn test_gas_station() { - make_gas_station_request.mock_safe(|_| { - let data = GasStationData { - average: 500.into(), - fast: 1000.into(), - }; - MockResult::Return(Box::pin(async move { Ok(data) })) - }); - let res_eth = GasStationData::get_gas_price( - "https://ethgasstation.info/api/ethgasAPI.json", - 8, - GasStationPricePolicy::MeanAverageFast, - ) - .wait() - .unwrap(); - let one_gwei = U256::from(10u64.pow(9)); - - let expected_eth_wei = U256::from(75) * one_gwei; - assert_eq!(expected_eth_wei, res_eth); - - let res_polygon = GasStationData::get_gas_price( - "https://gasstation-mainnet.matic.network/", - 9, - GasStationPricePolicy::Average, - ) - .wait() - .unwrap(); - - let expected_eth_polygon = U256::from(500) * one_gwei; - assert_eq!(expected_eth_polygon, res_polygon); -} - #[cfg(not(target_arch = "wasm32"))] #[test] fn test_withdraw_impl_manual_fee() { @@ -1331,3 +1300,14 @@ fn test_eth_validate_valid_and_invalid_pubkey() { assert!(coin.validate_other_pubkey(&[1u8; 20]).is_err()); assert!(coin.validate_other_pubkey(&[1u8; 8]).is_err()); } + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_fee_history() { + use mm2_test_helpers::for_tests::ETH_DEV_NODES; + + let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, ETH_DEV_NODES, None); + // check fee history without percentiles decoded okay + let res = block_on(coin.eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[])); + assert!(res.is_ok()); +} diff --git a/mm2src/coins/eth/web3_transport/mod.rs b/mm2src/coins/eth/web3_transport/mod.rs index 9a20b98871..f0186b626c 100644 --- a/mm2src/coins/eth/web3_transport/mod.rs +++ b/mm2src/coins/eth/web3_transport/mod.rs @@ -131,7 +131,7 @@ pub struct FeeHistoryResult { #[serde(rename = "gasUsedRatio")] pub gas_used_ratio: Vec, #[serde(rename = "reward")] - pub priority_rewards: Vec>, + pub priority_rewards: Option>>, } /// Generates a signed message and inserts it into the request payload. From 8750b6bcabfc1377c6000f7dc960d166863b94e4 Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 13 Mar 2024 15:25:41 +0500 Subject: [PATCH 16/71] fix fmt --- mm2src/coins/eth.rs | 56 +++++++++++++++-------------- mm2src/coins/eth/eip1559_gas_fee.rs | 9 +++-- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index d1accf4b4f..56fc0d50f3 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4838,16 +4838,17 @@ impl EthCoin { let coin = self.clone(); let fut = async move { let eth_gas_price_fut = async { - match coin.gas_price().await { + match coin.gas_price().await { Ok(eth_gas) => Some(eth_gas), Err(e) => { error!("Error {} on eth_gasPrice request", e); None }, } - }.boxed(); + } + .boxed(); - let eth_fee_history_price_fut = async { + let eth_fee_history_price_fut = async { match coin.eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]).await { Ok(res) => res .base_fee_per_gas @@ -4858,7 +4859,8 @@ impl EthCoin { None }, } - }.boxed(); + } + .boxed(); let (eth_gas_price, eth_fee_history_price) = join(eth_gas_price_fut, eth_fee_history_price_fut).await; // on editions < 2021 the compiler will resolve array.into_iter() as (&array).into_iter() @@ -4874,32 +4876,32 @@ impl EthCoin { /// Get gas base fee and suggest priority tip fees for the next block (see EIP-1559) pub async fn get_eip1559_gas_fee(&self) -> Result> { let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(self); - let ctx = MmArc::from_weak(&self.ctx) - .ok_or("!ctx") - .map_to_mm(|err| Web3RpcError::Internal(err.to_string()))?; + let ctx = + MmArc::from_weak(&self.ctx).ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; let gas_api_conf = ctx.conf["gas_api"].clone(); - if !gas_api_conf.is_null() { - let gas_api_conf: GasApiConfig = - json::from_value(gas_api_conf).map_to_mm(|e| Web3RpcError::InvalidGasApiConfig(e.to_string()))?; - let provider_estimator_fut = match gas_api_conf.provider { - GasApiProvider::Infura => InfuraGasApiCaller::fetch_infura_fee_estimation(&gas_api_conf.url).boxed(), - GasApiProvider::Blocknative => { - BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(&gas_api_conf.url).boxed() - }, - }; - - let (res_history, res_provider) = join(history_estimator_fut, provider_estimator_fut).await; - match (res_history, res_provider) { - (Ok(ref history_est), Err(_)) => Ok(history_est.clone()), - (_, Ok(ref provider_est)) => Ok(provider_est.clone()), - (_, _) => MmError::err(Web3RpcError::Internal("All gas api requests failed".into())), // TODO: send errors - } - } else { - // use only internal fee per gas estimator - history_estimator_fut + if gas_api_conf.is_null() { + return history_estimator_fut .await - .mm_err(|e| Web3RpcError::Internal(e.to_string())) + .map_err(|e| MmError::new(Web3RpcError::Internal(e.to_string()))); } + let gas_api_conf: GasApiConfig = json::from_value(gas_api_conf) + .map_err(|e| MmError::new(Web3RpcError::InvalidGasApiConfig(e.to_string())))?; + let provider_estimator_fut = match gas_api_conf.provider { + GasApiProvider::Infura => InfuraGasApiCaller::fetch_infura_fee_estimation(&gas_api_conf.url).boxed(), + GasApiProvider::Blocknative => { + BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(&gas_api_conf.url).boxed() + }, + }; + provider_estimator_fut + .or_else(|provider_estimator_err| { + history_estimator_fut.map_err(move |history_estimator_err| { + MmError::new(Web3RpcError::Internal(format!( + "All gas api requests failed, provider estimator error: {}, history estimator error: {}", + provider_estimator_err, history_estimator_err + ))) + }) + }) + .await } /// Checks every second till at least one ETH node recognizes that nonce is increased. diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index bea693b010..205f5be641 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -1,8 +1,8 @@ //! Provides estimations of base and priority fee per gas or fetch estimations from a gas api provider -use crate::EthCoin; use super::web3_transport::FeeHistoryResult; use super::{u256_to_big_decimal, Web3RpcError, Web3RpcResult, ETH_GWEI_DECIMALS}; +use crate::EthCoin; use ethereum_types::U256; use mm2_err_handle::mm_error::MmError; use mm2_err_handle::or_mm_error::OrMmError; @@ -223,9 +223,7 @@ impl FeePerGasSimpleEstimator { } /// Estimate simplified gas priority fees based on fee history - pub async fn estimate_fee_by_history( - coin: &EthCoin, - ) -> Web3RpcResult { + pub async fn estimate_fee_by_history(coin: &EthCoin) -> Web3RpcResult { let res = coin .eth_fee_history( U256::from(Self::history_depth()), @@ -247,7 +245,8 @@ impl FeePerGasSimpleEstimator { ) -> Web3RpcResult { let level_i = level as usize; let mut level_rewards = fee_history - .priority_rewards.as_ref() + .priority_rewards + .as_ref() .or_mm_err(|| Web3RpcError::Internal("expected reward in eth_feeHistory".into()))? .iter() .map(|rewards| { From 504657eae02510f957b6af2394b2a2c4f00eaa38 Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 13 Mar 2024 17:20:08 +0500 Subject: [PATCH 17/71] remove gas station remnants --- mm2src/adex_cli/src/rpc_data.rs | 8 +-- mm2src/coins/eth.rs | 54 +------------------ mm2src/coins/eth/eth_tests.rs | 18 ------- mm2src/coins/eth/eth_wasm_tests.rs | 3 -- mm2src/coins/eth/v2_activation.rs | 13 ----- mm2src/mm2_rpc/src/data/legacy.rs | 3 +- mm2src/mm2_rpc/src/data/legacy/activation.rs | 1 - .../mm2_rpc/src/data/legacy/activation/eth.rs | 16 ------ 8 files changed, 3 insertions(+), 113 deletions(-) delete mode 100644 mm2src/mm2_rpc/src/data/legacy/activation/eth.rs diff --git a/mm2src/adex_cli/src/rpc_data.rs b/mm2src/adex_cli/src/rpc_data.rs index f8e1329453..a3146cbe47 100644 --- a/mm2src/adex_cli/src/rpc_data.rs +++ b/mm2src/adex_cli/src/rpc_data.rs @@ -3,7 +3,7 @@ //! *Note: it's expected that the following data types will be moved to mm2_rpc::data when mm2 is refactored to be able to handle them* //! -use mm2_rpc::data::legacy::{ElectrumProtocol, GasStationPricePolicy, UtxoMergeParams}; +use mm2_rpc::data::legacy::{ElectrumProtocol, UtxoMergeParams}; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] @@ -23,12 +23,6 @@ pub(crate) struct EnableRequest { #[serde(skip_serializing_if = "Option::is_none")] fallback_swap_contract: Option, #[serde(skip_serializing_if = "Option::is_none")] - gas_station_url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - gas_station_decimals: Option, - #[serde(skip_serializing_if = "Option::is_none")] - gas_station_policy: Option, - #[serde(skip_serializing_if = "Option::is_none")] mm2: Option, #[serde(default)] tx_history: bool, diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 56fc0d50f3..8b48ce963b 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -56,10 +56,9 @@ use instant::Instant; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; -use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; +use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator}; use mm2_number::bigdecimal_custom::CheckedDivision; use mm2_number::{BigDecimal, MmNumber}; -use mm2_rpc::data::legacy::GasStationPricePolicy; #[cfg(test)] use mocktopus::macros::*; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; @@ -148,8 +147,6 @@ pub enum PaymentState { Spent, Refunded, } -// Ethgasstation API returns response in 10^8 wei units. So 10 from their API mean 1 gwei -const ETH_GAS_STATION_DECIMALS: u8 = 8; /// It can change 12.5% max each block according to https://www.blocknative.com/blog/eip-1559-fees const BASE_BLOCK_FEE_DIFF_PCT: u64 = 13; const DEFAULT_LOGS_BLOCK_RANGE: u64 = 1000; @@ -191,35 +188,6 @@ pub type Web3RpcResult = Result>; type EthPrivKeyPolicy = PrivKeyPolicy; type GasDetails = (U256, U256); -#[derive(Debug, Display)] -pub enum GasStationReqErr { - #[display(fmt = "Transport '{}' error: {}", uri, error)] - Transport { - uri: String, - error: String, - }, - #[display(fmt = "Invalid response: {}", _0)] - InvalidResponse(String), - Internal(String), -} - -impl From for GasStationReqErr { - fn from(e: serde_json::Error) -> Self { GasStationReqErr::InvalidResponse(e.to_string()) } -} - -impl From for GasStationReqErr { - fn from(e: SlurpError) -> Self { - let error = e.to_string(); - match e { - SlurpError::ErrorDeserializing { .. } => GasStationReqErr::InvalidResponse(error), - SlurpError::Transport { uri, .. } | SlurpError::Timeout { uri, .. } => { - GasStationReqErr::Transport { uri, error } - }, - SlurpError::Internal(_) | SlurpError::InvalidRequest(_) => GasStationReqErr::Internal(error), - } - } -} - #[derive(Debug, Display)] pub enum Web3RpcError { #[display(fmt = "Transport: {}", _0)] @@ -236,16 +204,6 @@ pub enum Web3RpcError { NftProtocolNotSupported, } -impl From for Web3RpcError { - fn from(err: GasStationReqErr) -> Self { - match err { - GasStationReqErr::Transport { .. } => Web3RpcError::Transport(err.to_string()), - GasStationReqErr::InvalidResponse(err) => Web3RpcError::InvalidResponse(err), - GasStationReqErr::Internal(err) => Web3RpcError::Internal(err), - } - } -} - impl From for Web3RpcError { fn from(e: serde_json::Error) -> Self { Web3RpcError::InvalidResponse(e.to_string()) } } @@ -460,9 +418,6 @@ pub struct EthCoinImpl { contract_supports_watchers: bool, web3_instances: AsyncMutex>, decimals: u8, - gas_station_url: Option, - gas_station_decimals: u8, - gas_station_policy: GasStationPricePolicy, history_sync_state: Mutex, required_confirmations: AtomicU64, /// Coin needs access to the context in order to reuse the logging and shutdown facilities. @@ -5907,10 +5862,6 @@ pub async fn eth_coin_from_conf_and_request( HistorySyncState::NotEnabled }; - let gas_station_decimals: Option = try_s!(json::from_value(req["gas_station_decimals"].clone())); - let gas_station_policy: GasStationPricePolicy = - json::from_value(req["gas_station_policy"].clone()).unwrap_or_default(); - let key_lock = match &coin_type { EthCoinType::Eth => String::from(ticker), EthCoinType::Erc20 { ref platform, .. } => String::from(platform), @@ -5936,9 +5887,6 @@ pub async fn eth_coin_from_conf_and_request( contract_supports_watchers, decimals, ticker: ticker.into(), - gas_station_url: try_s!(json::from_value(req["gas_station_url"].clone())), - gas_station_decimals: gas_station_decimals.unwrap_or(ETH_GAS_STATION_DECIMALS), - gas_station_policy, web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(initial_history_state), ctx: ctx.weak(), diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index f9a5dc64e7..7b3e378fea 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -139,10 +139,7 @@ fn eth_coin_from_keypair( let eth_coin = EthCoin(Arc::new(EthCoinImpl { coin_type, decimals: 18, - gas_station_url: None, - gas_station_decimals: ETH_GAS_STATION_DECIMALS, history_sync_state: Mutex::new(HistorySyncState::NotEnabled), - gas_station_policy: GasStationPricePolicy::MeanAverageFast, my_address: key_pair.address(), sign_message_prefix: Some(String::from("Ethereum Signed Message:\n")), priv_key_policy: key_pair.into(), @@ -358,9 +355,6 @@ fn test_nonce_several_urls() { }, ]), decimals: 18, - gas_station_url: Some("https://ethgasstation.info/json/ethgasAPI.json".into()), - gas_station_decimals: ETH_GAS_STATION_DECIMALS, - gas_station_policy: GasStationPricePolicy::MeanAverageFast, history_sync_state: Mutex::new(HistorySyncState::NotStarted), ctx: ctx.weak(), required_confirmations: 1.into(), @@ -402,9 +396,6 @@ fn test_wait_for_payment_spend_timeout() { let coin = EthCoinImpl { coin_type: EthCoinType::Eth, decimals: 18, - gas_station_url: None, - gas_station_decimals: ETH_GAS_STATION_DECIMALS, - gas_station_policy: GasStationPricePolicy::MeanAverageFast, history_sync_state: Mutex::new(HistorySyncState::NotEnabled), my_address: key_pair.address(), sign_message_prefix: Some(String::from("Ethereum Signed Message:\n")), @@ -1089,9 +1080,6 @@ fn test_message_hash() { contract_supports_watchers: false, web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), decimals: 18, - gas_station_url: None, - gas_station_decimals: ETH_GAS_STATION_DECIMALS, - gas_station_policy: GasStationPricePolicy::MeanAverageFast, history_sync_state: Mutex::new(HistorySyncState::NotStarted), ctx: ctx.weak(), required_confirmations: 1.into(), @@ -1137,9 +1125,6 @@ fn test_sign_verify_message() { contract_supports_watchers: false, web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), decimals: 18, - gas_station_url: None, - gas_station_decimals: ETH_GAS_STATION_DECIMALS, - gas_station_policy: GasStationPricePolicy::MeanAverageFast, history_sync_state: Mutex::new(HistorySyncState::NotStarted), ctx: ctx.weak(), required_confirmations: 1.into(), @@ -1186,9 +1171,6 @@ fn test_eth_extract_secret() { token_addr: Address::from_str("0xc0eb7aed740e1796992a08962c15661bdeb58003").unwrap(), }, decimals: 18, - gas_station_url: None, - gas_station_decimals: ETH_GAS_STATION_DECIMALS, - gas_station_policy: GasStationPricePolicy::MeanAverageFast, history_sync_state: Mutex::new(HistorySyncState::NotEnabled), my_address: key_pair.address(), sign_message_prefix: Some(String::from("Ethereum Signed Message:\n")), diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index ea2dace0cd..d8d7df851d 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -39,9 +39,6 @@ async fn test_send() { contract_supports_watchers: false, web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), decimals: 18, - gas_station_url: None, - gas_station_decimals: ETH_GAS_STATION_DECIMALS, - gas_station_policy: GasStationPricePolicy::MeanAverageFast, history_sync_state: Mutex::new(HistorySyncState::NotStarted), ctx: ctx.weak(), required_confirmations: 1.into(), diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 1573a17cf7..f6d391be58 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -118,10 +118,6 @@ pub struct EthActivationV2Request { pub fallback_swap_contract: Option
, #[serde(default)] pub contract_supports_watchers: bool, - pub gas_station_url: Option, - pub gas_station_decimals: Option, - #[serde(default)] - pub gas_station_policy: GasStationPricePolicy, pub mm2: Option, pub required_confirmations: Option, #[serde(default)] @@ -302,9 +298,6 @@ impl EthCoin { contract_supports_watchers: self.contract_supports_watchers, decimals, ticker, - gas_station_url: self.gas_station_url.clone(), - gas_station_decimals: self.gas_station_decimals, - gas_station_policy: self.gas_station_policy.clone(), web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), ctx: self.ctx.clone(), @@ -351,9 +344,6 @@ impl EthCoin { contract_supports_watchers: self.contract_supports_watchers, web3_instances: self.web3_instances.lock().await.clone().into(), decimals: self.decimals, - gas_station_url: self.gas_station_url.clone(), - gas_station_decimals: self.gas_station_decimals, - gas_station_policy: self.gas_station_policy.clone(), history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), required_confirmations: AtomicU64::new(self.required_confirmations.load(Ordering::Relaxed)), ctx: self.ctx.clone(), @@ -458,9 +448,6 @@ pub async fn eth_coin_from_conf_and_request_v2( contract_supports_watchers: req.contract_supports_watchers, decimals: ETH_DECIMALS, ticker, - gas_station_url: req.gas_station_url, - gas_station_decimals: req.gas_station_decimals.unwrap_or(ETH_GAS_STATION_DECIMALS), - gas_station_policy: req.gas_station_policy, web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(HistorySyncState::NotEnabled), ctx: ctx.weak(), diff --git a/mm2src/mm2_rpc/src/data/legacy.rs b/mm2src/mm2_rpc/src/data/legacy.rs index c16a328847..07869d76ba 100644 --- a/mm2src/mm2_rpc/src/data/legacy.rs +++ b/mm2src/mm2_rpc/src/data/legacy.rs @@ -3,8 +3,7 @@ #[path = "legacy/utility.rs"] mod utility; #[path = "legacy/wallet.rs"] mod wallet; -pub use activation::{eth::GasStationPricePolicy, - utxo::{ElectrumProtocol, UtxoMergeParams}, +pub use activation::{utxo::{ElectrumProtocol, UtxoMergeParams}, CoinInitResponse, EnabledCoin, GetEnabledResponse}; pub use orders::{AggregatedOrderbookEntry, MatchBy, OrderConfirmationsSettings, OrderType, OrderbookRequest, OrderbookResponse, RpcOrderbookEntry, SellBuyRequest, SellBuyResponse, TakerAction, diff --git a/mm2src/mm2_rpc/src/data/legacy/activation.rs b/mm2src/mm2_rpc/src/data/legacy/activation.rs index a43f531b38..1bb6feafb3 100644 --- a/mm2src/mm2_rpc/src/data/legacy/activation.rs +++ b/mm2src/mm2_rpc/src/data/legacy/activation.rs @@ -1,4 +1,3 @@ -#[path = "activation/eth.rs"] pub mod eth; #[path = "activation/utxo.rs"] pub mod utxo; use common::serde_derive::{Deserialize, Serialize}; diff --git a/mm2src/mm2_rpc/src/data/legacy/activation/eth.rs b/mm2src/mm2_rpc/src/data/legacy/activation/eth.rs deleted file mode 100644 index 75146a9c2d..0000000000 --- a/mm2src/mm2_rpc/src/data/legacy/activation/eth.rs +++ /dev/null @@ -1,16 +0,0 @@ -use common::serde_derive::{Deserialize, Serialize}; - -/// Using tagged representation to allow adding variants with coefficients, percentage, etc in the future. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(tag = "policy", content = "additional_data")] -pub enum GasStationPricePolicy { - /// Use mean between average and fast values, default and recommended to use on ETH mainnet due to - /// gas price big spikes. - MeanAverageFast, - /// Use average value only. Useful for non-heavily congested networks (Matic, etc.) - Average, -} - -impl Default for GasStationPricePolicy { - fn default() -> Self { GasStationPricePolicy::MeanAverageFast } -} From fc4ca71252944ca30aea26b4426c022693413dcf Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 4 Mar 2024 19:05:56 +0500 Subject: [PATCH 18/71] refactor to support typed txns (used test eth dependency repo), renamed trait tx_hash to tx_hash_as_bytes to avoid conflict --- Cargo.lock | 8 +- mm2src/coins/Cargo.toml | 4 +- mm2src/coins/eth.rs | 142 +++++++++--------- mm2src/coins/eth/eth_tests.rs | 2 +- mm2src/coins/lightning.rs | 2 +- mm2src/coins/lightning/ln_platform.rs | 2 +- mm2src/coins/lp_coins.rs | 2 +- mm2src/coins/my_tx_history_v2.rs | 2 +- mm2src/coins/tendermint/tendermint_coin.rs | 6 +- mm2src/coins/test_coin.rs | 2 +- mm2src/coins/utxo.rs | 2 +- mm2src/coins/utxo/utxo_common.rs | 2 +- mm2src/coins/z_coin.rs | 2 +- mm2src/coins/z_coin/z_coin_native_tests.rs | 8 +- mm2src/mm2_eth/Cargo.toml | 2 +- mm2src/mm2_main/src/lp_swap.rs | 2 +- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 16 +- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 26 ++-- mm2src/mm2_main/src/lp_swap/swap_watcher.rs | 4 +- mm2src/mm2_main/src/lp_swap/taker_restart.rs | 8 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 20 +-- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 50 +++--- .../tests/docker_tests/docker_tests_common.rs | 4 +- .../tests/docker_tests/eth_docker_tests.rs | 8 +- .../tests/docker_tests/qrc20_tests.rs | 30 ++-- .../tests/docker_tests/swap_proto_v2_tests.rs | 32 ++-- .../tests/docker_tests/swap_watcher_tests.rs | 26 ++-- mm2src/mm2_net/Cargo.toml | 2 +- 28 files changed, 208 insertions(+), 208 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 475508ffe3..9fa788d049 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,7 +2174,7 @@ dependencies = [ [[package]] name = "ethcore-transaction" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" dependencies = [ "ethereum-types", "ethkey", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" dependencies = [ "byteorder", "edit-distance", @@ -4109,7 +4109,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" [[package]] name = "memchr" @@ -8647,7 +8647,7 @@ dependencies = [ [[package]] name = "unexpected" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" [[package]] name = "unicode-bidi" diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index aeee782425..8efe11e9e3 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -44,9 +44,9 @@ derive_more = "0.99" ed25519-dalek = "1.0.1" enum_derives = { path = "../derives/enum_derives" } ethabi = { version = "17.0.0" } -ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } +ethcore-transaction = { git = "https://github.com/dimxy/mm2-parity-ethereum.git", branch = "eip1559-support" } ethereum-types = { version = "0.13", default-features = false, features = ["std", "serialize"] } -ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } +ethkey = { git = "https://github.com/dimxy/mm2-parity-ethereum.git", branch = "eip1559-support" } # Waiting for https://github.com/rust-lang/rust/issues/54725 to use on Stable. #enum_dispatch = "0.1" futures01 = { version = "0.1", package = "futures" } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 8b48ce963b..9d040b1506 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -44,7 +44,7 @@ use derive_more::Display; use enum_derives::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; -use ethcore_transaction::{Action, Transaction as UnSignedEthTx, UnverifiedTransaction}; +use ethcore_transaction::{Action, LegacyTransaction, TransactionWrapper as UnSignedEthTx, UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; use ethereum_types::{Address, H160, H256, U256}; use ethkey::{public_to_address, KeyPair, Public, Signature}; use ethkey::{sign, verify_address}; @@ -683,19 +683,19 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { .await? .map_to_mm(WithdrawError::Transport)?; - let tx = UnSignedEthTx { + let tx = UnSignedEthTx::Legacy(LegacyTransaction { nonce, value: eth_value, action: Action::Call(call_addr), data, gas, gas_price, - }; + }); let signed = tx.sign(key_pair.secret(), coin.chain_id); let bytes = rlp::encode(&signed); - (signed.hash, BytesJson::from(bytes.to_vec())) + (signed.tx_hash(), BytesJson::from(bytes.to_vec())) }, EthPrivKeyPolicy::Trezor => { return MmError::err(WithdrawError::UnsupportedError( @@ -840,14 +840,14 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .await? .map_to_mm(WithdrawError::Transport)?; - let tx = UnSignedEthTx { + let tx = UnSignedEthTx::Legacy(LegacyTransaction { nonce, value: eth_value, action: Action::Call(call_addr), data, gas, gas_price, - }; + }); let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); @@ -856,7 +856,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), - tx_hash: format!("{:02x}", signed.tx_hash()), + tx_hash: format!("{:02x}", signed.tx_hash_as_bytes()), from: vec![my_address_str], to: vec![withdraw_type.to], contract_type: ContractType::Erc1155, @@ -928,14 +928,14 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .await? .map_to_mm(WithdrawError::Transport)?; - let tx = UnSignedEthTx { + let tx = UnSignedEthTx::Legacy(LegacyTransaction { nonce, value: eth_value, action: Action::Call(call_addr), data, gas, gas_price, - }; + }); let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); @@ -944,7 +944,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), - tx_hash: format!("{:02x}", signed.tx_hash()), + tx_hash: format!("{:02x}", signed.tx_hash_as_bytes()), from: vec![my_address_str], to: vec![withdraw_type.to], contract_type: ContractType::Erc721, @@ -1029,7 +1029,7 @@ impl SwapOps for EthCoin { _ => panic!(), }; validate_fee_impl(self.clone(), EthValidateFeeArgs { - fee_tx_hash: &tx.hash, + fee_tx_hash: &tx.tx_hash(), expected_sender: validate_fee_args.expected_sender, fee_addr: validate_fee_args.fee_addr, amount: &validate_fee_args.dex_fee.fee_amount().into(), @@ -1154,14 +1154,14 @@ impl SwapOps for EthCoin { spend_tx: &[u8], watcher_reward: bool, ) -> Result, String> { - let unverified: UnverifiedTransaction = try_s!(rlp::decode(spend_tx)); + let unverified: UnverifiedTransactionWrapper = try_s!(rlp::decode(spend_tx)); let function_name = get_function_name("receiverSpend", watcher_reward); let function = try_s!(SWAP_CONTRACT.function(&function_name)); // Validate contract call; expected to be receiverSpend. // https://www.4byte.directory/signatures/?bytes4_signature=02ed292b. let expected_signature = function.short_signature(); - let actual_signature = &unverified.data[0..4]; + let actual_signature = &unverified.unsigned().data()[0..4]; if actual_signature != expected_signature { return ERR!( "Expected 'receiverSpend' contract call signature: {:?}, found {:?}", @@ -1170,7 +1170,7 @@ impl SwapOps for EthCoin { ); }; - let tokens = try_s!(decode_contract_call(function, &unverified.data)); + let tokens = try_s!(decode_contract_call(function, unverified.unsigned().data())); if tokens.len() < 3 { return ERR!("Invalid arguments in 'receiverSpend' call: {:?}", tokens); } @@ -1341,7 +1341,7 @@ impl WatcherOps for EthCoin { _secret_hash: &[u8], _swap_unique_data: &[u8], ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(maker_payment_tx)); + let tx: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(maker_payment_tx)); let signed = try_tx_fus!(SignedEthTx::new(tx)); let fut = async move { Ok(TransactionEnum::from(signed)) }; @@ -1357,7 +1357,7 @@ impl WatcherOps for EthCoin { _swap_contract_address: &Option, _swap_unique_data: &[u8], ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(taker_payment_tx)); + let tx: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(taker_payment_tx)); let signed = try_tx_fus!(SignedEthTx::new(tx)); let fut = async move { Ok(TransactionEnum::from(signed)) }; @@ -1397,7 +1397,7 @@ impl WatcherOps for EthCoin { .try_to_address() .map_to_mm(ValidatePaymentError::InvalidParameter)); - let unsigned: UnverifiedTransaction = try_f!(rlp::decode(&input.payment_tx)); + let unsigned: UnverifiedTransactionWrapper = try_f!(rlp::decode(&input.payment_tx)); let tx = try_f!(SignedEthTx::new(unsigned) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); @@ -1419,9 +1419,9 @@ impl WatcherOps for EthCoin { let trade_amount = try_f!(wei_from_big_decimal(&(input.amount), decimals)); let fut = async move { - match tx.action { + match tx.unsigned().action() { Call(contract_address) => { - if contract_address != expected_swap_contract_address { + if *contract_address != expected_swap_contract_address { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Transaction {:?} was sent to wrong address, expected {:?}", contract_address, expected_swap_contract_address, @@ -1459,7 +1459,7 @@ impl WatcherOps for EthCoin { .function(&function_name) .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; - let decoded = decode_contract_call(function, &tx.data) + let decoded = decode_contract_call(function, tx.unsigned().data()) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; let swap_id_input = get_function_input_data(&decoded, function, 0) @@ -1555,10 +1555,10 @@ impl WatcherOps for EthCoin { ))); } - if tx.value != U256::zero() { + if tx.unsigned().value() != U256::zero() { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Transaction value arg {:?} is invalid, expected 0", - tx.value + tx.unsigned().value() ))); } @@ -1629,7 +1629,7 @@ impl WatcherOps for EthCoin { } fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { - let unsigned: UnverifiedTransaction = try_f!(rlp::decode(&input.payment_tx)); + let unsigned: UnverifiedTransactionWrapper = try_f!(rlp::decode(&input.payment_tx)); let tx = try_f!(SignedEthTx::new(unsigned) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); @@ -1651,7 +1651,7 @@ impl WatcherOps for EthCoin { let fallback_swap_contract = self.fallback_swap_contract; let fut = async move { - let tx_from_rpc = selfi.transaction(TransactionId::Hash(tx.hash)).await?; + let tx_from_rpc = selfi.transaction(TransactionId::Hash(tx.tx_hash())).await?; let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { ValidatePaymentError::TxDoesNotExist(format!("Didn't find provided tx {:?} on ETH node", tx)) @@ -1873,10 +1873,10 @@ impl WatcherOps for EthCoin { &self, input: WatcherSearchForSwapTxSpendInput<'_>, ) -> Result, String> { - let unverified: UnverifiedTransaction = try_s!(rlp::decode(input.tx)); + let unverified: UnverifiedTransactionWrapper = try_s!(rlp::decode(input.tx)); let tx = try_s!(SignedEthTx::new(unverified)); - let swap_contract_address = match tx.action { - Call(address) => address, + let swap_contract_address = match tx.unsigned().action() { + Call(address) => *address, Create => return Err(ERRL!("Invalid payment action: the payment action cannot be create")), }; @@ -2118,9 +2118,9 @@ impl MarketCoinOps for EthCoin { status.status(&[&self.ticker], "Waiting for confirmations…"); status.deadline(input.wait_until * 1000); - let unsigned: UnverifiedTransaction = try_fus!(rlp::decode(&input.payment_tx)); + let unsigned: UnverifiedTransactionWrapper = try_fus!(rlp::decode(&input.payment_tx)); let tx = try_fus!(SignedEthTx::new(unsigned)); - let tx_hash = tx.hash(); + let tx_hash = tx.tx_hash(); let required_confirms = U64::from(input.confirmations); let check_every = input.check_every as f64; @@ -2191,13 +2191,13 @@ impl MarketCoinOps for EthCoin { } fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let unverified: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.tx_bytes)); + let unverified: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(args.tx_bytes)); let tx = try_tx_fus!(SignedEthTx::new(unverified)); let swap_contract_address = match args.swap_contract_address { Some(addr) => try_tx_fus!(addr.try_to_address()), - None => match tx.action { - Call(address) => address, + None => match tx.unsigned().action() { + Call(address) => *address, Create => { return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( "Invalid payment action: the payment action cannot be create" @@ -2217,7 +2217,7 @@ impl MarketCoinOps for EthCoin { }; let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&func_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &tx.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, tx.unsigned().data())); let id = match decoded.first() { Some(Token::FixedBytes(bytes)) => bytes.clone(), invalid_token => { @@ -2334,7 +2334,7 @@ impl MarketCoinOps for EthCoin { } pub fn signed_eth_tx_from_bytes(bytes: &[u8]) -> Result { - let tx: UnverifiedTransaction = try_s!(rlp::decode(bytes)); + let tx: UnverifiedTransactionWrapper = try_s!(rlp::decode(bytes)); let signed = try_s!(SignedEthTx::new(tx)); Ok(signed) } @@ -2369,14 +2369,14 @@ async fn sign_transaction_with_keypair( status.status(tags!(), "get_gas_price…"); let gas_price = try_tx_s!(coin.get_gas_price().compat().await); - let tx = UnSignedEthTx { + let tx = UnSignedEthTx::Legacy(LegacyTransaction { nonce, gas_price, gas, action, value, data, - }; + }); Ok(( tx.sign(key_pair.secret(), coin.chain_id), @@ -2410,7 +2410,7 @@ async fn sign_and_send_transaction_with_keypair( try_tx_s!(select_ok(futures).await.map_err(|e| ERRL!("{}", e)), signed); status.status(tags!(), "get_addr_nonce…"); - coin.wait_for_addr_nonce_increase(coin.my_address, signed.transaction.unsigned.nonce) + coin.wait_for_addr_nonce_increase(coin.my_address, signed.unsigned().nonce()) .await; Ok(signed) } @@ -2965,7 +2965,7 @@ impl EthCoin { coin: self.ticker.clone(), fee_details: fee_details.map(|d| d.into()), block_height: trace.block_number, - tx_hash: format!("{:02x}", BytesJson(raw.hash.as_bytes().to_vec())), + tx_hash: format!("{:02x}", BytesJson(raw.tx_hash().as_bytes().to_vec())), tx_hex: BytesJson(rlp::encode(&raw).to_vec()), internal_id, timestamp: block.timestamp.into_or_max(), @@ -3329,7 +3329,7 @@ impl EthCoin { coin: self.ticker.clone(), fee_details: fee_details.map(|d| d.into()), block_height: block_number.as_u64(), - tx_hash: format!("{:02x}", BytesJson(raw.hash.as_bytes().to_vec())), + tx_hash: format!("{:02x}", BytesJson(raw.tx_hash().as_bytes().to_vec())), tx_hex: BytesJson(rlp::encode(&raw).to_vec()), internal_id: BytesJson(internal_id.to_vec()), timestamp: block.timestamp.into_or_max(), @@ -3543,7 +3543,7 @@ impl EthCoin { .map_err(move |e| { TransactionErr::Plain(ERRL!( "Allowed value was not updated in time after sending approve transaction {:02x}: {}", - approved.tx_hash(), + approved.tx_hash_as_bytes(), e )) }) @@ -3576,7 +3576,7 @@ impl EthCoin { } fn watcher_spends_hash_time_locked_payment(&self, input: SendMakerPaymentSpendPreimageInput) -> EthTxFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(input.preimage)); + let tx: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(input.preimage)); let payment = try_tx_fus!(SignedEthTx::new(tx)); let function_name = get_function_name("receiverSpend", input.watcher_reward); @@ -3584,8 +3584,8 @@ impl EthCoin { let clone = self.clone(); let secret_vec = input.secret.to_vec(); let taker_addr = addr_from_raw_pubkey(input.taker_pub).unwrap(); - let swap_contract_address = match payment.action { - Call(address) => address, + let swap_contract_address = match payment.unsigned().action() { + Call(address) => *address, Create => { return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( "Invalid payment action: the payment action cannot be create" @@ -3598,7 +3598,7 @@ impl EthCoin { EthCoinType::Eth => { let function_name = get_function_name("ethPayment", watcher_reward); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); @@ -3614,7 +3614,7 @@ impl EthCoin { )))); } - let value = payment.value; + let value = payment.unsigned().value(); let reward_target = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); let sends_contract_reward = try_tx_fus!(get_function_input_data(&decoded, payment_func, 5)); let watcher_reward_amount = try_tx_fus!(get_function_input_data(&decoded, payment_func, 6)); @@ -3647,7 +3647,7 @@ impl EthCoin { let function_name = get_function_name("erc20Payment", watcher_reward); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); @@ -3697,7 +3697,7 @@ impl EthCoin { } fn watcher_refunds_hash_time_locked_payment(&self, args: RefundPaymentArgs) -> EthTxFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.payment_tx)); + let tx: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(args.payment_tx)); let payment = try_tx_fus!(SignedEthTx::new(tx)); let function_name = get_function_name("senderRefund", true); @@ -3705,8 +3705,8 @@ impl EthCoin { let clone = self.clone(); let taker_addr = addr_from_raw_pubkey(args.other_pubkey).unwrap(); - let swap_contract_address = match payment.action { - Call(address) => address, + let swap_contract_address = match payment.unsigned().action() { + Call(address) => *address, Create => { return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( "Invalid payment action: the payment action cannot be create" @@ -3718,7 +3718,7 @@ impl EthCoin { EthCoinType::Eth => { let function_name = get_function_name("ethPayment", true); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let receiver_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); let hash_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 2)); @@ -3736,7 +3736,7 @@ impl EthCoin { )))); } - let value = payment.value; + let value = payment.unsigned().value(); let reward_target = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); let sends_contract_reward = try_tx_fus!(get_function_input_data(&decoded, payment_func, 5)); let reward_amount = try_tx_fus!(get_function_input_data(&decoded, payment_func, 6)); @@ -3769,7 +3769,7 @@ impl EthCoin { let function_name = get_function_name("erc20Payment", true); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); let receiver_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 3)); @@ -3822,7 +3822,7 @@ impl EthCoin { } fn spend_hash_time_locked_payment(&self, args: SpendPaymentArgs) -> EthTxFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.other_payment_tx)); + let tx: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(args.other_payment_tx)); let payment = try_tx_fus!(SignedEthTx::new(tx)); let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); @@ -3837,7 +3837,7 @@ impl EthCoin { EthCoinType::Eth => { let function_name = get_function_name("ethPayment", watcher_reward); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); Box::new( @@ -3855,7 +3855,7 @@ impl EthCoin { let data = if watcher_reward { try_tx_fus!(spend_func.encode_input(&[ decoded[0].clone(), - Token::Uint(payment.value), + Token::Uint(payment.unsigned().value()), Token::FixedBytes(secret_vec), Token::Address(Address::default()), Token::Address(payment.sender()), @@ -3867,7 +3867,7 @@ impl EthCoin { } else { try_tx_fus!(spend_func.encode_input(&[ decoded[0].clone(), - Token::Uint(payment.value), + Token::Uint(payment.unsigned().value()), Token::FixedBytes(secret_vec), Token::Address(Address::default()), Token::Address(payment.sender()), @@ -3890,7 +3890,7 @@ impl EthCoin { let function_name = get_function_name("erc20Payment", watcher_reward); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); Box::new( @@ -3944,7 +3944,7 @@ impl EthCoin { } fn refund_hash_time_locked_payment(&self, args: RefundPaymentArgs) -> EthTxFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.payment_tx)); + let tx: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(args.payment_tx)); let payment = try_tx_fus!(SignedEthTx::new(tx)); let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); @@ -3959,7 +3959,7 @@ impl EthCoin { let function_name = get_function_name("ethPayment", watcher_reward); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); Box::new( @@ -3974,7 +3974,7 @@ impl EthCoin { )))); } - let value = payment.value; + let value = payment.unsigned().value(); let data = if watcher_reward { try_tx_fus!(refund_func.encode_input(&[ decoded[0].clone(), @@ -4013,7 +4013,7 @@ impl EthCoin { let function_name = get_function_name("erc20Payment", watcher_reward); let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, payment.unsigned().data())); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); Box::new( state_f @@ -4378,7 +4378,7 @@ impl EthCoin { .try_to_address() .map_to_mm(ValidatePaymentError::InvalidParameter)); - let unsigned: UnverifiedTransaction = try_f!(rlp::decode(&input.payment_tx)); + let unsigned: UnverifiedTransactionWrapper = try_f!(rlp::decode(&input.payment_tx)); let tx = try_f!(SignedEthTx::new(unsigned) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); @@ -4410,9 +4410,9 @@ impl EthCoin { ))); } - let tx_from_rpc = selfi.transaction(TransactionId::Hash(tx.hash)).await?; + let tx_from_rpc = selfi.transaction(TransactionId::Hash(tx.tx_hash())).await?; let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { - ValidatePaymentError::TxDoesNotExist(format!("Didn't find provided tx {:?} on ETH node", tx.hash)) + ValidatePaymentError::TxDoesNotExist(format!("Didn't find provided tx {:?} on ETH node", tx.tx_hash())) })?; if tx_from_rpc.from != Some(sender) { @@ -4690,7 +4690,7 @@ impl EthCoin { search_from_block: u64, watcher_reward: bool, ) -> Result, String> { - let unverified: UnverifiedTransaction = try_s!(rlp::decode(tx)); + let unverified: UnverifiedTransactionWrapper = try_s!(rlp::decode(tx)); let tx = try_s!(SignedEthTx::new(unverified)); let func_name = match self.coin_type { @@ -4700,7 +4700,7 @@ impl EthCoin { }; let payment_func = try_s!(SWAP_CONTRACT.function(&func_name)); - let decoded = try_s!(decode_contract_call(payment_func, &tx.data)); + let decoded = try_s!(decode_contract_call(payment_func, tx.unsigned().data())); let id = match decoded.first() { Some(Token::FixedBytes(bytes)) => bytes.clone(), invalid_token => return ERR!("Expected Token::FixedBytes, got {:?}", invalid_token), @@ -5606,7 +5606,7 @@ pub fn wei_from_big_decimal(amount: &BigDecimal, decimals: u8) -> NumConversResu impl Transaction for SignedEthTx { fn tx_hex(&self) -> Vec { rlp::encode(self).to_vec() } - fn tx_hash(&self) -> BytesJson { self.hash.0.to_vec().into() } + fn tx_hash_as_bytes(&self) -> BytesJson { self.tx_hash().0.to_vec().into() } } fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { @@ -5620,12 +5620,12 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result Result Action::Create, }, }, - }; + }); Ok(try_s!(SignedEthTx::new(unverified))) } diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 7b3e378fea..a99dd5f01f 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1052,7 +1052,7 @@ fn polygon_check_if_my_payment_sent() { .unwrap() .unwrap(); let expected_hash = BytesJson::from("69a20008cea0c15ee483b5bbdff942752634aa072dfd2ff715fe87eec302de11"); - assert_eq!(expected_hash, my_payment.tx_hash()); + assert_eq!(expected_hash, my_payment.tx_hash_as_bytes()); } #[test] diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 4698a595ac..a6294fe743 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -152,7 +152,7 @@ pub(crate) struct GetOpenChannelsResult { impl Transaction for PaymentHash { fn tx_hex(&self) -> Vec { self.0.to_vec() } - fn tx_hash(&self) -> BytesJson { self.0.to_vec().into() } + fn tx_hash_as_bytes(&self) -> BytesJson { self.0.to_vec().into() } } impl LightningCoin { diff --git a/mm2src/coins/lightning/ln_platform.rs b/mm2src/coins/lightning/ln_platform.rs index 51f683b313..4d0573af95 100644 --- a/mm2src/coins/lightning/ln_platform.rs +++ b/mm2src/coins/lightning/ln_platform.rs @@ -546,7 +546,7 @@ impl Platform { .await .map_to_mm(|e| SaveChannelClosingError::WaitForFundingTxSpendError(e.get_plain_text_format()))?; - let closing_tx_hash = format!("{:02x}", closing_tx.tx_hash()); + let closing_tx_hash = format!("{:02x}", closing_tx.tx_hash_as_bytes()); Ok(closing_tx_hash) } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 5179a5db80..dda3ea46b1 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -581,7 +581,7 @@ pub trait Transaction: fmt::Debug + 'static { /// Raw transaction bytes of the transaction fn tx_hex(&self) -> Vec; /// Serializable representation of tx hash for displaying purpose - fn tx_hash(&self) -> BytesJson; + fn tx_hash_as_bytes(&self) -> BytesJson; } #[derive(Clone, Debug, PartialEq)] diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs index 97c5a5ca8f..adab4de9b5 100644 --- a/mm2src/coins/my_tx_history_v2.rs +++ b/mm2src/coins/my_tx_history_v2.rs @@ -214,7 +214,7 @@ impl<'a, Addr: Clone + DisplayAddress + Eq + std::hash::Hash, Tx: Transaction> T let mut to: Vec<_> = self.to_addresses.iter().map(DisplayAddress::display_address).collect(); to.sort(); - let tx_hash = self.tx.tx_hash(); + let tx_hash = self.tx.tx_hash_as_bytes(); let internal_id = match &self.transaction_type { TransactionType::TokenTransfer(token_id) => { let mut bytes_for_hash = tx_hash.0.clone(); diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index dbbb609e82..4f450acd0e 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -355,7 +355,7 @@ pub struct CosmosTransaction { impl crate::Transaction for CosmosTransaction { fn tx_hex(&self) -> Vec { self.data.encode_to_vec() } - fn tx_hash(&self) -> BytesJson { + fn tx_hash_as_bytes(&self) -> BytesJson { let bytes = self.data.encode_to_vec(); let hash = sha256(&bytes); hash.to_vec().into() @@ -3157,7 +3157,7 @@ pub mod tendermint_coin_tests { // https://nyancat.iobscan.io/#/tx?txHash=565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137 let expected_spend_hash = "565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137"; - let hash = spend_tx.tx_hash(); + let hash = spend_tx.tx_hash_as_bytes(); assert_eq!(hex::encode_upper(hash.0), expected_spend_hash); } @@ -3505,7 +3505,7 @@ pub mod tendermint_coin_tests { // https://nyancat.iobscan.io/#/tx?txHash=565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137 let expected_spend_hash = "565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137"; - let hash = spend_tx.tx_hash(); + let hash = spend_tx.tx_hash_as_bytes(); assert_eq!(hex::encode_upper(hash.0), expected_spend_hash); } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 5ac52ef31d..c3771cf237 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -410,7 +410,7 @@ pub struct TestTx {} impl Transaction for TestTx { fn tx_hex(&self) -> Vec { todo!() } - fn tx_hash(&self) -> BytesJson { todo!() } + fn tx_hash_as_bytes(&self) -> BytesJson { todo!() } } pub struct TestPreimage {} diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 1bba5184c2..8fb5c34a0a 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -186,7 +186,7 @@ impl Transaction for UtxoTx { } } - fn tx_hash(&self) -> BytesJson { self.hash().reversed().to_vec().into() } + fn tx_hash_as_bytes(&self) -> BytesJson { self.hash().reversed().to_vec().into() } } impl From for BalanceError { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 88ff31c2d8..03a4421297 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -4671,7 +4671,7 @@ pub async fn validate_payment<'a, T: UtxoCommonOps>( let amount = sat_from_big_decimal(&amount, coin.as_ref().decimals)?; let expected_redeem = tx_type_with_secret_hash.redeem_script(time_lock, first_pub0, second_pub0); - let tx_hash = tx.tx_hash(); + let tx_hash = tx.tx_hash_as_bytes(); let tx_from_rpc = retry_on_err!(async { coin.as_ref() diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 3ec97cd558..3d25528b29 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -220,7 +220,7 @@ impl Transaction for ZTransaction { hex } - fn tx_hash(&self) -> BytesJson { + fn tx_hash_as_bytes(&self) -> BytesJson { let mut bytes = self.txid().0.to_vec(); bytes.reverse(); bytes.into() diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index b2a52c3bf1..fec179a88f 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -56,7 +56,7 @@ fn zombie_coin_send_and_refund_maker_payment() { wait_for_confirmation_until: 0, }; let tx = coin.send_maker_payment(args).wait().unwrap(); - println!("swap tx {}", hex::encode(tx.tx_hash().0)); + println!("swap tx {}", hex::encode(tx.tx_hash_as_bytes().0)); let refund_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), @@ -70,7 +70,7 @@ fn zombie_coin_send_and_refund_maker_payment() { watcher_reward: false, }; let refund_tx = block_on(coin.send_maker_refunds_payment(refund_args)).unwrap(); - println!("refund tx {}", hex::encode(refund_tx.tx_hash().0)); + println!("refund tx {}", hex::encode(refund_tx.tx_hash_as_bytes().0)); } #[test] @@ -117,7 +117,7 @@ fn zombie_coin_send_and_spend_maker_payment() { }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); - println!("swap tx {}", hex::encode(tx.tx_hash().0)); + println!("swap tx {}", hex::encode(tx.tx_hash_as_bytes().0)); let maker_pub = taker_pub; @@ -135,7 +135,7 @@ fn zombie_coin_send_and_spend_maker_payment() { .send_taker_spends_maker_payment(spends_payment_args) .wait() .unwrap(); - println!("spend tx {}", hex::encode(spend_tx.tx_hash().0)); + println!("spend tx {}", hex::encode(spend_tx.tx_hash_as_bytes().0)); } #[test] diff --git a/mm2src/mm2_eth/Cargo.toml b/mm2src/mm2_eth/Cargo.toml index 367b4162df..5343b90f6a 100644 --- a/mm2src/mm2_eth/Cargo.toml +++ b/mm2src/mm2_eth/Cargo.toml @@ -8,7 +8,7 @@ doctest = false [dependencies] ethabi = { version = "17.0.0" } -ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } +ethkey = { git = "https://github.com/dimxy/mm2-parity-ethereum.git", branch = "eip1559-support" } hex = "0.4.2" indexmap = "1.7.0" itertools = "0.10" diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index c4b7a405a0..91f13d9638 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1527,7 +1527,7 @@ pub async fn recover_funds_of_swap(ctx: MmArc, req: Json) -> Result { return ERR!( "Taker payment was already refunded by {} tx {:02x}", selfi.taker_coin.ticker(), - tx.tx_hash() + tx.tx_hash_as_bytes() ) }, Err(e) => return ERR!("Error {} when trying to find taker payment spend", e), @@ -1506,7 +1506,7 @@ impl MakerSwap { Ok(Some(FoundSwapTxSpend::Refunded(tx))) => ERR!( "Maker payment was already refunded by {} tx {:02x}", self.maker_coin.ticker(), - tx.tx_hash() + tx.tx_hash_as_bytes() ), Err(e) => ERR!("Error {} when trying to find maker payment spend", e), Ok(None) => { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 77ce092be1..8a8bc44d0f 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -1210,7 +1210,7 @@ impl maker_payment_spend_tx, - Ok(Some(FoundSwapTxSpend::Refunded(maker_payment_refund_tx))) => return ERR!("Maker has cheated by both spending the taker payment, and refunding the maker payment with transaction {:#?}", maker_payment_refund_tx.tx_hash()), + Ok(Some(FoundSwapTxSpend::Refunded(maker_payment_refund_tx))) => return ERR!("Maker has cheated by both spending the taker payment, and refunding the maker payment with transaction {:#?}", maker_payment_refund_tx.tx_hash_as_bytes()), Ok(None) => return Ok(TakerSwapCommand::SpendMakerPayment), Err(e) => return ERR!("Error {} when trying to find maker payment spend", e) }; @@ -124,7 +124,7 @@ pub async fn check_maker_payment_spend_and_add_event( .await .map_err(|e| e.to_string())?; - let tx_hash = maker_payment_spend_tx.tx_hash(); + let tx_hash = maker_payment_spend_tx.tx_hash_as_bytes(); info!("Watcher maker payment spend tx {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: Bytes::from(maker_payment_spend_tx.tx_hex()), @@ -183,7 +183,7 @@ pub async fn add_taker_payment_spent_event( let secret_hash = swap.r().secret_hash.0.clone(); let watcher_reward = swap.r().watcher_reward; - let tx_hash = taker_payment_spend_tx.tx_hash(); + let tx_hash = taker_payment_spend_tx.tx_hash_as_bytes(); info!("Taker payment spend tx {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: Bytes::from(taker_payment_spend_tx.tx_hex()), @@ -243,7 +243,7 @@ pub async fn add_taker_payment_refunded_by_watcher_event( .await .map_err(|e| e.to_string())?; - let tx_hash = taker_payment_refund_tx.tx_hash(); + let tx_hash = taker_payment_refund_tx.tx_hash_as_bytes(); info!("Taker refund tx hash {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: Bytes::from(taker_payment_refund_tx.tx_hex()), diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index e4acf1b968..d64fa7ab50 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -1287,7 +1287,7 @@ impl TakerSwap { }, }; - let tx_hash = transaction.tx_hash(); + let tx_hash = transaction.tx_hash_as_bytes(); info!("Taker fee tx hash {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: BytesJson::from(transaction.tx_hex()), @@ -1371,7 +1371,7 @@ impl TakerSwap { }, }; - let tx_hash = maker_payment.tx_hash(); + let tx_hash = maker_payment.tx_hash_as_bytes(); info!("Got maker payment {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: BytesJson::from(maker_payment.tx_hex()), @@ -1571,7 +1571,7 @@ impl TakerSwap { }, }; - let tx_hash = transaction.tx_hash(); + let tx_hash = transaction.tx_hash_as_bytes(); let tx_hex = BytesJson::from(transaction.tx_hex()); info!("Taker payment tx hash {:02x}", tx_hash); let tx_ident = TransactionIdentifier { @@ -1612,7 +1612,7 @@ impl TakerSwap { match payment_fut_pair.await { Ok((maker_payment_spend, taker_payment_refund)) => { let watcher_data = self.create_watcher_data( - transaction.tx_hash().into_vec(), + transaction.tx_hash_as_bytes().into_vec(), maker_payment_spend.tx_hex(), taker_payment_refund.tx_hex(), ); @@ -1740,7 +1740,7 @@ impl TakerSwap { }; drop(send_abort_handle); drop(watcher_broadcast_abort_handle); - let tx_hash = tx.tx_hash(); + let tx_hash = tx.tx_hash_as_bytes(); info!("Taker payment spend tx {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: BytesJson::from(tx.tx_hex()), @@ -1815,7 +1815,7 @@ impl TakerSwap { &self.p2p_privkey, ); - let tx_hash = transaction.tx_hash(); + let tx_hash = transaction.tx_hash_as_bytes(); info!("Maker payment spend tx {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: BytesJson::from(transaction.tx_hex()), @@ -1920,7 +1920,7 @@ impl TakerSwap { &self.p2p_privkey, ); - let tx_hash = transaction.tx_hash(); + let tx_hash = transaction.tx_hash_as_bytes(); info!("Taker refund tx hash {:02x}", tx_hash); let tx_ident = TransactionIdentifier { tx_hex: BytesJson::from(transaction.tx_hex()), @@ -2091,14 +2091,14 @@ impl TakerSwap { return ERR!( "Maker payment was already spent by {} tx {:02x}", self.maker_coin.ticker(), - tx.tx_hash() + tx.tx_hash_as_bytes() ) }, Ok(Some(FoundSwapTxSpend::Refunded(tx))) => { return ERR!( "Maker payment was already refunded by {} tx {:02x}", self.maker_coin.ticker(), - tx.tx_hash() + tx.tx_hash_as_bytes() ) }, Err(e) => return ERR!("Error {} when trying to find maker payment spend", e), @@ -2236,7 +2236,7 @@ impl TakerSwap { }, FoundSwapTxSpend::Refunded(tx) => ERR!( "Taker payment has been refunded already by transaction {:02x}", - tx.tx_hash() + tx.tx_hash_as_bytes() ), }, None => { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index ec5c88c0c5..adc7e00615 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -1240,7 +1240,7 @@ impl Date: Thu, 7 Mar 2024 20:34:13 +0500 Subject: [PATCH 19/71] add eip1559 fee support to eth withdraw (still use test eth tx repo) --- Cargo.lock | 8 +- mm2src/coins/eth.rs | 233 +++++++++++++----- mm2src/coins/eth/eth_tests.rs | 4 + mm2src/coins/lp_coins.rs | 13 +- mm2src/coins/nft.rs | 16 +- .../tests/docker_tests/swap_proto_v2_tests.rs | 5 +- 6 files changed, 211 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fa788d049..dec5226491 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,7 +2174,7 @@ dependencies = [ [[package]] name = "ethcore-transaction" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" dependencies = [ "ethereum-types", "ethkey", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" dependencies = [ "byteorder", "edit-distance", @@ -4109,7 +4109,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" [[package]] name = "memchr" @@ -8647,7 +8647,7 @@ dependencies = [ [[package]] name = "unexpected" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#93c99205210e5b661c953d92a253a5566545e039" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" [[package]] name = "unicode-bidi" diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9d040b1506..b8ef8deefe 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -44,7 +44,8 @@ use derive_more::Display; use enum_derives::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; -use ethcore_transaction::{Action, LegacyTransaction, TransactionWrapper as UnSignedEthTx, UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; +use ethcore_transaction::{Action, LegacyTransaction, TransactionWrapperBuilder as UnSignedEthTxBuilder, + UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; use ethereum_types::{Address, H160, H256, U256}; use ethkey::{public_to_address, KeyPair, Public, Signature}; use ethkey::{sign, verify_address}; @@ -186,7 +187,22 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; type EthPrivKeyPolicy = PrivKeyPolicy; -type GasDetails = (U256, U256); + +pub(crate) struct LegacyGasPrice { + pub gas_price: U256, +} + +pub(crate) struct Eip1559FeePerGas { + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, +} + +pub(crate) enum PayForGasOption { + Legacy(LegacyGasPrice), + Eip1559(Eip1559FeePerGas), +} + +type GasDetails = (U256, PayForGasOption); #[derive(Debug, Display)] pub enum Web3RpcError { @@ -655,9 +671,10 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }; let eth_value_dec = u256_to_big_decimal(eth_value, coin.decimals)?; - let (gas, gas_price) = - get_eth_gas_details(&coin, req.fee, eth_value, data.clone().into(), call_addr, req.max).await?; - let total_fee = gas * gas_price; + let (gas, pay_for_gas_option) = + get_eth_gas_details_from_withdraw_fee(&coin, req.fee, eth_value, data.clone().into(), call_addr, req.max) + .await?; + let total_fee = calc_total_fee(gas, &pay_for_gas_option)?; let total_fee_dec = u256_to_big_decimal(total_fee, coin.decimals)?; if req.max && coin.coin_type == EthCoinType::Eth { @@ -683,15 +700,24 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { .await? .map_to_mm(WithdrawError::Transport)?; - let tx = UnSignedEthTx::Legacy(LegacyTransaction { - nonce, - value: eth_value, - action: Action::Call(call_addr), - data, - gas, - gas_price, - }); - + let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); + let tx_builder = match pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + max_fee_per_gas, + }) => { + let chain_id = coin.chain_id.or_mm_err(|| WithdrawError::NoChainIdSet { + coin: coin.ticker().to_owned(), + })?; + tx_builder + .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) + .with_chain_id(chain_id) + }, + }; + let tx = tx_builder + .build() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let signed = tx.sign(key_pair.secret(), coin.chain_id); let bytes = rlp::encode(&signed); @@ -713,7 +739,21 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { from: coin.my_address, to: Some(to_addr), gas: Some(gas), - gas_price: Some(gas_price), + gas_price: if let PayForGasOption::Legacy(ref legacy_gas_price) = pay_for_gas_option { + Some(legacy_gas_price.gas_price) + } else { + None + }, + max_priority_fee_per_gas: if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { + Some(fee_per_gas.max_priority_fee_per_gas) + } else { + None + }, + max_fee_per_gas: if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { + Some(fee_per_gas.max_fee_per_gas) + } else { + None + }, value: Some(eth_value), data: Some(data.clone().into()), nonce: None, @@ -749,7 +789,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { } else { 0.into() }; - let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + let fee_details = EthTxFeeDetails::new(gas, pay_for_gas_option, fee_coin)?; if coin.coin_type == EthCoinType::Eth { spent_by_me += &fee_details.total_fee; } @@ -822,7 +862,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit }, EthCoinType::Nft { .. } => return MmError::err(WithdrawError::NftProtocolNotSupported), }; - let (gas, gas_price) = get_eth_gas_details( + let (gas, pay_for_gas_option) = get_eth_gas_details_from_withdraw_fee( ð_coin, withdraw_type.fee, eth_value, @@ -840,19 +880,28 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .await? .map_to_mm(WithdrawError::Transport)?; - let tx = UnSignedEthTx::Legacy(LegacyTransaction { - nonce, - value: eth_value, - action: Action::Call(call_addr), - data, - gas, - gas_price, - }); - + let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); + let tx_builder = match pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + max_fee_per_gas, + }) => { + let chain_id = eth_coin.chain_id.or_mm_err(|| WithdrawError::NoChainIdSet { + coin: eth_coin.ticker().to_owned(), + })?; + tx_builder + .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) + .with_chain_id(chain_id) + }, + }; + let tx = tx_builder + .build() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); let signed_bytes = rlp::encode(&signed); - let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + let fee_details = EthTxFeeDetails::new(gas, pay_for_gas_option, fee_coin)?; Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), @@ -910,7 +959,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd // TODO: start to use NFT GLOBAL TOKEN for withdraw EthCoinType::Nft { .. } => return MmError::err(WithdrawError::NftProtocolNotSupported), }; - let (gas, gas_price) = get_eth_gas_details( + let (gas, pay_for_gas_option) = get_eth_gas_details_from_withdraw_fee( ð_coin, withdraw_type.fee, eth_value, @@ -928,19 +977,28 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .await? .map_to_mm(WithdrawError::Transport)?; - let tx = UnSignedEthTx::Legacy(LegacyTransaction { - nonce, - value: eth_value, - action: Action::Call(call_addr), - data, - gas, - gas_price, - }); - + let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); + let tx_builder = match pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + max_fee_per_gas, + }) => { + let chain_id = eth_coin.chain_id.or_mm_err(|| WithdrawError::NoChainIdSet { + coin: eth_coin.ticker().to_owned(), + })?; + tx_builder + .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) + .with_chain_id(chain_id) + }, + }; + let tx = tx_builder + .build() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); let signed_bytes = rlp::encode(&signed); - let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + let fee_details = EthTxFeeDetails::new(gas, pay_for_gas_option, fee_coin)?; Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), @@ -2369,14 +2427,8 @@ async fn sign_transaction_with_keypair( status.status(tags!(), "get_gas_price…"); let gas_price = try_tx_s!(coin.get_gas_price().compat().await); - let tx = UnSignedEthTx::Legacy(LegacyTransaction { - nonce, - gas_price, - gas, - action, - value, - data, - }); + let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, action, value, data).with_gas_price(gas_price); // TODO: add support for eip1559 params (f.e.: create a GasParams struct with gas_limit and PayForGasOption members and use it as a param instead of 'gas' param) + let tx = tx_builder.build().map_err(|e| TransactionErr::Plain(e.to_string()))?; Ok(( tx.sign(key_pair.secret(), coin.chain_id), @@ -2909,11 +2961,18 @@ impl EthCoin { let fee_details: Option = match receipt { Some(r) => { let gas_used = r.gas_used.unwrap_or_default(); - let gas_price = web3_tx.gas_price.unwrap_or_default(); - // It's relatively safe to unwrap `EthTxFeeDetails::new` as it may fail - // due to `u256_to_big_decimal` only. - // Also TX history is not used by any GUI and has significant disadvantages. - Some(EthTxFeeDetails::new(gas_used, gas_price, fee_coin).unwrap()) + let gas_price = web3_tx.gas_price.unwrap_or_default(); // TODO: create and use EthTxFeeDetails::from(web3_tx) + // It's relatively safe to unwrap `EthTxFeeDetails::new` as it may fail + // due to `u256_to_big_decimal` only. + // Also TX history is not used by any GUI and has significant disadvantages. + Some( + EthTxFeeDetails::new( + gas_used, + PayForGasOption::Legacy(LegacyGasPrice { gas_price }), + fee_coin, + ) + .unwrap(), + ) }, None => None, }; @@ -3293,7 +3352,14 @@ impl EthCoin { // It's relatively safe to unwrap `EthTxFeeDetails::new` as it may fail // due to `u256_to_big_decimal` only. // Also TX history is not used by any GUI and has significant disadvantages. - Some(EthTxFeeDetails::new(gas_used, gas_price, fee_coin).unwrap()) + Some( + EthTxFeeDetails::new( + gas_used, + PayForGasOption::Legacy(LegacyGasPrice { gas_price }), + fee_coin, + ) + .unwrap(), + ) }, None => None, }; @@ -5080,22 +5146,44 @@ pub struct EthTxFeeDetails { pub gas: u64, /// WEI units per 1 gas pub gas_price: BigDecimal, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, pub total_fee: BigDecimal, } impl EthTxFeeDetails { - pub(crate) fn new(gas: U256, gas_price: U256, coin: &str) -> NumConversResult { - let total_fee = gas * gas_price; + pub(crate) fn new(gas: U256, pay_for_gas_option: PayForGasOption, coin: &str) -> NumConversResult { + let total_fee = calc_total_fee(gas, &pay_for_gas_option)?; // Fees are always paid in ETH, can use 18 decimals by default let total_fee = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; + let (gas_price, max_fee_per_gas, max_priority_fee_per_gas) = match pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => (gas_price, None, None), + // Using max_fee_per_gas as estimated gas_price value for compatibility in caller not expecting eip1559 fee per gas values. + // Normally the caller should pay attention to presence of max_fee_per_gas and max_priority_fee_per_gas in the result: + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas, + max_priority_fee_per_gas, + }) => (max_fee_per_gas, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)), + }; let gas_price = u256_to_big_decimal(gas_price, ETH_DECIMALS)?; - + let max_fee_per_gas = if let Some(max_fee_per_gas) = max_fee_per_gas { + Some(u256_to_big_decimal(max_fee_per_gas, ETH_DECIMALS)?) + } else { + None + }; + let max_priority_fee_per_gas = if let Some(max_priority_fee_per_gas) = max_priority_fee_per_gas { + Some(u256_to_big_decimal(max_priority_fee_per_gas, ETH_DECIMALS)?) + } else { + None + }; let gas_u64 = u64::try_from(gas).map_to_mm(|e| NumConversError::new(e.to_string()))?; Ok(EthTxFeeDetails { coin: coin.to_owned(), gas: gas_u64, gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, total_fee, }) } @@ -6073,7 +6161,7 @@ impl From for EthGasDetailsErr { } } -async fn get_eth_gas_details( +async fn get_eth_gas_details_from_withdraw_fee( eth_coin: &EthCoin, fee: Option, eth_value: U256, @@ -6083,8 +6171,23 @@ async fn get_eth_gas_details( ) -> MmResult { match fee { Some(WithdrawFee::EthGas { gas_price, gas }) => { - let gas_price = wei_from_big_decimal(&gas_price, 9)?; - Ok((gas.into(), gas_price)) + let gas_price = wei_from_big_decimal(&gas_price, ETH_GWEI_DECIMALS)?; + Ok((gas.into(), PayForGasOption::Legacy(LegacyGasPrice { gas_price }))) + }, + Some(WithdrawFee::EthGasEip1559 { + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + }) => { + let max_fee_per_gas = wei_from_big_decimal(&max_fee_per_gas, ETH_GWEI_DECIMALS)?; + let max_priority_fee_per_gas = wei_from_big_decimal(&max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?; + Ok(( + gas.into(), + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas, + max_priority_fee_per_gas, + }), + )) }, Some(fee_policy) => { let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); @@ -6112,7 +6215,19 @@ async fn get_eth_gas_details( // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. // TODO Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. let gas_limit = eth_coin.estimate_gas_wrapper(estimate_gas_req).compat().await?; - Ok((gas_limit, gas_price)) + Ok((gas_limit, PayForGasOption::Legacy(LegacyGasPrice { gas_price }))) }, } } + +/// Calc estimated total gas fee +fn calc_total_fee(gas: U256, pay_for_gas_option: &PayForGasOption) -> NumConversResult { + match *pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => gas + .checked_mul(gas_price) + .or_mm_err(|| NumConversError("total fee overflow".into())), + PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) => gas + .checked_mul(max_fee_per_gas) + .or_mm_err(|| NumConversError("total fee overflow".into())), + } +} diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index a99dd5f01f..9dfa910ba0 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -478,6 +478,8 @@ fn test_withdraw_impl_manual_fee() { gas_price: "0.000000001".parse().unwrap(), gas: ETH_GAS, total_fee: "0.00015".parse().unwrap(), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, } .into(), ); @@ -523,6 +525,8 @@ fn test_withdraw_impl_fee_details() { gas_price: "0.000000001".parse().unwrap(), gas: ETH_GAS, total_fee: "0.00015".parse().unwrap(), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, } .into(), ); diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index dda3ea46b1..88a0dcae70 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1822,6 +1822,12 @@ pub enum WithdrawFee { gas_price: BigDecimal, gas: u64, }, + EthGasEip1559 { + /// in gwei + max_priority_fee_per_gas: BigDecimal, + max_fee_per_gas: BigDecimal, + gas: u64, + }, Qrc20Gas { /// in satoshi gas_limit: u64, @@ -2695,6 +2701,10 @@ pub enum WithdrawError { }, #[display(fmt = "Nft Protocol is not supported yet!")] NftProtocolNotSupported, + #[display(fmt = "Chain id must be set for typed transaction for coin {}", coin)] + NoChainIdSet { + coin: String, + }, } impl HttpStatusCode for WithdrawError { @@ -2720,7 +2730,8 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) | WithdrawError::CoinDoesntSupportNftWithdraw { .. } | WithdrawError::NotEnoughNftsAmount { .. } - | WithdrawError::MyAddressNotNftOwner { .. } => StatusCode::BAD_REQUEST, + | WithdrawError::MyAddressNotNftOwner { .. } + | WithdrawError::NoChainIdSet { .. } => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST, diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 8e73e2b272..a993547de1 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -16,7 +16,7 @@ use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftLis TransactionNftDetails, UpdateNftReq, WithdrawNftReq}; use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721, EthCoin, EthCoinType, - EthTxFeeDetails}; + EthTxFeeDetails, LegacyGasPrice, PayForGasOption}; use crate::nft::nft_errors::{ClearNftDbError, MetaFromUrlError, ProtectFromSpamError, TransferConfirmationsError, UpdateSpamPhishingError}; use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, ClearNftDbReq, NftCommon, NftCtx, NftInfo, @@ -821,7 +821,12 @@ async fn get_fee_details(eth_coin: &EthCoin, transaction_hash: &str) -> Option { let gas_used = r.gas_used.unwrap_or_default(); match r.effective_gas_price { - Some(gas_price) => EthTxFeeDetails::new(gas_used, gas_price, fee_coin).ok(), + Some(gas_price) => EthTxFeeDetails::new( + gas_used, + PayForGasOption::Legacy(LegacyGasPrice { gas_price }), + fee_coin, + ) + .ok(), None => { let web3_tx = eth_coin .web3() @@ -832,7 +837,12 @@ async fn get_fee_details(eth_coin: &EthCoin, transaction_hash: &str) -> Option Date: Tue, 12 Mar 2024 19:54:49 +0500 Subject: [PATCH 20/71] add swap txfee policy --- mm2src/coins/eth.rs | 30 ++++++++----- mm2src/coins/eth/eth_tests.rs | 6 +++ mm2src/coins/eth/eth_wasm_tests.rs | 1 + mm2src/coins/eth/v2_activation.rs | 3 ++ mm2src/coins/lightning.rs | 8 +++- mm2src/coins/lp_coins.rs | 43 +++++++++++++++++++ mm2src/coins/qrc20.rs | 20 +++++---- mm2src/coins/solana.rs | 18 +++++--- mm2src/coins/solana/spl.rs | 10 +++-- mm2src/coins/tendermint/tendermint_coin.rs | 6 ++- mm2src/coins/tendermint/tendermint_token.rs | 20 +++++---- mm2src/coins/test_coin.rs | 10 +++-- mm2src/coins/utxo/bch.rs | 6 ++- mm2src/coins/utxo/qtum.rs | 14 +++--- mm2src/coins/utxo/slp.rs | 10 +++-- mm2src/coins/utxo/utxo_standard.rs | 14 +++--- mm2src/coins/z_coin.rs | 19 +++++--- .../src/rpc/dispatcher/dispatcher_legacy.rs | 5 ++- 18 files changed, 177 insertions(+), 66 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b8ef8deefe..f068cd8285 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -98,15 +98,15 @@ use super::{coin_conf, lp_coinfind_or_err, AsyncMutex, BalanceError, BalanceFut, RefundResult, RewardTarget, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignEthTransactionParams, SignRawTransactionEnum, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, - SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - TradePreimageValue, Transaction, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, - TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, 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}; + SwapOps, SwapTxFeePolicy, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, + TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, TransactionEnum, TransactionErr, + TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, + ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, 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}; pub use rlp; mod eth_balance_events; @@ -436,6 +436,7 @@ pub struct EthCoinImpl { decimals: u8, history_sync_state: Mutex, required_confirmations: AtomicU64, + swap_txfee_policy: Mutex, /// Coin needs access to the context in order to reuse the logging and shutdown facilities. /// Using a weak reference by default in order to avoid circular references and leaks. pub ctx: MmWeak, @@ -5145,8 +5146,8 @@ pub struct EthTxFeeDetails { pub coin: String, pub gas: u64, /// WEI units per 1 gas - pub gas_price: BigDecimal, - pub max_fee_per_gas: Option, + pub gas_price: BigDecimal, // if fee per gas is used we set gas_price as max_fee_per_gas for compatibility with GUI + pub max_fee_per_gas: Option, // pub max_priority_fee_per_gas: Option, pub total_fee: BigDecimal, } @@ -5451,6 +5452,12 @@ impl MmCoin for EthCoin { tokens.remove(ticker); }; } + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { self.swap_txfee_policy.lock().unwrap().clone() } + + fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy) { + *self.swap_txfee_policy.lock().unwrap() = swap_txfee_policy + } } pub trait TryToAddress { @@ -5977,6 +5984,7 @@ pub async fn eth_coin_from_conf_and_request( ticker: ticker.into(), web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(initial_history_state), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations, chain_id: conf["chain_id"].as_u64(), diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 9dfa910ba0..e8f2680486 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -150,6 +150,7 @@ fn eth_coin_from_keypair( web3_instances: AsyncMutex::new(web3_instances), ctx: ctx.weak(), required_confirmations: 1.into(), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), chain_id: None, logs_block_range: DEFAULT_LOGS_BLOCK_RANGE, nonce_lock: new_nonce_lock(), @@ -356,6 +357,7 @@ fn test_nonce_several_urls() { ]), decimals: 18, history_sync_state: Mutex::new(HistorySyncState::NotStarted), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations: 1.into(), chain_id: None, @@ -405,6 +407,7 @@ fn test_wait_for_payment_spend_timeout() { contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations: 1.into(), chain_id: None, @@ -1085,6 +1088,7 @@ fn test_message_hash() { web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), decimals: 18, history_sync_state: Mutex::new(HistorySyncState::NotStarted), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations: 1.into(), chain_id: None, @@ -1130,6 +1134,7 @@ fn test_sign_verify_message() { web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), decimals: 18, history_sync_state: Mutex::new(HistorySyncState::NotStarted), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations: 1.into(), chain_id: None, @@ -1184,6 +1189,7 @@ fn test_eth_extract_secret() { contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: true }]), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations: 1.into(), chain_id: None, diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index d8d7df851d..97a2879b5e 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -40,6 +40,7 @@ async fn test_send() { web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), decimals: 18, history_sync_state: Mutex::new(HistorySyncState::NotStarted), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations: 1.into(), chain_id: None, diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index f6d391be58..f9adfc7817 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -300,6 +300,7 @@ impl EthCoin { ticker, web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: self.ctx.clone(), required_confirmations, chain_id: self.chain_id, @@ -345,6 +346,7 @@ impl EthCoin { web3_instances: self.web3_instances.lock().await.clone().into(), decimals: self.decimals, history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), required_confirmations: AtomicU64::new(self.required_confirmations.load(Ordering::Relaxed)), ctx: self.ctx.clone(), chain_id: self.chain_id, @@ -450,6 +452,7 @@ pub async fn eth_coin_from_conf_and_request_v2( ticker, web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(HistorySyncState::NotEnabled), + swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), ctx: ctx.weak(), required_confirmations, chain_id, diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index a6294fe743..985dd3ef31 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -21,8 +21,8 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, - TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, - TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxMarshalingErr, + SwapTxFeePolicy, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + Transaction, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, @@ -1465,4 +1465,8 @@ impl MmCoin for LightningCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.platform.abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 88a0dcae70..2b513c8038 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -2225,6 +2225,26 @@ pub enum TradePreimageValue { UpperBound(BigDecimal), } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum SwapTxFeePolicy { + Unsupported, + Internal, + Low, + Medium, + High, +} + +impl Default for SwapTxFeePolicy { + fn default() -> Self { SwapTxFeePolicy::Unsupported } +} + +#[derive(Debug, Deserialize)] +pub struct SetSwapTxFeePolicyReq { + coin: String, + #[serde(default)] + swap_tx_fee_policy: SwapTxFeePolicy, +} + #[derive(Debug, Display, PartialEq)] pub enum TradePreimageError { #[display( @@ -3081,6 +3101,12 @@ pub trait MmCoin: /// For Handling the removal/deactivation of token on platform coin deactivation. fn on_token_deactivated(&self, ticker: &str); + + /// Return swap transaction fee policy + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy; + + /// set swap transaction fee policy + fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy); } /// The coin futures spawner. It's used to spawn futures that can be aborted immediately or after a timeout @@ -4905,6 +4931,23 @@ fn coins_conf_check(ctx: &MmArc, coins_en: &Json, ticker: &str, req: Option<&Jso Ok(()) } +pub async fn set_swap_transaction_fee_policy(ctx: MmArc, req: Json) -> Result>, String> { + let req: SetSwapTxFeePolicyReq = try_s!(json::from_value(req)); + let coin = match lp_coinfind(&ctx, &req.coin).await { + Ok(Some(t)) => t, + Ok(None) => return ERR!("No such coin {}", req.coin), + Err(err) => return ERR!("!lp_coinfind ({}): {}", req.coin, err), + }; + coin.set_swap_transaction_fee_policy(req.swap_tx_fee_policy); + let res = try_s!(json::to_vec(&json!({ + "result": { + "coin": req.coin, + "swap_tx_fee_policy": coin.swap_transaction_fee_policy(), + } + }))); + Ok(try_s!(Response::builder().body(res))) +} + #[cfg(test)] mod tests { use super::*; diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 806fe3c0e2..0914780a6f 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -22,14 +22,14 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, - TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; + SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, + TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, + TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -1501,6 +1501,10 @@ impl MmCoin for Qrc20Coin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } pub fn qrc20_swap_id(time_lock: u32, secret_hash: &[u8]) -> Vec { diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index e209c02172..0c579bce70 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -8,13 +8,13 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, - SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + SpendPaymentArgs, SwapTxFeePolicy, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, TransactionType, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -793,4 +793,8 @@ impl MmCoin for SolanaCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 253b1187c0..00875c5437 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -7,9 +7,9 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPayment PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, - SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, TransactionType, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + SignatureResult, SolanaCoin, SpendPaymentArgs, SwapTxFeePolicy, TakerSwapMakerCoin, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, + TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, @@ -587,4 +587,8 @@ impl MmCoin for SplToken { fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 4f450acd0e..267b6af4ec 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -21,7 +21,7 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, - SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, @@ -2209,6 +2209,10 @@ impl MmCoin for TendermintCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 34a637ef67..771131a23a 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -14,14 +14,14 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionErr, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFrom, WithdrawFut, - WithdrawRequest}; + SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, + TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, + TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFrom, WithdrawFut, WithdrawRequest}; use crate::{DexFee, MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; use async_trait::async_trait; use bitcrypto::sha256; @@ -896,4 +896,8 @@ impl MmCoin for TendermintToken { fn on_disabled(&self) -> Result<(), AbortedError> { self.abortable_system.abort_all() } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index c3771cf237..150ff9bec6 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -10,9 +10,9 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionResult, RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, - SignatureResult, SpendPaymentArgs, TakerCoinSwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, - TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + SignatureResult, SpendPaymentArgs, SwapTxFeePolicy, TakerCoinSwapOpsV2, TakerSwapMakerCoin, + TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, + TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, VerificationResult, @@ -396,6 +396,10 @@ impl MmCoin for TestCoin { fn on_disabled(&self) -> Result<(), AbortedError> { Ok(()) } fn on_token_deactivated(&self, _ticker: &str) { () } + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } pub struct TestPubkey {} diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 68ade20347..eee0cfa23f 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -14,7 +14,7 @@ use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBal NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, + SendPaymentArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, @@ -1316,6 +1316,10 @@ impl MmCoin for BchCoin { tokens.remove(ticker); }; } + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } impl CoinWithDerivationMethod for BchCoin { diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 3bfe1f6959..d602fb8186 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -31,11 +31,11 @@ use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithD PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, StakingInfosFut, SwapOps, - TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + SwapTxFeePolicy, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; @@ -987,6 +987,10 @@ impl MmCoin for QtumCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 2b5087e0e0..a3ecbd87c5 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -19,9 +19,9 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, - SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TakerSwapMakerCoin, TradeFee, TradePreimageError, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionErr, TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, + SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, TakerSwapMakerCoin, TradeFee, + TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, + TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, @@ -1888,6 +1888,10 @@ impl MmCoin for SlpToken { fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 1453acc346..8523e0a443 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -32,11 +32,11 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva RefundMakerPaymentArgs, RefundPaymentArgs, RefundResult, SearchForFundingSpendErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, - SwapOps, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, - TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, - ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, + SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, + TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateMakerPaymentArgs, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateSwapV2TxResult, ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WaitForTakerPaymentSpendError, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, @@ -1001,6 +1001,10 @@ impl MmCoin for UtxoStandardCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 3d25528b29..afe65fb121 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -20,13 +20,14 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, - TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionEnum, TransactionFut, - TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, + TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionEnum, + TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, + ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, + WithdrawRequest}; use crate::{NumConversError, TransactionDetails}; use crate::{Transaction, WithdrawError}; @@ -1848,6 +1849,10 @@ impl MmCoin for ZCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs index 795b76cee0..b54b154b70 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs @@ -16,8 +16,8 @@ use crate::mm2::lp_swap::{active_swaps_rpc, all_swaps_uuids_by_filter, ban_pubke recover_funds_of_swap, stats_swap_status, unban_pubkeys_rpc}; use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::{convert_address, convert_utxo_address, get_enabled_coins, get_trade_fee, kmd_rewards_info, my_tx_history, - send_raw_transaction, set_required_confirmations, set_requires_notarization, show_priv_key, - validate_address}; + send_raw_transaction, set_required_confirmations, set_requires_notarization, + set_swap_transaction_fee_policy, show_priv_key, validate_address}; /// Result of `fn dispatcher`. pub enum DispatcherRes { @@ -107,6 +107,7 @@ pub fn dispatcher(req: Json, ctx: MmArc) -> DispatcherRes { "set_requires_notarization" => hyres(set_requires_notarization(ctx, req)), "setprice" => hyres(set_price(ctx, req)), "stats_swap_status" => hyres(stats_swap_status(ctx, req)), + "set_swap_transaction_fee_policy" => hyres(set_swap_transaction_fee_policy(ctx, req)), "stop" => hyres(stop(ctx)), "trade_preimage" => hyres(into_legacy::trade_preimage(ctx, req)), "unban_pubkeys" => hyres(unban_pubkeys_rpc(ctx, req)), From a56d2c35f735478af491be2febc62a8ac422cb04 Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 13 Mar 2024 18:55:23 +0500 Subject: [PATCH 21/71] refactor to reuse tx builder code --- mm2src/coins/eth.rs | 73 +++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 43 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index f068cd8285..f57cea6c2d 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -188,15 +188,18 @@ pub type Web3RpcFut = Box> pub type Web3RpcResult = Result>; type EthPrivKeyPolicy = PrivKeyPolicy; +#[derive(Clone)] pub(crate) struct LegacyGasPrice { pub gas_price: U256, } +#[derive(Clone)] pub(crate) struct Eip1559FeePerGas { pub max_priority_fee_per_gas: U256, pub max_fee_per_gas: U256, } +#[derive(Clone)] pub(crate) enum PayForGasOption { Legacy(LegacyGasPrice), Eip1559(Eip1559FeePerGas), @@ -702,20 +705,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { .map_to_mm(WithdrawError::Transport)?; let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); - let tx_builder = match pay_for_gas_option { - PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), - PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - max_fee_per_gas, - }) => { - let chain_id = coin.chain_id.or_mm_err(|| WithdrawError::NoChainIdSet { - coin: coin.ticker().to_owned(), - })?; - tx_builder - .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) - .with_chain_id(chain_id) - }, - }; + let tx_builder = tx_builder_with_pay_for_gas_option(&coin, tx_builder, pay_for_gas_option.clone())?; let tx = tx_builder .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -882,20 +872,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .map_to_mm(WithdrawError::Transport)?; let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); - let tx_builder = match pay_for_gas_option { - PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), - PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - max_fee_per_gas, - }) => { - let chain_id = eth_coin.chain_id.or_mm_err(|| WithdrawError::NoChainIdSet { - coin: eth_coin.ticker().to_owned(), - })?; - tx_builder - .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) - .with_chain_id(chain_id) - }, - }; + let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, pay_for_gas_option.clone())?; let tx = tx_builder .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -979,20 +956,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .map_to_mm(WithdrawError::Transport)?; let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); - let tx_builder = match pay_for_gas_option { - PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), - PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - max_fee_per_gas, - }) => { - let chain_id = eth_coin.chain_id.or_mm_err(|| WithdrawError::NoChainIdSet { - coin: eth_coin.ticker().to_owned(), - })?; - tx_builder - .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) - .with_chain_id(chain_id) - }, - }; + let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, pay_for_gas_option.clone())?; let tx = tx_builder .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -6228,7 +6192,7 @@ async fn get_eth_gas_details_from_withdraw_fee( } } -/// Calc estimated total gas fee +/// Calc estimated total gas fee or price fn calc_total_fee(gas: U256, pay_for_gas_option: &PayForGasOption) -> NumConversResult { match *pay_for_gas_option { PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => gas @@ -6239,3 +6203,26 @@ fn calc_total_fee(gas: U256, pay_for_gas_option: &PayForGasOption) -> NumConvers .or_mm_err(|| NumConversError("total fee overflow".into())), } } + +#[allow(clippy::result_large_err)] +fn tx_builder_with_pay_for_gas_option( + eth_coin: &EthCoin, + tx_builder: UnSignedEthTxBuilder, + pay_for_gas_option: PayForGasOption, +) -> MmResult { + let tx_builder = match pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + max_fee_per_gas, + }) => { + let chain_id = eth_coin.chain_id.or_mm_err(|| WithdrawError::NoChainIdSet { + coin: eth_coin.ticker().to_owned(), + })?; + tx_builder + .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) + .with_chain_id(chain_id) + }, + }; + Ok(tx_builder) +} From 8ebb6551567b5a9504602ce417be6532863b31c3 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 15 Mar 2024 17:56:04 +0500 Subject: [PATCH 22/71] add eip1559 fee to swap txns (pre-TPU) --- mm2src/coins/eth.rs | 235 ++++++++++++------ mm2src/coins/lp_coins.rs | 11 +- .../coins/rpc_command/get_estimated_fees.rs | 5 +- 3 files changed, 169 insertions(+), 82 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index f57cea6c2d..d51e8043b3 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -26,7 +26,7 @@ use crate::eth::web3_transport::websocket_transport::{WebsocketTransport, Websoc use crate::lp_price::get_base_price_in_rel; use crate::nft::nft_structs::{ContractType, ConvertChain, NftInfo, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; -use crate::{DexFee, RpcCommonOps, ValidateWatcherSpendInput, WatcherSpendType}; +use crate::{DexFee, EthGasLimitOption, RpcCommonOps, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; @@ -705,7 +705,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { .map_to_mm(WithdrawError::Transport)?; let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); - let tx_builder = tx_builder_with_pay_for_gas_option(&coin, tx_builder, pay_for_gas_option.clone())?; + let tx_builder = tx_builder_with_pay_for_gas_option(&coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -872,7 +872,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .map_to_mm(WithdrawError::Transport)?; let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); - let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, pay_for_gas_option.clone())?; + let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -956,7 +956,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .map_to_mm(WithdrawError::Transport)?; let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); - let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, pay_for_gas_option.clone())?; + let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -2390,9 +2390,10 @@ async fn sign_transaction_with_keypair( let (nonce, web3_instances_with_latest_nonce) = try_tx_s!(coin.clone().get_addr_nonce(coin.my_address).compat().await); status.status(tags!(), "get_gas_price…"); - let gas_price = try_tx_s!(coin.get_gas_price().compat().await); - - let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, action, value, data).with_gas_price(gas_price); // TODO: add support for eip1559 params (f.e.: create a GasParams struct with gas_limit and PayForGasOption members and use it as a param instead of 'gas' param) + let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option().compat().await); + let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, action, value, data); + let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, &pay_for_gas_option) + .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; let tx = tx_builder.build().map_err(|e| TransactionErr::Plain(e.to_string()))?; Ok(( @@ -2445,13 +2446,15 @@ async fn sign_and_send_transaction_with_metamask( Action::Call(to) => Some(to), }; - let gas_price = try_tx_s!(coin.get_gas_price().compat().await); + let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option().compat().await); let tx_to_send = TransactionRequest { from: coin.my_address, to, gas: Some(gas), - gas_price: Some(gas_price), + gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { Some(gas_price) } else { None }, + max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, .. }) = pay_for_gas_option { Some(max_priority_fee_per_gas) } else { None }, + max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option { Some(max_fee_per_gas) } else { None }, value: Some(value), data: Some(data.clone().into()), nonce: None, @@ -4232,19 +4235,18 @@ impl EthCoin { /// because [`CallRequest::from`] is set to [`EthCoinImpl::my_address`]. fn estimate_gas_for_contract_call(&self, contract_addr: Address, call_data: Bytes) -> Web3RpcFut { let coin = self.clone(); - Box::new(coin.get_gas_price().and_then(move |gas_price| { + Box::new(coin.get_swap_pay_for_gas_option().and_then(move |pay_for_gas_option| { let eth_value = U256::zero(); let estimate_gas_req = CallRequest { value: Some(eth_value), data: Some(call_data), from: Some(coin.my_address), to: Some(contract_addr), - gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), ..CallRequest::default() }; + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + let estimate_gas_req = call_request_with_pay_for_gas_option(estimate_gas_req, pay_for_gas_option); coin.estimate_gas_wrapper(estimate_gas_req) .map_to_mm_fut(Web3RpcError::from) })) @@ -4807,13 +4809,14 @@ impl EthCoin { } pub async fn get_watcher_reward_amount(&self, wait_until: u64) -> Result> { - let gas_price = repeatable!(async { self.get_gas_price().compat().await.retry_on_err() }) + let pay_for_gas_option = repeatable!(async { self.get_swap_pay_for_gas_option().compat().await.retry_on_err() }) .until_s(wait_until) .repeat_every_secs(10.) .await .map_err(|_| WatcherRewardError::RPCError("Error getting the gas price".to_string()))?; - let gas_cost_wei = U256::from(REWARD_GAS_AMOUNT) * gas_price; + let gas_cost_wei = calc_total_fee(U256::from(REWARD_GAS_AMOUNT), &pay_for_gas_option) + .map_err(|e| WatcherRewardError::InternalError(e.to_string()))?; let gas_cost_eth = u256_to_big_decimal(gas_cost_wei, ETH_DECIMALS) .map_err(|e| WatcherRewardError::InternalError(e.to_string()))?; Ok(gas_cost_eth) @@ -4890,6 +4893,40 @@ impl EthCoin { .await } + fn get_swap_pay_for_gas_option(&self) -> Web3RpcFut { + let coin = self.clone(); + let fut = async move { + match coin.swap_transaction_fee_policy() { + SwapTxFeePolicy::Internal => { + let gas_price = coin.get_gas_price().compat().await?; + Ok(PayForGasOption::Legacy(LegacyGasPrice { gas_price })) + }, + SwapTxFeePolicy::Low | SwapTxFeePolicy::Medium | SwapTxFeePolicy::High => { + let fee_per_gas = coin.get_eip1559_gas_fee().compat().await?; + let pay_result = (|| -> MmResult { + match coin.swap_transaction_fee_policy() { + SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas: wei_from_big_decimal(&fee_per_gas.low.max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?, + max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.low.max_fee_per_gas, ETH_GWEI_DECIMALS)? + })), + SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas: wei_from_big_decimal(&fee_per_gas.medium.max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?, + max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.medium.max_fee_per_gas, ETH_GWEI_DECIMALS)? + })), + _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas: wei_from_big_decimal(&fee_per_gas.high.max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?, + max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.high.max_fee_per_gas, ETH_GWEI_DECIMALS)? + })), + } + })(); + pay_result.mm_err(|e| Web3RpcError::Internal(format!("gas api result conversion error: {}", e.to_string()))) + }, + SwapTxFeePolicy::Unsupported => Err(MmError::new(Web3RpcError::Internal("swap fee policy not set".into()))), + } + }; + Box::new(fut.boxed().compat()) + } + /// Checks every second till at least one ETH node recognizes that nonce is increased. /// Parity has reliable "nextNonce" method that always returns correct nonce for address. /// But we can't expect that all nodes will always be Parity. @@ -5234,10 +5271,10 @@ impl MmCoin for EthCoin { fn get_trade_fee(&self) -> Box + Send> { let coin = self.clone(); Box::new( - self.get_gas_price() + self.get_swap_pay_for_gas_option() .map_err(|e| e.to_string()) - .and_then(move |gas_price| { - let fee = gas_price * U256::from(ETH_GAS); + .and_then(move |pay_for_gas_option| { + let fee = calc_total_fee(U256::from(ETH_GAS), &pay_for_gas_option).map_err(|e| e.to_string())?; let fee_coin = match &coin.coin_type { EthCoinType::Eth => &coin.ticker, EthCoinType::Erc20 { platform, .. } => platform, @@ -5257,8 +5294,8 @@ impl MmCoin for EthCoin { value: TradePreimageValue, stage: FeeApproxStage, ) -> TradePreimageResult { - let gas_price = self.get_gas_price().compat().await?; - let gas_price = increase_gas_price_by_stage(gas_price, &stage); + let pay_for_gas_option = self.get_swap_pay_for_gas_option().compat().await?; + let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let gas_limit = match self.coin_type { EthCoinType::Eth => { // this gas_limit includes gas for `ethPayment` and `senderRefund` contract calls @@ -5293,7 +5330,7 @@ impl MmCoin for EthCoin { EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; - let total_fee = gas_limit * gas_price; + let total_fee = calc_total_fee(gas_limit, &pay_for_gas_option)?; let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; let fee_coin = match &self.coin_type { EthCoinType::Eth => &self.ticker, @@ -5310,9 +5347,9 @@ impl MmCoin for EthCoin { fn get_receiver_trade_fee(&self, stage: FeeApproxStage) -> TradePreimageFut { let coin = self.clone(); let fut = async move { - let gas_price = coin.get_gas_price().compat().await?; - let gas_price = increase_gas_price_by_stage(gas_price, &stage); - let total_fee = gas_price * U256::from(ETH_GAS); + let pay_for_gas_option = coin.get_swap_pay_for_gas_option().compat().await?; + let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); + let total_fee = calc_total_fee(U256::from(ETH_GAS), &pay_for_gas_option)?; let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; let fee_coin = match &coin.coin_type { EthCoinType::Eth => &coin.ticker, @@ -5348,24 +5385,23 @@ impl MmCoin for EthCoin { EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; - let gas_price = self.get_gas_price().compat().await?; - let gas_price = increase_gas_price_by_stage(gas_price, &stage); + let pay_for_gas_option = self.get_swap_pay_for_gas_option().compat().await?; + let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let estimate_gas_req = CallRequest { value: Some(eth_value), data: Some(data.clone().into()), from: Some(self.my_address), to: Some(*call_addr), gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), ..CallRequest::default() }; - + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + let estimate_gas_req = call_request_with_pay_for_gas_option(estimate_gas_req, pay_for_gas_option.clone()); // Please note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. // Ideally we should determine the case when we have the insufficient balance and return `TradePreimageError::NotSufficientBalance` error. let gas_limit = self.estimate_gas_wrapper(estimate_gas_req).compat().await?; - let total_fee = gas_limit * gas_price; + let total_fee = calc_total_fee(gas_limit, &pay_for_gas_option)?; let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; Ok(TradeFee { coin: fee_coin.into(), @@ -5638,11 +5674,13 @@ fn display_u256_with_decimal_point(number: U256, decimals: u8) -> String { 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 { let string = display_u256_with_decimal_point(number, decimals); Ok(string.parse::()?) } +/// 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 { let mut amount = amount.to_string(); let dot = amount.find(|c| c == '.'); @@ -6014,21 +6052,26 @@ fn increase_by_percent_one_gwei(num: U256, percent: u64) -> U256 { } } -fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 { - match level { - FeeApproxStage::WithoutApprox => gas_price, - FeeApproxStage::StartSwap => { - increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_START_SWAP) - }, - FeeApproxStage::OrderIssue => { - increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_ORDER_ISSUE) - }, - FeeApproxStage::TradePreimage => { - increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE) - }, - FeeApproxStage::WatcherPreimage => { - increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_WATCHER_PREIMAGE) - }, +fn increase_gas_price_by_stage(pay_for_gas_option: PayForGasOption, level: &FeeApproxStage) -> PayForGasOption { + if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { + let new_gas_price = match level { + FeeApproxStage::WithoutApprox => gas_price, + FeeApproxStage::StartSwap => { + increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_START_SWAP) + }, + FeeApproxStage::OrderIssue => { + increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_ORDER_ISSUE) + }, + FeeApproxStage::TradePreimage => { + increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE) + }, + FeeApproxStage::WatcherPreimage => { + increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_WATCHER_PREIMAGE) + }, + }; + PayForGasOption::Legacy(LegacyGasPrice { gas_price: new_gas_price }) + } else { + pay_for_gas_option } } @@ -6141,55 +6184,66 @@ async fn get_eth_gas_details_from_withdraw_fee( call_addr: Address, fungible_max: bool, ) -> MmResult { - match fee { + let pay_for_gas_option = match fee { Some(WithdrawFee::EthGas { gas_price, gas }) => { let gas_price = wei_from_big_decimal(&gas_price, ETH_GWEI_DECIMALS)?; - Ok((gas.into(), PayForGasOption::Legacy(LegacyGasPrice { gas_price }))) + return Ok((gas.into(), PayForGasOption::Legacy(LegacyGasPrice { gas_price }))); }, Some(WithdrawFee::EthGasEip1559 { max_fee_per_gas, max_priority_fee_per_gas, - gas, + gas_option: gas_limit, }) => { let max_fee_per_gas = wei_from_big_decimal(&max_fee_per_gas, ETH_GWEI_DECIMALS)?; let max_priority_fee_per_gas = wei_from_big_decimal(&max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?; - Ok(( - gas.into(), + if let EthGasLimitOption::Set(gas) = gas_limit { + return Ok((gas.into(), PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas, + max_priority_fee_per_gas, + }))); + } else { + // go to gas estimate code PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, max_priority_fee_per_gas, - }), - )) + }) + } }, Some(fee_policy) => { let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); - MmError::err(EthGasDetailsErr::InvalidFeePolicy(error)) + return MmError::err(EthGasDetailsErr::InvalidFeePolicy(error)); }, None => { + // If WithdrawFee not set use legacy gas price (?) let gas_price = eth_coin.get_gas_price().compat().await?; - // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH - let eth_value_for_estimate = if fungible_max && eth_coin.coin_type == EthCoinType::Eth { - eth_value - gas_price * U256::from(21000) - } else { - eth_value - }; - let estimate_gas_req = CallRequest { - value: Some(eth_value_for_estimate), - data: Some(data), - from: Some(eth_coin.my_address), - to: Some(call_addr), - gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), - ..CallRequest::default() - }; - // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. - // TODO Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. - let gas_limit = eth_coin.estimate_gas_wrapper(estimate_gas_req).compat().await?; - Ok((gas_limit, PayForGasOption::Legacy(LegacyGasPrice { gas_price }))) + // go to gas estimate code + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) }, - } + }; + + // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH + let eth_value_for_estimate = if fungible_max && eth_coin.coin_type == EthCoinType::Eth { + eth_value - calc_total_fee(U256::from(21000), &pay_for_gas_option)? + } else { + eth_value + }; + let estimate_gas_req = CallRequest { + value: Some(eth_value_for_estimate), + data: Some(data), + from: Some(eth_coin.my_address), + to: Some(call_addr), + gas: None, + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { Some(gas_price) } else { None }, + max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, .. }) = pay_for_gas_option { Some(max_priority_fee_per_gas) } else { None }, + max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option { Some(max_fee_per_gas) } else { None }, + ..CallRequest::default() + }; + // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. + // TODO Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. + let gas_limit = eth_coin.estimate_gas_wrapper(estimate_gas_req).compat().await?; + Ok((gas_limit, pay_for_gas_option)) } /// Calc estimated total gas fee or price @@ -6208,9 +6262,9 @@ fn calc_total_fee(gas: U256, pay_for_gas_option: &PayForGasOption) -> NumConvers fn tx_builder_with_pay_for_gas_option( eth_coin: &EthCoin, tx_builder: UnSignedEthTxBuilder, - pay_for_gas_option: PayForGasOption, + pay_for_gas_option: &PayForGasOption, ) -> MmResult { - let tx_builder = match pay_for_gas_option { + let tx_builder = match *pay_for_gas_option { PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => tx_builder.with_gas_price(gas_price), PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, @@ -6226,3 +6280,26 @@ fn tx_builder_with_pay_for_gas_option( }; Ok(tx_builder) } + +fn call_request_with_pay_for_gas_option( + call_request: CallRequest, + pay_for_gas_option: PayForGasOption, +) -> CallRequest { + match pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => CallRequest { + gas_price: Some(gas_price), + max_priority_fee_per_gas: None, + max_fee_per_gas: None, + .. call_request + }, + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + max_fee_per_gas, + }) => CallRequest { + gas_price: None, + max_priority_fee_per_gas: Some(max_priority_fee_per_gas), + max_fee_per_gas: Some(max_fee_per_gas), + .. call_request + }, + } +} \ No newline at end of file diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 2b513c8038..99d743de60 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1808,6 +1808,15 @@ pub trait MarketCoinOps { fn is_privacy(&self) -> bool { false } } +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(tag = "type")] +pub enum EthGasLimitOption { + /// Use this value as gas limit + Set(u64), + /// Make MM2 calculate gas limit + Calc, +} + #[derive(Clone, Debug, Deserialize, PartialEq)] #[serde(tag = "type")] pub enum WithdrawFee { @@ -1826,7 +1835,7 @@ pub enum WithdrawFee { /// in gwei max_priority_fee_per_gas: BigDecimal, max_fee_per_gas: BigDecimal, - gas: u64, + gas_option: EthGasLimitOption, }, Qrc20Gas { /// in satoshi diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index abfa493f99..9d974d5a28 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,5 +1,7 @@ //! RPCs to start/stop gas fee estimator and get estimated base and priority fee per gas +use std::sync::Arc; +use futures::compat::Future01CompatExt; use crate::eth::{EthCoin, FeePerGasEstimated}; use crate::AsyncMutex; use crate::{from_ctx, lp_coinfind, MmCoinEnum, NumConversError}; @@ -8,7 +10,6 @@ use common::log::debug; use common::{HttpStatusCode, StatusCode}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) @@ -144,7 +145,7 @@ impl FeeEstimatorContext { async fn fee_estimator_loop(estimated_fees: Arc>, coin: EthCoin) { loop { let started = common::now_float(); - *estimated_fees.lock().await = coin.get_eip1559_gas_fee().await.unwrap_or_default(); + *estimated_fees.lock().await = coin.get_eip1559_gas_fee().compat().await.unwrap_or_default(); let elapsed = common::now_float() - started; debug!( From 07434719a5c2401c18c6d4898c5cfc27d1a025ea Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 18 Mar 2024 23:51:41 +0500 Subject: [PATCH 23/71] add get_swap_transaction_fee_policy rpc for eth coin --- mm2src/coins/eth.rs | 143 +++++++++++++----- mm2src/coins/lightning.rs | 2 +- mm2src/coins/lp_coins.rs | 55 ++++--- mm2src/coins/qrc20.rs | 2 +- mm2src/coins/solana.rs | 2 +- mm2src/coins/solana/spl.rs | 2 +- mm2src/coins/tendermint/tendermint_coin.rs | 2 +- mm2src/coins/tendermint/tendermint_token.rs | 2 +- mm2src/coins/test_coin.rs | 2 +- mm2src/coins/utxo/bch.rs | 2 +- mm2src/coins/utxo/qtum.rs | 2 +- mm2src/coins/utxo/slp.rs | 2 +- mm2src/coins/utxo/utxo_standard.rs | 2 +- mm2src/coins/z_coin.rs | 2 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 7 +- .../src/rpc/dispatcher/dispatcher_legacy.rs | 5 +- 16 files changed, 158 insertions(+), 76 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index d51e8043b3..f6a0e8f781 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -188,18 +188,18 @@ pub type Web3RpcFut = Box> pub type Web3RpcResult = Result>; type EthPrivKeyPolicy = PrivKeyPolicy; -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct LegacyGasPrice { pub gas_price: U256, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct Eip1559FeePerGas { pub max_priority_fee_per_gas: U256, pub max_fee_per_gas: U256, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) enum PayForGasOption { Legacy(LegacyGasPrice), Eip1559(Eip1559FeePerGas), @@ -2452,9 +2452,26 @@ async fn sign_and_send_transaction_with_metamask( from: coin.my_address, to, gas: Some(gas), - gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { Some(gas_price) } else { None }, - max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, .. }) = pay_for_gas_option { Some(max_priority_fee_per_gas) } else { None }, - max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option { Some(max_fee_per_gas) } else { None }, + gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { + Some(gas_price) + } else { + None + }, + max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + .. + }) = pay_for_gas_option + { + Some(max_priority_fee_per_gas) + } else { + None + }, + max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option + { + Some(max_fee_per_gas) + } else { + None + }, value: Some(value), data: Some(data.clone().into()), nonce: None, @@ -4809,11 +4826,12 @@ impl EthCoin { } pub async fn get_watcher_reward_amount(&self, wait_until: u64) -> Result> { - let pay_for_gas_option = repeatable!(async { self.get_swap_pay_for_gas_option().compat().await.retry_on_err() }) - .until_s(wait_until) - .repeat_every_secs(10.) - .await - .map_err(|_| WatcherRewardError::RPCError("Error getting the gas price".to_string()))?; + let pay_for_gas_option = + repeatable!(async { self.get_swap_pay_for_gas_option().compat().await.retry_on_err() }) + .until_s(wait_until) + .repeat_every_secs(10.) + .await + .map_err(|_| WatcherRewardError::RPCError("Error getting the gas price".to_string()))?; let gas_cost_wei = calc_total_fee(U256::from(REWARD_GAS_AMOUNT), &pay_for_gas_option) .map_err(|e| WatcherRewardError::InternalError(e.to_string()))?; @@ -4895,33 +4913,55 @@ impl EthCoin { fn get_swap_pay_for_gas_option(&self) -> Web3RpcFut { let coin = self.clone(); - let fut = async move { - match coin.swap_transaction_fee_policy() { + let fut = async move { + match coin.get_swap_transaction_fee_policy() { SwapTxFeePolicy::Internal => { let gas_price = coin.get_gas_price().compat().await?; Ok(PayForGasOption::Legacy(LegacyGasPrice { gas_price })) }, SwapTxFeePolicy::Low | SwapTxFeePolicy::Medium | SwapTxFeePolicy::High => { let fee_per_gas = coin.get_eip1559_gas_fee().compat().await?; - let pay_result = (|| -> MmResult { - match coin.swap_transaction_fee_policy() { - SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas: wei_from_big_decimal(&fee_per_gas.low.max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?, - max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.low.max_fee_per_gas, ETH_GWEI_DECIMALS)? + let pay_result = (|| -> MmResult { + match coin.get_swap_transaction_fee_policy() { + SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.low.max_priority_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + max_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.low.max_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, })), - SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas: wei_from_big_decimal(&fee_per_gas.medium.max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?, - max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.medium.max_fee_per_gas, ETH_GWEI_DECIMALS)? + SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.medium.max_priority_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + max_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.medium.max_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, })), - _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas: wei_from_big_decimal(&fee_per_gas.high.max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?, - max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.high.max_fee_per_gas, ETH_GWEI_DECIMALS)? + _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.high.max_priority_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + max_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.high.max_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, })), } })(); - pay_result.mm_err(|e| Web3RpcError::Internal(format!("gas api result conversion error: {}", e.to_string()))) + pay_result.mm_err(|e| { + Web3RpcError::Internal(format!("gas api result conversion error: {}", e)) + }) + }, + SwapTxFeePolicy::Unsupported => { + Err(MmError::new(Web3RpcError::Internal("swap fee policy not set".into()))) }, - SwapTxFeePolicy::Unsupported => Err(MmError::new(Web3RpcError::Internal("swap fee policy not set".into()))), } }; Box::new(fut.boxed().compat()) @@ -5453,7 +5493,7 @@ impl MmCoin for EthCoin { }; } - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { self.swap_txfee_policy.lock().unwrap().clone() } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { self.swap_txfee_policy.lock().unwrap().clone() } fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy) { *self.swap_txfee_policy.lock().unwrap() = swap_txfee_policy @@ -6069,7 +6109,9 @@ fn increase_gas_price_by_stage(pay_for_gas_option: PayForGasOption, level: &FeeA increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_WATCHER_PREIMAGE) }, }; - PayForGasOption::Legacy(LegacyGasPrice { gas_price: new_gas_price }) + PayForGasOption::Legacy(LegacyGasPrice { + gas_price: new_gas_price, + }) } else { pay_for_gas_option } @@ -6197,10 +6239,13 @@ async fn get_eth_gas_details_from_withdraw_fee( let max_fee_per_gas = wei_from_big_decimal(&max_fee_per_gas, ETH_GWEI_DECIMALS)?; let max_priority_fee_per_gas = wei_from_big_decimal(&max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?; if let EthGasLimitOption::Set(gas) = gas_limit { - return Ok((gas.into(), PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas, - max_priority_fee_per_gas, - }))); + return Ok(( + gas.into(), + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas, + max_priority_fee_per_gas, + }), + )); } else { // go to gas estimate code PayForGasOption::Eip1559(Eip1559FeePerGas { @@ -6235,9 +6280,26 @@ async fn get_eth_gas_details_from_withdraw_fee( gas: None, // gas price must be supplied because some smart contracts base their // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { Some(gas_price) } else { None }, - max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, .. }) = pay_for_gas_option { Some(max_priority_fee_per_gas) } else { None }, - max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option { Some(max_fee_per_gas) } else { None }, + gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { + Some(gas_price) + } else { + None + }, + max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + .. + }) = pay_for_gas_option + { + Some(max_priority_fee_per_gas) + } else { + None + }, + max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option + { + Some(max_fee_per_gas) + } else { + None + }, ..CallRequest::default() }; // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. @@ -6281,16 +6343,13 @@ fn tx_builder_with_pay_for_gas_option( Ok(tx_builder) } -fn call_request_with_pay_for_gas_option( - call_request: CallRequest, - pay_for_gas_option: PayForGasOption, -) -> CallRequest { +fn call_request_with_pay_for_gas_option(call_request: CallRequest, pay_for_gas_option: PayForGasOption) -> CallRequest { match pay_for_gas_option { PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => CallRequest { gas_price: Some(gas_price), max_priority_fee_per_gas: None, max_fee_per_gas: None, - .. call_request + ..call_request }, PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, @@ -6299,7 +6358,7 @@ fn call_request_with_pay_for_gas_option( gas_price: None, max_priority_fee_per_gas: Some(max_priority_fee_per_gas), max_fee_per_gas: Some(max_fee_per_gas), - .. call_request + ..call_request }, } -} \ No newline at end of file +} diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 985dd3ef31..ea83401fe8 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -1466,7 +1466,7 @@ impl MmCoin for LightningCoin { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 99d743de60..ef3b2f4a6b 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1813,7 +1813,7 @@ pub trait MarketCoinOps { pub enum EthGasLimitOption { /// Use this value as gas limit Set(u64), - /// Make MM2 calculate gas limit + /// Make MM2 calculate gas limit Calc, } @@ -2248,12 +2248,37 @@ impl Default for SwapTxFeePolicy { } #[derive(Debug, Deserialize)] -pub struct SetSwapTxFeePolicyReq { +pub struct SwapTxFeePolicyRequest { coin: String, #[serde(default)] swap_tx_fee_policy: SwapTxFeePolicy, } +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum SwapTxFeePolicyError { + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { coin: String }, +} + +impl From for SwapTxFeePolicyError { + fn from(e: CoinFindError) -> Self { + match e { + CoinFindError::NoSuchCoin { coin } => SwapTxFeePolicyError::NoSuchCoin { coin }, + } + } +} + +impl HttpStatusCode for SwapTxFeePolicyError { + fn status_code(&self) -> StatusCode { + match self { + SwapTxFeePolicyError::NoSuchCoin { .. } => StatusCode::BAD_REQUEST, + } + } +} + +pub type SwapTxFeePolicyResult = Result>; + #[derive(Debug, Display, PartialEq)] pub enum TradePreimageError { #[display( @@ -3112,7 +3137,7 @@ pub trait MmCoin: fn on_token_deactivated(&self, ticker: &str); /// Return swap transaction fee policy - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy; + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy; /// set swap transaction fee policy fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy); @@ -4940,21 +4965,17 @@ fn coins_conf_check(ctx: &MmArc, coins_en: &Json, ticker: &str, req: Option<&Jso Ok(()) } -pub async fn set_swap_transaction_fee_policy(ctx: MmArc, req: Json) -> Result>, String> { - let req: SetSwapTxFeePolicyReq = try_s!(json::from_value(req)); - let coin = match lp_coinfind(&ctx, &req.coin).await { - Ok(Some(t)) => t, - Ok(None) => return ERR!("No such coin {}", req.coin), - Err(err) => return ERR!("!lp_coinfind ({}): {}", req.coin, err), - }; +/// Get eip 1559 transaction fee per gas policy (low, medium, high) set for the coin +pub async fn get_swap_transaction_fee_policy(ctx: MmArc, req: SwapTxFeePolicyRequest) -> SwapTxFeePolicyResult { + let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; + Ok(coin.get_swap_transaction_fee_policy()) +} + +/// Set eip 1559 transaction fee per gas policy (low, medium, high) +pub async fn set_swap_transaction_fee_policy(ctx: MmArc, req: SwapTxFeePolicyRequest) -> SwapTxFeePolicyResult { + let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; coin.set_swap_transaction_fee_policy(req.swap_tx_fee_policy); - let res = try_s!(json::to_vec(&json!({ - "result": { - "coin": req.coin, - "swap_tx_fee_policy": coin.swap_transaction_fee_policy(), - } - }))); - Ok(try_s!(Response::builder().body(res))) + Ok(coin.get_swap_transaction_fee_policy()) } #[cfg(test)] diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 0914780a6f..ba33b4a23b 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1502,7 +1502,7 @@ impl MmCoin for Qrc20Coin { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 0c579bce70..fdbc0d8b9f 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -794,7 +794,7 @@ impl MmCoin for SolanaCoin { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 00875c5437..bc38b4b479 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -588,7 +588,7 @@ impl MmCoin for SplToken { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 267b6af4ec..dcb5fe2a66 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2210,7 +2210,7 @@ impl MmCoin for TendermintCoin { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 771131a23a..c2480381bc 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -897,7 +897,7 @@ impl MmCoin for TendermintToken { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 150ff9bec6..725cf97b74 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -397,7 +397,7 @@ impl MmCoin for TestCoin { fn on_token_deactivated(&self, _ticker: &str) { () } - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index eee0cfa23f..2801ce2388 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1317,7 +1317,7 @@ impl MmCoin for BchCoin { }; } - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index d602fb8186..2d3c21160e 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -988,7 +988,7 @@ impl MmCoin for QtumCoin { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index a3ecbd87c5..d8ebead6e5 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -1889,7 +1889,7 @@ impl MmCoin for SlpToken { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 8523e0a443..8088bec24a 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -1002,7 +1002,7 @@ impl MmCoin for UtxoStandardCoin { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index afe65fb121..44d9d1b99f 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1850,7 +1850,7 @@ impl MmCoin for ZCoin { fn on_token_deactivated(&self, _ticker: &str) {} - fn swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 029922db82..326afda15e 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -33,8 +33,9 @@ use coins::utxo::qtum::QtumCoin; use coins::utxo::slp::SlpToken; use coins::utxo::utxo_standard::UtxoStandardCoin; use coins::z_coin::ZCoin; -use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, nft, remove_delegation, - sign_message, sign_raw_transaction, verify_message, withdraw}; +use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, get_swap_transaction_fee_policy, + nft, remove_delegation, set_swap_transaction_fee_policy, sign_message, sign_raw_transaction, + verify_message, withdraw}; #[cfg(all( feature = "enable-solana", not(target_os = "ios"), @@ -211,6 +212,8 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, start_eth_fee_estimator).await, "stop_eth_fee_estimator" => handle_mmrpc(ctx, request, stop_eth_fee_estimator).await, "get_eth_estimated_fee_per_gas" => handle_mmrpc(ctx, request, get_eth_estimated_fee_per_gas).await, + "get_swap_transaction_fee_policy" => handle_mmrpc(ctx, request, get_swap_transaction_fee_policy).await, + "set_swap_transaction_fee_policy" => handle_mmrpc(ctx, request, set_swap_transaction_fee_policy).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs index b54b154b70..795b76cee0 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs @@ -16,8 +16,8 @@ use crate::mm2::lp_swap::{active_swaps_rpc, all_swaps_uuids_by_filter, ban_pubke recover_funds_of_swap, stats_swap_status, unban_pubkeys_rpc}; use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::{convert_address, convert_utxo_address, get_enabled_coins, get_trade_fee, kmd_rewards_info, my_tx_history, - send_raw_transaction, set_required_confirmations, set_requires_notarization, - set_swap_transaction_fee_policy, show_priv_key, validate_address}; + send_raw_transaction, set_required_confirmations, set_requires_notarization, show_priv_key, + validate_address}; /// Result of `fn dispatcher`. pub enum DispatcherRes { @@ -107,7 +107,6 @@ pub fn dispatcher(req: Json, ctx: MmArc) -> DispatcherRes { "set_requires_notarization" => hyres(set_requires_notarization(ctx, req)), "setprice" => hyres(set_price(ctx, req)), "stats_swap_status" => hyres(stats_swap_status(ctx, req)), - "set_swap_transaction_fee_policy" => hyres(set_swap_transaction_fee_policy(ctx, req)), "stop" => hyres(stop(ctx)), "trade_preimage" => hyres(into_legacy::trade_preimage(ctx, req)), "unban_pubkeys" => hyres(unban_pubkeys_rpc(ctx, req)), From 9081aa590ecedced7c5f7dccaf677365a01383df Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 18 Mar 2024 23:52:31 +0500 Subject: [PATCH 24/71] refactor get_estimated_fees rpc to use lp_coinfind_or_err --- .../coins/rpc_command/get_estimated_fees.rs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 9d974d5a28..2cc13c8eca 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,15 +1,15 @@ //! RPCs to start/stop gas fee estimator and get estimated base and priority fee per gas -use std::sync::Arc; -use futures::compat::Future01CompatExt; use crate::eth::{EthCoin, FeePerGasEstimated}; -use crate::AsyncMutex; -use crate::{from_ctx, lp_coinfind, MmCoinEnum, NumConversError}; +use crate::{from_ctx, NumConversError}; +use crate::{lp_coinfind_or_err, AsyncMutex, CoinFindError, MmCoinEnum}; use common::executor::{spawn_abortable, AbortOnDropHandle, Timer}; use common::log::debug; use common::{HttpStatusCode, StatusCode}; +use futures::compat::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) @@ -18,8 +18,8 @@ const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported (Blockn #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum FeeEstimatorError { - #[display(fmt = "Coin not activated")] - CoinNotActivated, + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { coin: String }, #[display(fmt = "Gas fee estimation not supported for this coin")] CoinNotSupported, #[display(fmt = "Chain id not supported")] @@ -37,7 +37,7 @@ pub enum FeeEstimatorError { impl HttpStatusCode for FeeEstimatorError { fn status_code(&self) -> StatusCode { match self { - FeeEstimatorError::CoinNotActivated + FeeEstimatorError::NoSuchCoin { .. } | FeeEstimatorError::CoinNotSupported | FeeEstimatorError::ChainNotSupported | FeeEstimatorError::AlreadyStarted @@ -55,6 +55,14 @@ impl From for FeeEstimatorError { fn from(e: String) -> Self { FeeEstimatorError::InternalError(e) } } +impl From for FeeEstimatorError { + fn from(e: CoinFindError) -> Self { + match e { + CoinFindError::NoSuchCoin { coin } => FeeEstimatorError::NoSuchCoin { coin }, + } + } +} + /// Gas fee estimator loop context, /// runs a loop to estimate max fee and max priority fee per gas according to EIP-1559 for the next block /// @@ -123,13 +131,13 @@ impl FeeEstimatorContext { } async fn check_if_coin_supported(ctx: &MmArc, ticker: &str) -> Result> { - let coin = match lp_coinfind(ctx, ticker).await { - Ok(Some(MmCoinEnum::EthCoin(eth))) => eth, - Ok(Some(_)) => return MmError::err(FeeEstimatorError::CoinNotSupported), - Ok(None) | Err(_) => return MmError::err(FeeEstimatorError::CoinNotActivated), + let eth_coin = match lp_coinfind_or_err(ctx, ticker).await? { + MmCoinEnum::EthCoin(eth) => eth, + _ => return MmError::err(FeeEstimatorError::CoinNotSupported), }; - Self::check_if_chain_id_supported(&coin)?; - Ok(coin) + + Self::check_if_chain_id_supported(ð_coin)?; + Ok(eth_coin) } } From acd048beca106adabadbb3f8fbae7d08b69801e5 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 19 Mar 2024 11:13:46 +0500 Subject: [PATCH 25/71] fix fmt --- mm2src/coins/eth.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index f6a0e8f781..6b2bdcffd9 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4955,9 +4955,7 @@ impl EthCoin { })), } })(); - pay_result.mm_err(|e| { - Web3RpcError::Internal(format!("gas api result conversion error: {}", e)) - }) + pay_result.mm_err(|e| Web3RpcError::Internal(format!("gas api result conversion error: {}", e))) }, SwapTxFeePolicy::Unsupported => { Err(MmError::new(Web3RpcError::Internal("swap fee policy not set".into()))) From d7de499ec1f05ed4bac88155c276087ad459f04d Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 22 Mar 2024 19:55:46 +0500 Subject: [PATCH 26/71] fix estimated base fee adjustment --- mm2src/coins/eth/eip1559_gas_fee.rs | 43 +++++++++++++++++------------ 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index 205f5be641..19e120e1cf 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -195,11 +195,14 @@ impl FeePerGasSimpleEstimator { /// percentiles to pass to eth_feeHistory const HISTORY_PERCENTILES: [f64; FEE_PER_GAS_LEVELS] = [25.0, 50.0, 75.0]; + /// percentile to predict next base fee over historical rewards + const BASE_FEE_PERCENTILE: f64 = 75.0; + /// percentiles to calc max priority fee over historical rewards - const CALC_PERCENTILES: [f64; FEE_PER_GAS_LEVELS] = [50.0, 50.0, 50.0]; + const PRIORITY_FEE_PERCENTILES: [f64; FEE_PER_GAS_LEVELS] = [50.0, 50.0, 50.0]; - /// adjustment for max priority fee picked up by sampling - const ADJUST_MAX_FEE: [f64; FEE_PER_GAS_LEVELS] = [1.0, 1.0, 1.0]; + /// adjustment for max fee per gas picked up by sampling + const ADJUST_MAX_FEE: [f64; FEE_PER_GAS_LEVELS] = [1.1, 1.175, 1.25]; // 1.25 assures max_fee_per_gas will be over next block base_fee /// adjustment for max priority fee picked up by sampling const ADJUST_MAX_PRIORITY_FEE: [f64; FEE_PER_GAS_LEVELS] = [1.0, 1.0, 1.0]; @@ -211,20 +214,21 @@ impl FeePerGasSimpleEstimator { pub fn history_percentiles() -> &'static [f64] { &Self::HISTORY_PERCENTILES } /// percentile for vector - fn percentile_of(v: &mut Vec, percent: f64) -> U256 { - v.sort(); + fn percentile_of(v: &Vec, percent: f64) -> U256 { + let mut v_mut = v.clone(); + v_mut.sort(); // validate bounds: let percent = if percent > 100.0 { 100.0 } else { percent }; let percent = if percent < 0.0 { 0.0 } else { percent }; - let value_pos = ((v.len() - 1) as f64 * percent / 100.0).round() as usize; - v[value_pos] + let value_pos = ((v_mut.len() - 1) as f64 * percent / 100.0).round() as usize; + v_mut[value_pos] } /// Estimate simplified gas priority fees based on fee history pub async fn estimate_fee_by_history(coin: &EthCoin) -> Web3RpcResult { - let res = coin + let res: Result = coin .eth_fee_history( U256::from(Self::history_depth()), BlockNumber::Latest, @@ -238,9 +242,13 @@ impl FeePerGasSimpleEstimator { } } + fn predict_base_fee(base_fees: &Vec) -> U256 { + Self::percentile_of(base_fees, Self::BASE_FEE_PERCENTILE) + } + fn priority_fee_for_level( level: PriorityLevelId, - base_fee: &BigDecimal, + base_fee: BigDecimal, fee_history: &FeeHistoryResult, ) -> Web3RpcResult { let level_i = level as usize; @@ -258,7 +266,7 @@ impl FeePerGasSimpleEstimator { }) .collect::>(); - let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::CALC_PERCENTILES[level_i]); + let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_i]); let max_priority_fee_per_gas = u256_to_big_decimal(max_priority_fee_per_gas, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); let max_fee_per_gas = base_fee @@ -269,19 +277,20 @@ impl FeePerGasSimpleEstimator { max_priority_fee_per_gas, max_fee_per_gas, min_wait_time: None, - max_wait_time: None, // TODO: maybe fill with some default values (and mark as uncertain)? + max_wait_time: None, // TODO: maybe fill with some default values (and mark them as uncertain)? }) } /// estimate priority fees by fee history fn calculate_with_history(fee_history: &FeeHistoryResult) -> Web3RpcResult { - let base_fee = *fee_history.base_fee_per_gas.first().unwrap_or(&U256::from(0)); - let base_fee = u256_to_big_decimal(base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); + let latest_base_fee = fee_history.base_fee_per_gas.first().cloned().unwrap_or_else(|| U256::from(0)); + let latest_base_fee = u256_to_big_decimal(latest_base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); + let predicted_base_fee = Self::predict_base_fee(&fee_history.base_fee_per_gas); Ok(FeePerGasEstimated { - base_fee: base_fee.clone(), - low: Self::priority_fee_for_level(PriorityLevelId::Low, &base_fee, fee_history)?, - medium: Self::priority_fee_for_level(PriorityLevelId::Medium, &base_fee, fee_history)?, - high: Self::priority_fee_for_level(PriorityLevelId::High, &base_fee, fee_history)?, + base_fee: u256_to_big_decimal(predicted_base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)), + low: Self::priority_fee_for_level(PriorityLevelId::Low, latest_base_fee.clone(), fee_history)?, + medium: Self::priority_fee_for_level(PriorityLevelId::Medium, latest_base_fee.clone(), fee_history)?, + high: Self::priority_fee_for_level(PriorityLevelId::High, latest_base_fee.clone(), fee_history)?, source: EstimationSource::Simple, units: EstimationUnits::Gwei, base_fee_trend: String::default(), From 908605292808335677e19315d65f7d55196ae8bf Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 22 Mar 2024 19:59:26 +0500 Subject: [PATCH 27/71] always use high priority fee for gas estimation --- mm2src/coins/eth.rs | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6b2bdcffd9..43f8e6e4e9 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2390,7 +2390,7 @@ async fn sign_transaction_with_keypair( let (nonce, web3_instances_with_latest_nonce) = try_tx_s!(coin.clone().get_addr_nonce(coin.my_address).compat().await); status.status(tags!(), "get_gas_price…"); - let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option().compat().await); + let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()).compat().await); let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, action, value, data); let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, &pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; @@ -2446,7 +2446,7 @@ async fn sign_and_send_transaction_with_metamask( Action::Call(to) => Some(to), }; - let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option().compat().await); + let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()).compat().await); let tx_to_send = TransactionRequest { from: coin.my_address, @@ -4252,7 +4252,8 @@ impl EthCoin { /// because [`CallRequest::from`] is set to [`EthCoinImpl::my_address`]. fn estimate_gas_for_contract_call(&self, contract_addr: Address, call_data: Bytes) -> Web3RpcFut { let coin = self.clone(); - Box::new(coin.get_swap_pay_for_gas_option().and_then(move |pay_for_gas_option| { + let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(self.get_swap_transaction_fee_policy()); + Box::new(coin.get_swap_pay_for_gas_option(fee_policy_for_estimate).and_then(move |pay_for_gas_option| { let eth_value = U256::zero(); let estimate_gas_req = CallRequest { value: Some(eth_value), @@ -4827,7 +4828,7 @@ impl EthCoin { pub async fn get_watcher_reward_amount(&self, wait_until: u64) -> Result> { let pay_for_gas_option = - repeatable!(async { self.get_swap_pay_for_gas_option().compat().await.retry_on_err() }) + repeatable!(async { self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()).compat().await.retry_on_err() }) .until_s(wait_until) .repeat_every_secs(10.) .await @@ -4911,10 +4912,10 @@ impl EthCoin { .await } - fn get_swap_pay_for_gas_option(&self) -> Web3RpcFut { + fn get_swap_pay_for_gas_option(&self, swap_fee_policy: SwapTxFeePolicy) -> Web3RpcFut { let coin = self.clone(); let fut = async move { - match coin.get_swap_transaction_fee_policy() { + match swap_fee_policy { SwapTxFeePolicy::Internal => { let gas_price = coin.get_gas_price().compat().await?; Ok(PayForGasOption::Legacy(LegacyGasPrice { gas_price })) @@ -4922,7 +4923,7 @@ impl EthCoin { SwapTxFeePolicy::Low | SwapTxFeePolicy::Medium | SwapTxFeePolicy::High => { let fee_per_gas = coin.get_eip1559_gas_fee().compat().await?; let pay_result = (|| -> MmResult { - match coin.get_swap_transaction_fee_policy() { + match swap_fee_policy { SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas: wei_from_big_decimal( &fee_per_gas.low.max_priority_fee_per_gas, @@ -5309,10 +5310,10 @@ impl MmCoin for EthCoin { fn get_trade_fee(&self) -> Box + Send> { let coin = self.clone(); Box::new( - self.get_swap_pay_for_gas_option() + self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()) .map_err(|e| e.to_string()) .and_then(move |pay_for_gas_option| { - let fee = calc_total_fee(U256::from(ETH_GAS), &pay_for_gas_option).map_err(|e| e.to_string())?; + let fee = calc_total_fee(U256::from(ETH_MAX_TRADE_GAS), &pay_for_gas_option).map_err(|e| e.to_string())?; let fee_coin = match &coin.coin_type { EthCoinType::Eth => &coin.ticker, EthCoinType::Erc20 { platform, .. } => platform, @@ -5332,7 +5333,7 @@ impl MmCoin for EthCoin { value: TradePreimageValue, stage: FeeApproxStage, ) -> TradePreimageResult { - let pay_for_gas_option = self.get_swap_pay_for_gas_option().compat().await?; + let pay_for_gas_option = self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()).compat().await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let gas_limit = match self.coin_type { EthCoinType::Eth => { @@ -5385,7 +5386,7 @@ impl MmCoin for EthCoin { fn get_receiver_trade_fee(&self, stage: FeeApproxStage) -> TradePreimageFut { let coin = self.clone(); let fut = async move { - let pay_for_gas_option = coin.get_swap_pay_for_gas_option().compat().await?; + let pay_for_gas_option = coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()).compat().await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let total_fee = calc_total_fee(U256::from(ETH_GAS), &pay_for_gas_option)?; let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; @@ -5423,7 +5424,8 @@ impl MmCoin for EthCoin { EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; - let pay_for_gas_option = self.get_swap_pay_for_gas_option().compat().await?; + let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(self.get_swap_transaction_fee_policy()); + let pay_for_gas_option = self.get_swap_pay_for_gas_option(fee_policy_for_estimate).compat().await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let estimate_gas_req = CallRequest { value: Some(eth_value), @@ -6341,6 +6343,16 @@ fn tx_builder_with_pay_for_gas_option( Ok(tx_builder) } +/// convert fee policy for gas estimate requests +fn get_swap_fee_policy_for_estimate(swap_fee_policy :SwapTxFeePolicy) -> SwapTxFeePolicy { + match swap_fee_policy { + SwapTxFeePolicy::Internal => SwapTxFeePolicy::Internal, + // always use 'high' for estimate to avoid max_fee_per_gas less than base_fee errors: + SwapTxFeePolicy::Low | SwapTxFeePolicy::Medium | SwapTxFeePolicy::High => SwapTxFeePolicy::High, + SwapTxFeePolicy::Unsupported => SwapTxFeePolicy::Unsupported, + } +} + fn call_request_with_pay_for_gas_option(call_request: CallRequest, pay_for_gas_option: PayForGasOption) -> CallRequest { match pay_for_gas_option { PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => CallRequest { From 90e585b14cdb6b22c3f0385038da96d6c7bd5a19 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 22 Mar 2024 20:03:00 +0500 Subject: [PATCH 28/71] add different eth gas limit consts for swap operations --- mm2src/coins/eth.rs | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 43f8e6e4e9..7470e1d038 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -172,7 +172,18 @@ const GAS_PRICE_APPROXIMATION_PERCENT_ON_ORDER_ISSUE: u64 = 5; /// - it may increase by 3% during the swap. const GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE: u64 = 7; -const ETH_GAS: u64 = 150_000; +/// Heuristic gas limits for swap operations (including extra margin value for possible changes in opcodes gas) +mod swap_gas { + pub(crate) const ETH_PAYMENT: u64 = 65_000; // real values are approx 48,6K by etherscan + pub(crate) const ERC20_PAYMENT: u64 = 120_000; // real values 98,9K + pub(crate) const ETH_RECEIVER_SPEND: u64 = 65_000; // real values 40,7K + pub(crate) const ERC20_RECEIVER_SPEND: u64 = 120_000; // real values 72,8K + pub(crate) const ETH_SENDER_REFUND: u64 = 100_000; + pub(crate) const ERC20_SENDER_REFUND: u64 = 150_000; +} + +/// trade tx gas limit max for all operations +const ETH_MAX_TRADE_GAS: u64 = 150_000; /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; @@ -3468,7 +3479,7 @@ impl EthCoin { let trade_amount = try_tx_fus!(wei_from_big_decimal(&args.amount, self.decimals)); let time_lock = U256::from(args.time_lock); - let gas = U256::from(ETH_GAS); + let secret_hash = if args.secret_hash.len() == 32 { ripemd160(args.secret_hash).to_vec() @@ -3506,7 +3517,7 @@ impl EthCoin { Token::Uint(time_lock), ])), }; - + let gas = U256::from(swap_gas::ETH_PAYMENT); self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas) }, EthCoinType::Erc20 { @@ -3577,6 +3588,7 @@ impl EthCoin { }; let wait_for_required_allowance_until = args.wait_for_confirmation_until; + let gas = U256::from(swap_gas::ERC20_PAYMENT); let arc = self.clone(); Box::new(allowance_fut.and_then(move |allowed| -> EthTxFut { @@ -3686,7 +3698,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ERC20_SENDER_REFUND), ) }), ) @@ -3734,7 +3746,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ETH_RECEIVER_SPEND), ) }), ) @@ -3808,7 +3820,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ERC20_RECEIVER_SPEND), ) }), ) @@ -3859,7 +3871,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ETH_SENDER_REFUND), ) }), ) @@ -3929,7 +3941,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ERC20_SENDER_REFUND), ) }), ) @@ -3981,7 +3993,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ETH_RECEIVER_SPEND), ) }), ) @@ -4052,7 +4064,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ERC20_RECEIVER_SPEND), ) }), ) @@ -4104,7 +4116,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + U256::from(swap_gas::ETH_SENDER_REFUND), ) }), ) @@ -5388,13 +5400,14 @@ impl MmCoin for EthCoin { let fut = async move { let pay_for_gas_option = coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()).compat().await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); - let total_fee = calc_total_fee(U256::from(ETH_GAS), &pay_for_gas_option)?; - let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; - let fee_coin = match &coin.coin_type { - EthCoinType::Eth => &coin.ticker, - EthCoinType::Erc20 { platform, .. } => platform, + let (fee_coin, total_fee) = match &coin.coin_type { + EthCoinType::Eth => + (&coin.ticker, calc_total_fee(U256::from(swap_gas::ETH_RECEIVER_SPEND), &pay_for_gas_option)?), + EthCoinType::Erc20 { platform, .. } => + (platform, calc_total_fee(U256::from(swap_gas::ERC20_RECEIVER_SPEND), &pay_for_gas_option)?), EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; + let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; Ok(TradeFee { coin: fee_coin.into(), amount: amount.into(), From 61b8fdc23749d9b04714d256ea3de2c4de0e742a Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 22 Mar 2024 20:09:19 +0500 Subject: [PATCH 29/71] add include_refund_fee param to show only required fee for lock funds swap step --- mm2src/coins/eth.rs | 24 +++++++++++++------ mm2src/coins/eth/eth_tests.rs | 13 +++++----- mm2src/coins/lightning.rs | 1 + mm2src/coins/lp_coins.rs | 3 ++- mm2src/coins/qrc20.rs | 17 ++++++++----- mm2src/coins/qrc20/qrc20_tests.rs | 4 +++- mm2src/coins/solana.rs | 1 + mm2src/coins/solana/spl.rs | 1 + mm2src/coins/tendermint/tendermint_coin.rs | 1 + mm2src/coins/tendermint/tendermint_token.rs | 1 + mm2src/coins/test_coin.rs | 1 + mm2src/coins/utxo/bch.rs | 1 + mm2src/coins/utxo/qtum.rs | 1 + mm2src/coins/utxo/slp.rs | 1 + mm2src/coins/utxo/utxo_standard.rs | 1 + mm2src/coins/utxo/utxo_tests.rs | 4 +++- mm2src/coins/z_coin.rs | 1 + mm2src/mm2_main/src/lp_swap/maker_swap.rs | 8 +++---- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 2 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 8 +++---- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 2 +- .../tests/docker_tests/qrc20_tests.rs | 1 + 22 files changed, 65 insertions(+), 32 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 7470e1d038..ab99dbb86f 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5344,15 +5344,21 @@ impl MmCoin for EthCoin { &self, value: TradePreimageValue, stage: FeeApproxStage, + include_refund_fee: bool, ) -> TradePreimageResult { let pay_for_gas_option = self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()).compat().await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); - let gas_limit = match self.coin_type { + let mut gas_limit = match self.coin_type { EthCoinType::Eth => { - // this gas_limit includes gas for `ethPayment` and `senderRefund` contract calls - U256::from(300_000) + // this gas_limit includes gas for `ethPayment` and optionally `senderRefund` contract calls + if include_refund_fee { + U256::from(swap_gas::ETH_PAYMENT) + } else { + U256::from(swap_gas::ETH_PAYMENT) + U256::from(swap_gas::ETH_SENDER_REFUND) + } }, EthCoinType::Erc20 { token_addr, .. } => { + let gas = U256::from(swap_gas::ERC20_PAYMENT); let value = match value { TradePreimageValue::Exact(value) | TradePreimageValue::UpperBound(value) => { wei_from_big_decimal(&value, self.decimals)? @@ -5371,16 +5377,20 @@ impl MmCoin for EthCoin { .compat() .await?; - // this gas_limit includes gas for `approve`, `erc20Payment` and `senderRefund` contract calls - U256::from(300_000) + approve_gas_limit + // this gas_limit includes gas for `approve`, `erc20Payment` contract calls + gas + approve_gas_limit } else { - // this gas_limit includes gas for `erc20Payment` and `senderRefund` contract calls - U256::from(300_000) + // this gas_limit includes gas for `erc20Payment` contract calls + gas } }, EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; + if include_refund_fee { + gas_limit += U256::from(swap_gas::ERC20_SENDER_REFUND); // add 'senderRefund' gas if requested + } + let total_fee = calc_total_fee(gas_limit, &pay_for_gas_option)?; let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; let fee_coin = match &self.coin_type { diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index e8f2680486..5385135db4 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -604,25 +604,26 @@ fn get_sender_trade_preimage() { let actual = block_on(coin.get_sender_trade_fee( TradePreimageValue::UpperBound(150.into()), FeeApproxStage::WithoutApprox, + true, )) .expect("!get_sender_trade_fee"); let expected = expected_fee(GAS_PRICE); assert_eq!(actual, expected); let value = u256_to_big_decimal(100.into(), 18).expect("!u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue)) + let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue, true)) .expect("!get_sender_trade_fee"); let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE); assert_eq!(actual, expected); let value = u256_to_big_decimal(1.into(), 18).expect("!u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::StartSwap)) + let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::StartSwap, true)) .expect("!get_sender_trade_fee"); let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_START_SWAP); assert_eq!(actual, expected); let value = u256_to_big_decimal(10000000000u64.into(), 18).expect("!u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage)) + let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage, true)) .expect("!get_sender_trade_fee"); let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE); assert_eq!(actual, expected); @@ -674,7 +675,7 @@ fn get_erc20_sender_trade_preimage() { // value is greater than allowance unsafe { ALLOWANCE = 999 }; let value = u256_to_big_decimal(1000.into(), 18).expect("u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::UpperBound(value), FeeApproxStage::StartSwap)) + let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::UpperBound(value), FeeApproxStage::StartSwap, true)) .expect("!get_sender_trade_fee"); unsafe { assert!(ESTIMATE_GAS_CALLED); @@ -688,7 +689,7 @@ fn get_erc20_sender_trade_preimage() { // value is allowed unsafe { ALLOWANCE = 1000 }; let value = u256_to_big_decimal(999.into(), 18).expect("u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue)) + let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue, true)) .expect("!get_sender_trade_fee"); unsafe { assert!(!ESTIMATE_GAS_CALLED) } assert_eq!( @@ -699,7 +700,7 @@ fn get_erc20_sender_trade_preimage() { // value is greater than allowance unsafe { ALLOWANCE = 1000 }; let value = u256_to_big_decimal(1500.into(), 18).expect("u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage)) + let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage, true)) .expect("!get_sender_trade_fee"); unsafe { assert!(ESTIMATE_GAS_CALLED); diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index ea83401fe8..4b1d9b6bcd 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -1330,6 +1330,7 @@ impl MmCoin for LightningCoin { &self, _value: TradePreimageValue, _stage: FeeApproxStage, + _include_refund_fee: bool, ) -> TradePreimageResult { Ok(TradeFee { coin: self.ticker().to_owned(), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index ef3b2f4a6b..aa6f276a07 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -3079,11 +3079,12 @@ pub trait MmCoin: /// Get fee to be paid per 1 swap transaction fn get_trade_fee(&self) -> Box + Send>; - /// Get fee to be paid by sender per whole swap using the sending value and check if the wallet has sufficient balance to pay the fee. + /// Get fee to be paid by sender per whole swap (including possible refund) using the sending value and check if the wallet has sufficient balance to pay the fee. async fn get_sender_trade_fee( &self, value: TradePreimageValue, stage: FeeApproxStage, + include_refund_fee: bool, ) -> TradePreimageResult; /// Get fee to be paid by receiver per whole swap and check if the wallet has sufficient balance to pay the fee. diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index ba33b4a23b..7ceb61a944 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1367,6 +1367,7 @@ impl MmCoin for Qrc20Coin { &self, value: TradePreimageValue, stage: FeeApproxStage, + include_refund_fee: bool, ) -> TradePreimageResult { let decimals = self.utxo.decimals; // pass the dummy params @@ -1398,14 +1399,18 @@ impl MmCoin for Qrc20Coin { .await? }; - let sender_refund_fee = { - let sender_refund_output = - self.sender_refund_output(&self.swap_contract_address, swap_id, value, secret_hash, receiver_addr)?; - self.preimage_trade_fee_required_to_send_outputs(vec![sender_refund_output], &stage) - .await? + let total_fee = if include_refund_fee { + let sender_refund_fee = { + let sender_refund_output = + self.sender_refund_output(&self.swap_contract_address, swap_id, value, secret_hash, receiver_addr)?; + self.preimage_trade_fee_required_to_send_outputs(vec![sender_refund_output], &stage) + .await? + }; + erc20_payment_fee + sender_refund_fee + } else { + erc20_payment_fee }; - let total_fee = erc20_payment_fee + sender_refund_fee; Ok(TradeFee { coin: self.platform.clone(), amount: total_fee.into(), diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 99e48690e7..5e5876fcef 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -838,6 +838,7 @@ fn test_sender_trade_preimage_with_allowance() { let actual = block_on(coin.get_sender_trade_fee( TradePreimageValue::Exact(BigDecimal::try_from(2.5).unwrap()), FeeApproxStage::WithoutApprox, + true, )) .expect("!get_sender_trade_fee"); // the expected fee should not include any `approve` contract call @@ -851,6 +852,7 @@ fn test_sender_trade_preimage_with_allowance() { let actual = block_on(coin.get_sender_trade_fee( TradePreimageValue::Exact(BigDecimal::try_from(3.5).unwrap()), FeeApproxStage::WithoutApprox, + true, )) .expect("!get_sender_trade_fee"); // two `approve` contract calls should be included into the expected trade fee @@ -906,7 +908,7 @@ fn test_get_sender_trade_fee_preimage_for_correct_ticker() { )) .unwrap(); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(0.into()), FeeApproxStage::OrderIssue)) + let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(0.into()), FeeApproxStage::OrderIssue, true)) .err() .unwrap() .into_inner(); diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index fdbc0d8b9f..30ce7252e8 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -750,6 +750,7 @@ impl MmCoin for SolanaCoin { &self, _value: TradePreimageValue, _stage: FeeApproxStage, + _include_refund_fee: bool, ) -> TradePreimageResult { unimplemented!() } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index bc38b4b479..5dde457637 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -544,6 +544,7 @@ impl MmCoin for SplToken { &self, _value: TradePreimageValue, _stage: FeeApproxStage, + _include_refund_fee: bool, ) -> TradePreimageResult { unimplemented!() } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index dcb5fe2a66..45613a50d2 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2145,6 +2145,7 @@ impl MmCoin for TendermintCoin { &self, value: TradePreimageValue, _stage: FeeApproxStage, + _include_refund_fee: bool, ) -> TradePreimageResult { let amount = match value { TradePreimageValue::Exact(decimal) | TradePreimageValue::UpperBound(decimal) => decimal, diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index c2480381bc..b8dfdd1ae9 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -822,6 +822,7 @@ impl MmCoin for TendermintToken { &self, value: TradePreimageValue, _stage: FeeApproxStage, + _include_refund_fee: bool, ) -> TradePreimageResult { let amount = match value { TradePreimageValue::Exact(decimal) | TradePreimageValue::UpperBound(decimal) => decimal, diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 725cf97b74..bdbb18abb2 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -353,6 +353,7 @@ impl MmCoin for TestCoin { &self, _value: TradePreimageValue, _stage: FeeApproxStage, + _include_refund_fee: bool, ) -> TradePreimageResult { unimplemented!() } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 2801ce2388..974ff1984c 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1261,6 +1261,7 @@ impl MmCoin for BchCoin { &self, value: TradePreimageValue, stage: FeeApproxStage, + _include_refund_fee: bool, // refund fee is taken from swap output ) -> TradePreimageResult { utxo_common::get_sender_trade_fee(self, value, stage).await } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 2d3c21160e..5a1395c1fe 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -936,6 +936,7 @@ impl MmCoin for QtumCoin { &self, value: TradePreimageValue, stage: FeeApproxStage, + _include_refund_fee: bool, // refund fee is taken from swap output ) -> TradePreimageResult { utxo_common::get_sender_trade_fee(self, value, stage).await } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index d8ebead6e5..6d29e948e9 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -1775,6 +1775,7 @@ impl MmCoin for SlpToken { &self, value: TradePreimageValue, stage: FeeApproxStage, + _include_refund_fee: bool, // refund fee is taken from swap output ) -> TradePreimageResult { let slp_amount = match value { TradePreimageValue::Exact(decimal) | TradePreimageValue::UpperBound(decimal) => { diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 8088bec24a..bcf0ecac12 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -950,6 +950,7 @@ impl MmCoin for UtxoStandardCoin { &self, value: TradePreimageValue, stage: FeeApproxStage, + _include_refund_fee: bool, // refund fee is taken from swap output ) -> TradePreimageResult { utxo_common::get_sender_trade_fee(self, value, stage).await } diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index a40aaec887..4782b07d83 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -2514,6 +2514,7 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { let fee1 = block_on(coin.get_sender_trade_fee( TradePreimageValue::UpperBound(my_balance.clone()), FeeApproxStage::WithoutApprox, + false, )) .expect("!get_sender_trade_fee"); @@ -2522,6 +2523,7 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { let fee2 = block_on(coin.get_sender_trade_fee( TradePreimageValue::Exact(value_without_fee), FeeApproxStage::WithoutApprox, + false, )) .expect("!get_sender_trade_fee"); assert_eq!(fee1, fee2); @@ -2529,7 +2531,7 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { // `2.21934443` value was obtained as a result of executing the `max_taker_vol` RPC call for this wallet let max_taker_vol = BigDecimal::from_str("2.21934443").expect("!BigDecimal::from_str"); let fee3 = - block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(max_taker_vol), FeeApproxStage::WithoutApprox)) + block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(max_taker_vol), FeeApproxStage::WithoutApprox, false)) .expect("!get_sender_trade_fee"); assert_eq!(fee1, fee3); } diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 44d9d1b99f..69f4358a24 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1790,6 +1790,7 @@ impl MmCoin for ZCoin { &self, _value: TradePreimageValue, _stage: FeeApproxStage, + _include_refund_fee: bool, ) -> TradePreimageResult { Ok(TradeFee { coin: self.ticker().to_owned(), diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index a3fe3202b7..b211046a96 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -474,7 +474,7 @@ impl MakerSwap { // do not use self.r().data here as it is not initialized at this step yet let preimage_value = TradePreimageValue::Exact(self.maker_amount.clone()); let stage = FeeApproxStage::StartSwap; - let get_sender_trade_fee_fut = self.maker_coin.get_sender_trade_fee(preimage_value, stage); + let get_sender_trade_fee_fut = self.maker_coin.get_sender_trade_fee(preimage_value, stage, false); let maker_payment_trade_fee = match get_sender_trade_fee_fut.await { Ok(fee) => fee, Err(e) => { @@ -2177,7 +2177,7 @@ pub async fn check_balance_for_maker_swap( None => { let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); let maker_payment_trade_fee = my_coin - .get_sender_trade_fee(preimage_value, stage) + .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to check balance .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let taker_payment_spend_trade_fee = other_coin @@ -2231,7 +2231,7 @@ pub async fn maker_swap_trade_preimage( let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); let base_coin_fee = base_coin - .get_sender_trade_fee(preimage_value, FeeApproxStage::TradePreimage) + .get_sender_trade_fee(preimage_value, FeeApproxStage::TradePreimage, false) .await .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, base_coin_ticker))?; let rel_coin_fee = rel_coin @@ -2313,7 +2313,7 @@ pub async fn calc_max_maker_vol( let preimage_value = TradePreimageValue::UpperBound(volume.to_decimal()); let trade_fee = coin - .get_sender_trade_fee(preimage_value, stage) + .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to get max trade amount .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, ticker))?; diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 8a8bc44d0f..e401e96692 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -812,7 +812,7 @@ impl fee, diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index d64fa7ab50..793816a01e 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -1010,7 +1010,7 @@ impl TakerSwap { )])) }, }; - let get_sender_trade_fee_fut = self.taker_coin.get_sender_trade_fee(preimage_value, stage); + let get_sender_trade_fee_fut = self.taker_coin.get_sender_trade_fee(preimage_value, stage, false); let taker_payment_trade_fee = match get_sender_trade_fee_fut.await { Ok(fee) => fee, Err(e) => { @@ -2368,7 +2368,7 @@ pub async fn check_balance_for_taker_swap( .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); let taker_payment_trade_fee = my_coin - .get_sender_trade_fee(preimage_value, stage) + .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to check balance for swap .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let maker_payment_spend_trade_fee = other_coin @@ -2463,7 +2463,7 @@ pub async fn taker_swap_trade_preimage( let preimage_value = TradePreimageValue::Exact(my_coin_volume.to_decimal()); let my_coin_trade_fee = my_coin - .get_sender_trade_fee(preimage_value, stage) + .get_sender_trade_fee(preimage_value, stage, false) .await .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, my_coin_ticker))?; let other_coin_trade_fee = other_coin @@ -2588,7 +2588,7 @@ pub async fn calc_max_taker_vol( let max_possible = &balance - &locked; let preimage_value = TradePreimageValue::UpperBound(max_possible.to_decimal()); let max_trade_fee = coin - .get_sender_trade_fee(preimage_value, stage) + .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to get max trade amount .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin))?; diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index adc7e00615..6a29e3dee6 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -929,7 +929,7 @@ impl fee, diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 7f7dea8fdb..ba7126195b 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -1052,6 +1052,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let max_trade_fee = block_on(coin.get_sender_trade_fee( TradePreimageValue::UpperBound(qtum_balance.clone()), FeeApproxStage::TradePreimage, + true, )) .expect("!get_sender_trade_fee"); let max_trade_fee = max_trade_fee.amount.to_decimal(); From 220db97c385344f8e84ee3329f093a2902b4b4d5 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 22 Mar 2024 21:57:59 +0500 Subject: [PATCH 30/71] fix get_eip1559_gas_fee after rebase --- mm2src/coins/eth.rs | 56 ++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index ab99dbb86f..64069f4386 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4894,34 +4894,38 @@ impl EthCoin { } /// Get gas base fee and suggest priority tip fees for the next block (see EIP-1559) - pub async fn get_eip1559_gas_fee(&self) -> Result> { - let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(self); - let ctx = - MmArc::from_weak(&self.ctx).ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; - let gas_api_conf = ctx.conf["gas_api"].clone(); - if gas_api_conf.is_null() { - return history_estimator_fut + pub fn get_eip1559_gas_fee(&self) -> Web3RpcFut { + let coin = self.clone(); + let fut = async move { + let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(&coin); + let ctx = + MmArc::from_weak(&coin.ctx).ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; + let gas_api_conf = ctx.conf["gas_api"].clone(); + if gas_api_conf.is_null() { + return history_estimator_fut + .await + .map_err(|e| MmError::new(Web3RpcError::Internal(e.to_string()))); + } + let gas_api_conf: GasApiConfig = json::from_value(gas_api_conf) + .map_err(|e| MmError::new(Web3RpcError::InvalidGasApiConfig(e.to_string())))?; + let provider_estimator_fut = match gas_api_conf.provider { + GasApiProvider::Infura => InfuraGasApiCaller::fetch_infura_fee_estimation(&gas_api_conf.url).boxed(), + GasApiProvider::Blocknative => { + BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(&gas_api_conf.url).boxed() + }, + }; + provider_estimator_fut + .or_else(|provider_estimator_err| { + history_estimator_fut.map_err(move |history_estimator_err| { + MmError::new(Web3RpcError::Internal(format!( + "All gas api requests failed, provider estimator error: {}, history estimator error: {}", + provider_estimator_err, history_estimator_err + ))) + }) + }) .await - .map_err(|e| MmError::new(Web3RpcError::Internal(e.to_string()))); - } - let gas_api_conf: GasApiConfig = json::from_value(gas_api_conf) - .map_err(|e| MmError::new(Web3RpcError::InvalidGasApiConfig(e.to_string())))?; - let provider_estimator_fut = match gas_api_conf.provider { - GasApiProvider::Infura => InfuraGasApiCaller::fetch_infura_fee_estimation(&gas_api_conf.url).boxed(), - GasApiProvider::Blocknative => { - BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(&gas_api_conf.url).boxed() - }, }; - provider_estimator_fut - .or_else(|provider_estimator_err| { - history_estimator_fut.map_err(move |history_estimator_err| { - MmError::new(Web3RpcError::Internal(format!( - "All gas api requests failed, provider estimator error: {}, history estimator error: {}", - provider_estimator_err, history_estimator_err - ))) - }) - }) - .await + Box::new(fut.boxed().compat()) } fn get_swap_pay_for_gas_option(&self, swap_fee_policy: SwapTxFeePolicy) -> Web3RpcFut { From 24f76441a26a884e9d4d1f1cf343055884c68d4c Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 22 Mar 2024 22:48:16 +0500 Subject: [PATCH 31/71] fix clippy & fmt, fix missed param in get_sender_trade_fee tests --- mm2src/coins/eth.rs | 100 ++++++++++++++++++---------- mm2src/coins/eth/eip1559_gas_fee.rs | 26 +++++--- mm2src/coins/eth/eth_tests.rs | 46 +++++++------ mm2src/coins/qrc20/qrc20_tests.rs | 11 +-- mm2src/coins/utxo/utxo_tests.rs | 9 ++- 5 files changed, 118 insertions(+), 74 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 64069f4386..0e5d3a9e45 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -172,11 +172,11 @@ const GAS_PRICE_APPROXIMATION_PERCENT_ON_ORDER_ISSUE: u64 = 5; /// - it may increase by 3% during the swap. const GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE: u64 = 7; -/// Heuristic gas limits for swap operations (including extra margin value for possible changes in opcodes gas) +/// Heuristic gas limits for swap operations (including extra margin value for possible changes in opcodes gas) mod swap_gas { pub(crate) const ETH_PAYMENT: u64 = 65_000; // real values are approx 48,6K by etherscan pub(crate) const ERC20_PAYMENT: u64 = 120_000; // real values 98,9K - pub(crate) const ETH_RECEIVER_SPEND: u64 = 65_000; // real values 40,7K + pub(crate) const ETH_RECEIVER_SPEND: u64 = 65_000; // real values 40,7K pub(crate) const ERC20_RECEIVER_SPEND: u64 = 120_000; // real values 72,8K pub(crate) const ETH_SENDER_REFUND: u64 = 100_000; pub(crate) const ERC20_SENDER_REFUND: u64 = 150_000; @@ -2401,7 +2401,11 @@ async fn sign_transaction_with_keypair( let (nonce, web3_instances_with_latest_nonce) = try_tx_s!(coin.clone().get_addr_nonce(coin.my_address).compat().await); status.status(tags!(), "get_gas_price…"); - let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()).compat().await); + let pay_for_gas_option = try_tx_s!( + coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) + .compat() + .await + ); let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, action, value, data); let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, &pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; @@ -2457,7 +2461,11 @@ async fn sign_and_send_transaction_with_metamask( Action::Call(to) => Some(to), }; - let pay_for_gas_option = try_tx_s!(coin.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()).compat().await); + let pay_for_gas_option = try_tx_s!( + coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) + .compat() + .await + ); let tx_to_send = TransactionRequest { from: coin.my_address, @@ -3479,7 +3487,6 @@ impl EthCoin { let trade_amount = try_tx_fus!(wei_from_big_decimal(&args.amount, self.decimals)); let time_lock = U256::from(args.time_lock); - let secret_hash = if args.secret_hash.len() == 32 { ripemd160(args.secret_hash).to_vec() @@ -4265,21 +4272,24 @@ impl EthCoin { fn estimate_gas_for_contract_call(&self, contract_addr: Address, call_data: Bytes) -> Web3RpcFut { let coin = self.clone(); let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(self.get_swap_transaction_fee_policy()); - Box::new(coin.get_swap_pay_for_gas_option(fee_policy_for_estimate).and_then(move |pay_for_gas_option| { - let eth_value = U256::zero(); - let estimate_gas_req = CallRequest { - value: Some(eth_value), - data: Some(call_data), - from: Some(coin.my_address), - to: Some(contract_addr), - ..CallRequest::default() - }; - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - let estimate_gas_req = call_request_with_pay_for_gas_option(estimate_gas_req, pay_for_gas_option); - coin.estimate_gas_wrapper(estimate_gas_req) - .map_to_mm_fut(Web3RpcError::from) - })) + Box::new( + coin.get_swap_pay_for_gas_option(fee_policy_for_estimate) + .and_then(move |pay_for_gas_option| { + let eth_value = U256::zero(); + let estimate_gas_req = CallRequest { + value: Some(eth_value), + data: Some(call_data), + from: Some(coin.my_address), + to: Some(contract_addr), + ..CallRequest::default() + }; + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + let estimate_gas_req = call_request_with_pay_for_gas_option(estimate_gas_req, pay_for_gas_option); + coin.estimate_gas_wrapper(estimate_gas_req) + .map_to_mm_fut(Web3RpcError::from) + }), + ) } fn eth_balance(&self) -> BalanceFut { @@ -4839,12 +4849,16 @@ impl EthCoin { } pub async fn get_watcher_reward_amount(&self, wait_until: u64) -> Result> { - let pay_for_gas_option = - repeatable!(async { self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()).compat().await.retry_on_err() }) - .until_s(wait_until) - .repeat_every_secs(10.) + let pay_for_gas_option = repeatable!(async { + self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()) + .compat() .await - .map_err(|_| WatcherRewardError::RPCError("Error getting the gas price".to_string()))?; + .retry_on_err() + }) + .until_s(wait_until) + .repeat_every_secs(10.) + .await + .map_err(|_| WatcherRewardError::RPCError("Error getting the gas price".to_string()))?; let gas_cost_wei = calc_total_fee(U256::from(REWARD_GAS_AMOUNT), &pay_for_gas_option) .map_err(|e| WatcherRewardError::InternalError(e.to_string()))?; @@ -4898,8 +4912,8 @@ impl EthCoin { let coin = self.clone(); let fut = async move { let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(&coin); - let ctx = - MmArc::from_weak(&coin.ctx).ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; + let ctx = MmArc::from_weak(&coin.ctx) + .ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; let gas_api_conf = ctx.conf["gas_api"].clone(); if gas_api_conf.is_null() { return history_estimator_fut @@ -5329,7 +5343,8 @@ impl MmCoin for EthCoin { self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()) .map_err(|e| e.to_string()) .and_then(move |pay_for_gas_option| { - let fee = calc_total_fee(U256::from(ETH_MAX_TRADE_GAS), &pay_for_gas_option).map_err(|e| e.to_string())?; + let fee = calc_total_fee(U256::from(ETH_MAX_TRADE_GAS), &pay_for_gas_option) + .map_err(|e| e.to_string())?; let fee_coin = match &coin.coin_type { EthCoinType::Eth => &coin.ticker, EthCoinType::Erc20 { platform, .. } => platform, @@ -5350,7 +5365,10 @@ impl MmCoin for EthCoin { stage: FeeApproxStage, include_refund_fee: bool, ) -> TradePreimageResult { - let pay_for_gas_option = self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()).compat().await?; + let pay_for_gas_option = self + .get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()) + .compat() + .await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let mut gas_limit = match self.coin_type { EthCoinType::Eth => { @@ -5412,13 +5430,20 @@ impl MmCoin for EthCoin { fn get_receiver_trade_fee(&self, stage: FeeApproxStage) -> TradePreimageFut { let coin = self.clone(); let fut = async move { - let pay_for_gas_option = coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()).compat().await?; + let pay_for_gas_option = coin + .get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) + .compat() + .await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let (fee_coin, total_fee) = match &coin.coin_type { - EthCoinType::Eth => - (&coin.ticker, calc_total_fee(U256::from(swap_gas::ETH_RECEIVER_SPEND), &pay_for_gas_option)?), - EthCoinType::Erc20 { platform, .. } => - (platform, calc_total_fee(U256::from(swap_gas::ERC20_RECEIVER_SPEND), &pay_for_gas_option)?), + EthCoinType::Eth => ( + &coin.ticker, + calc_total_fee(U256::from(swap_gas::ETH_RECEIVER_SPEND), &pay_for_gas_option)?, + ), + EthCoinType::Erc20 { platform, .. } => ( + platform, + calc_total_fee(U256::from(swap_gas::ERC20_RECEIVER_SPEND), &pay_for_gas_option)?, + ), EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; @@ -5452,7 +5477,10 @@ impl MmCoin for EthCoin { }; let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(self.get_swap_transaction_fee_policy()); - let pay_for_gas_option = self.get_swap_pay_for_gas_option(fee_policy_for_estimate).compat().await?; + let pay_for_gas_option = self + .get_swap_pay_for_gas_option(fee_policy_for_estimate) + .compat() + .await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let estimate_gas_req = CallRequest { value: Some(eth_value), @@ -6371,7 +6399,7 @@ fn tx_builder_with_pay_for_gas_option( } /// convert fee policy for gas estimate requests -fn get_swap_fee_policy_for_estimate(swap_fee_policy :SwapTxFeePolicy) -> SwapTxFeePolicy { +fn get_swap_fee_policy_for_estimate(swap_fee_policy: SwapTxFeePolicy) -> SwapTxFeePolicy { match swap_fee_policy { SwapTxFeePolicy::Internal => SwapTxFeePolicy::Internal, // always use 'high' for estimate to avoid max_fee_per_gas less than base_fee errors: diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index 19e120e1cf..bceb321540 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -214,8 +214,8 @@ impl FeePerGasSimpleEstimator { pub fn history_percentiles() -> &'static [f64] { &Self::HISTORY_PERCENTILES } /// percentile for vector - fn percentile_of(v: &Vec, percent: f64) -> U256 { - let mut v_mut = v.clone(); + fn percentile_of(v: &[U256], percent: f64) -> U256 { + let mut v_mut = v.to_owned(); v_mut.sort(); // validate bounds: @@ -242,9 +242,7 @@ impl FeePerGasSimpleEstimator { } } - fn predict_base_fee(base_fees: &Vec) -> U256 { - Self::percentile_of(base_fees, Self::BASE_FEE_PERCENTILE) - } + fn predict_base_fee(base_fees: &[U256]) -> U256 { Self::percentile_of(base_fees, Self::BASE_FEE_PERCENTILE) } fn priority_fee_for_level( level: PriorityLevelId, @@ -252,7 +250,7 @@ impl FeePerGasSimpleEstimator { fee_history: &FeeHistoryResult, ) -> Web3RpcResult { let level_i = level as usize; - let mut level_rewards = fee_history + let level_rewards = fee_history .priority_rewards .as_ref() .or_mm_err(|| Web3RpcError::Internal("expected reward in eth_feeHistory".into()))? @@ -266,7 +264,7 @@ impl FeePerGasSimpleEstimator { }) .collect::>(); - let max_priority_fee_per_gas = Self::percentile_of(&mut level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_i]); + let max_priority_fee_per_gas = Self::percentile_of(&level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_i]); let max_priority_fee_per_gas = u256_to_big_decimal(max_priority_fee_per_gas, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); let max_fee_per_gas = base_fee @@ -283,14 +281,20 @@ impl FeePerGasSimpleEstimator { /// estimate priority fees by fee history fn calculate_with_history(fee_history: &FeeHistoryResult) -> Web3RpcResult { - let latest_base_fee = fee_history.base_fee_per_gas.first().cloned().unwrap_or_else(|| U256::from(0)); - let latest_base_fee = u256_to_big_decimal(latest_base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); + let latest_base_fee = fee_history + .base_fee_per_gas + .first() + .cloned() + .unwrap_or_else(|| U256::from(0)); + let latest_base_fee = + u256_to_big_decimal(latest_base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); let predicted_base_fee = Self::predict_base_fee(&fee_history.base_fee_per_gas); Ok(FeePerGasEstimated { - base_fee: u256_to_big_decimal(predicted_base_fee, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)), + base_fee: u256_to_big_decimal(predicted_base_fee, ETH_GWEI_DECIMALS) + .unwrap_or_else(|_| BigDecimal::from(0)), low: Self::priority_fee_for_level(PriorityLevelId::Low, latest_base_fee.clone(), fee_history)?, medium: Self::priority_fee_for_level(PriorityLevelId::Medium, latest_base_fee.clone(), fee_history)?, - high: Self::priority_fee_for_level(PriorityLevelId::High, latest_base_fee.clone(), fee_history)?, + high: Self::priority_fee_for_level(PriorityLevelId::High, latest_base_fee, fee_history)?, source: EstimationSource::Simple, units: EstimationUnits::Gwei, base_fee_trend: String::default(), diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 5385135db4..f0159583a1 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -467,7 +467,7 @@ fn test_withdraw_impl_manual_fee() { coin: "ETH".to_string(), max: false, fee: Some(WithdrawFee::EthGas { - gas: ETH_GAS, + gas: ETH_MAX_TRADE_GAS, gas_price: 1.into(), }), memo: None, @@ -479,7 +479,7 @@ fn test_withdraw_impl_manual_fee() { EthTxFeeDetails { coin: "ETH".into(), gas_price: "0.000000001".parse().unwrap(), - gas: ETH_GAS, + gas: ETH_MAX_TRADE_GAS, total_fee: "0.00015".parse().unwrap(), max_fee_per_gas: None, max_priority_fee_per_gas: None, @@ -514,7 +514,7 @@ fn test_withdraw_impl_fee_details() { coin: "JST".to_string(), max: false, fee: Some(WithdrawFee::EthGas { - gas: ETH_GAS, + gas: ETH_MAX_TRADE_GAS, gas_price: 1.into(), }), memo: None, @@ -526,7 +526,7 @@ fn test_withdraw_impl_fee_details() { EthTxFeeDetails { coin: "ETH".into(), gas_price: "0.000000001".parse().unwrap(), - gas: ETH_GAS, + gas: ETH_MAX_TRADE_GAS, total_fee: "0.00015".parse().unwrap(), max_fee_per_gas: None, max_priority_fee_per_gas: None, @@ -589,7 +589,7 @@ fn test_add_ten_pct_one_gwei() { fn get_sender_trade_preimage() { /// Trade fee for the ETH coin is `2 * 150_000 * gas_price` always. fn expected_fee(gas_price: u64) -> TradeFee { - let amount = u256_to_big_decimal((2 * ETH_GAS * gas_price).into(), 18).expect("!u256_to_big_decimal"); + let amount = u256_to_big_decimal((2 * ETH_MAX_TRADE_GAS * gas_price).into(), 18).expect("!u256_to_big_decimal"); TradeFee { coin: "ETH".to_owned(), amount: amount.into(), @@ -611,8 +611,9 @@ fn get_sender_trade_preimage() { assert_eq!(actual, expected); let value = u256_to_big_decimal(100.into(), 18).expect("!u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue, true)) - .expect("!get_sender_trade_fee"); + let actual = + block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue, true)) + .expect("!get_sender_trade_fee"); let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE); assert_eq!(actual, expected); @@ -623,8 +624,9 @@ fn get_sender_trade_preimage() { assert_eq!(actual, expected); let value = u256_to_big_decimal(10000000000u64.into(), 18).expect("!u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage, true)) - .expect("!get_sender_trade_fee"); + let actual = + block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage, true)) + .expect("!get_sender_trade_fee"); let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE); assert_eq!(actual, expected); } @@ -665,9 +667,12 @@ fn get_erc20_sender_trade_preimage() { // value is allowed unsafe { ALLOWANCE = 1000 }; let value = u256_to_big_decimal(1000.into(), 18).expect("u256_to_big_decimal"); - let actual = - block_on(coin.get_sender_trade_fee(TradePreimageValue::UpperBound(value), FeeApproxStage::WithoutApprox)) - .expect("!get_sender_trade_fee"); + let actual = block_on(coin.get_sender_trade_fee( + TradePreimageValue::UpperBound(value), + FeeApproxStage::WithoutApprox, + true, + )) + .expect("!get_sender_trade_fee"); log!("{:?}", actual.amount.to_decimal()); unsafe { assert!(!ESTIMATE_GAS_CALLED) } assert_eq!(actual, expected_trade_fee(300_000, GAS_PRICE)); @@ -675,8 +680,9 @@ fn get_erc20_sender_trade_preimage() { // value is greater than allowance unsafe { ALLOWANCE = 999 }; let value = u256_to_big_decimal(1000.into(), 18).expect("u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::UpperBound(value), FeeApproxStage::StartSwap, true)) - .expect("!get_sender_trade_fee"); + let actual = + block_on(coin.get_sender_trade_fee(TradePreimageValue::UpperBound(value), FeeApproxStage::StartSwap, true)) + .expect("!get_sender_trade_fee"); unsafe { assert!(ESTIMATE_GAS_CALLED); ESTIMATE_GAS_CALLED = false; @@ -689,8 +695,9 @@ fn get_erc20_sender_trade_preimage() { // value is allowed unsafe { ALLOWANCE = 1000 }; let value = u256_to_big_decimal(999.into(), 18).expect("u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue, true)) - .expect("!get_sender_trade_fee"); + let actual = + block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue, true)) + .expect("!get_sender_trade_fee"); unsafe { assert!(!ESTIMATE_GAS_CALLED) } assert_eq!( actual, @@ -700,8 +707,9 @@ fn get_erc20_sender_trade_preimage() { // value is greater than allowance unsafe { ALLOWANCE = 1000 }; let value = u256_to_big_decimal(1500.into(), 18).expect("u256_to_big_decimal"); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage, true)) - .expect("!get_sender_trade_fee"); + let actual = + block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage, true)) + .expect("!get_sender_trade_fee"); unsafe { assert!(ESTIMATE_GAS_CALLED); ESTIMATE_GAS_CALLED = false; @@ -717,7 +725,7 @@ fn get_receiver_trade_preimage() { EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(GAS_PRICE.into())))); let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &["http://dummy.dummy"], None); - let amount = u256_to_big_decimal((ETH_GAS * GAS_PRICE).into(), 18).expect("!u256_to_big_decimal"); + let amount = u256_to_big_decimal((ETH_MAX_TRADE_GAS * GAS_PRICE).into(), 18).expect("!u256_to_big_decimal"); let expected_fee = TradeFee { coin: "ETH".to_owned(), amount: amount.into(), diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 5e5876fcef..b33c8f1a5e 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -798,7 +798,7 @@ fn test_sender_trade_preimage_zero_allowance() { let sender_refund_fee = big_decimal_from_sat(CONTRACT_CALL_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals); let actual = - block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(1.into()), FeeApproxStage::WithoutApprox)) + block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(1.into()), FeeApproxStage::WithoutApprox, true)) .expect("!get_sender_trade_fee"); // one `approve` contract call should be included into the expected trade fee let expected = TradeFee { @@ -908,10 +908,11 @@ fn test_get_sender_trade_fee_preimage_for_correct_ticker() { )) .unwrap(); - let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(0.into()), FeeApproxStage::OrderIssue, true)) - .err() - .unwrap() - .into_inner(); + let actual = + block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(0.into()), FeeApproxStage::OrderIssue, true)) + .err() + .unwrap() + .into_inner(); // expecting TradePreimageError::NotSufficientBalance let expected = TradePreimageError::NotSufficientBalance { coin: "tQTUM".to_string(), diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 4782b07d83..b293b31c26 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -2530,9 +2530,12 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { // `2.21934443` value was obtained as a result of executing the `max_taker_vol` RPC call for this wallet let max_taker_vol = BigDecimal::from_str("2.21934443").expect("!BigDecimal::from_str"); - let fee3 = - block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(max_taker_vol), FeeApproxStage::WithoutApprox, false)) - .expect("!get_sender_trade_fee"); + let fee3 = block_on(coin.get_sender_trade_fee( + TradePreimageValue::Exact(max_taker_vol), + FeeApproxStage::WithoutApprox, + false, + )) + .expect("!get_sender_trade_fee"); assert_eq!(fee1, fee3); } From 25d80f4b3aaa17db5503b36c9ffe1cad085f02e3 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 23 Mar 2024 14:10:46 +0500 Subject: [PATCH 32/71] fix updated fn get_sender_trade_fee() gas limit calc and tests --- mm2src/coins/eth.rs | 21 +++++++---------- mm2src/coins/eth/eth_tests.rs | 44 ++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 0e5d3a9e45..da8523ac2f 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5370,17 +5370,17 @@ impl MmCoin for EthCoin { .compat() .await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); - let mut gas_limit = match self.coin_type { + let gas_limit = match self.coin_type { EthCoinType::Eth => { // this gas_limit includes gas for `ethPayment` and optionally `senderRefund` contract calls if include_refund_fee { - U256::from(swap_gas::ETH_PAYMENT) - } else { U256::from(swap_gas::ETH_PAYMENT) + U256::from(swap_gas::ETH_SENDER_REFUND) + } else { + U256::from(swap_gas::ETH_PAYMENT) } }, EthCoinType::Erc20 { token_addr, .. } => { - let gas = U256::from(swap_gas::ERC20_PAYMENT); + let mut gas = U256::from(swap_gas::ERC20_PAYMENT); let value = match value { TradePreimageValue::Exact(value) | TradePreimageValue::UpperBound(value) => { wei_from_big_decimal(&value, self.decimals)? @@ -5400,19 +5400,16 @@ impl MmCoin for EthCoin { .await?; // this gas_limit includes gas for `approve`, `erc20Payment` contract calls - gas + approve_gas_limit - } else { - // this gas_limit includes gas for `erc20Payment` contract calls - gas + gas += approve_gas_limit; + } + if include_refund_fee { + gas += U256::from(swap_gas::ERC20_SENDER_REFUND); // add 'senderRefund' gas if requested } + gas }, EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; - if include_refund_fee { - gas_limit += U256::from(swap_gas::ERC20_SENDER_REFUND); // add 'senderRefund' gas if requested - } - let total_fee = calc_total_fee(gas_limit, &pay_for_gas_option)?; let amount = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; let fee_coin = match &self.coin_type { diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index f0159583a1..53daed40a6 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -588,8 +588,8 @@ fn test_add_ten_pct_one_gwei() { #[test] fn get_sender_trade_preimage() { /// Trade fee for the ETH coin is `2 * 150_000 * gas_price` always. - fn expected_fee(gas_price: u64) -> TradeFee { - let amount = u256_to_big_decimal((2 * ETH_MAX_TRADE_GAS * gas_price).into(), 18).expect("!u256_to_big_decimal"); + fn expected_fee(gas_price: u64, gas_limit: u64) -> TradeFee { + let amount = u256_to_big_decimal((gas_limit * gas_price).into(), 18).expect("!u256_to_big_decimal"); TradeFee { coin: "ETH".to_owned(), amount: amount.into(), @@ -607,27 +607,36 @@ fn get_sender_trade_preimage() { true, )) .expect("!get_sender_trade_fee"); - let expected = expected_fee(GAS_PRICE); + let expected = expected_fee(GAS_PRICE, swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND); assert_eq!(actual, expected); let value = u256_to_big_decimal(100.into(), 18).expect("!u256_to_big_decimal"); let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::OrderIssue, true)) .expect("!get_sender_trade_fee"); - let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE); + let expected = expected_fee( + GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE, + swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND, + ); assert_eq!(actual, expected); let value = u256_to_big_decimal(1.into(), 18).expect("!u256_to_big_decimal"); let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::StartSwap, true)) .expect("!get_sender_trade_fee"); - let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_START_SWAP); + let expected = expected_fee( + GAS_PRICE_APPROXIMATION_ON_START_SWAP, + swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND, + ); assert_eq!(actual, expected); let value = u256_to_big_decimal(10000000000u64.into(), 18).expect("!u256_to_big_decimal"); let actual = block_on(coin.get_sender_trade_fee(TradePreimageValue::Exact(value), FeeApproxStage::TradePreimage, true)) .expect("!get_sender_trade_fee"); - let expected = expected_fee(GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE); + let expected = expected_fee( + GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE, + swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND, + ); assert_eq!(actual, expected); } @@ -675,7 +684,10 @@ fn get_erc20_sender_trade_preimage() { .expect("!get_sender_trade_fee"); log!("{:?}", actual.amount.to_decimal()); unsafe { assert!(!ESTIMATE_GAS_CALLED) } - assert_eq!(actual, expected_trade_fee(300_000, GAS_PRICE)); + assert_eq!( + actual, + expected_trade_fee(swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND, GAS_PRICE) + ); // value is greater than allowance unsafe { ALLOWANCE = 999 }; @@ -689,7 +701,10 @@ fn get_erc20_sender_trade_preimage() { } assert_eq!( actual, - expected_trade_fee(360_000, GAS_PRICE_APPROXIMATION_ON_START_SWAP) + expected_trade_fee( + swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND + APPROVE_GAS_LIMIT, + GAS_PRICE_APPROXIMATION_ON_START_SWAP + ) ); // value is allowed @@ -701,7 +716,10 @@ fn get_erc20_sender_trade_preimage() { unsafe { assert!(!ESTIMATE_GAS_CALLED) } assert_eq!( actual, - expected_trade_fee(300_000, GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE) + expected_trade_fee( + swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND, + GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE + ) ); // value is greater than allowance @@ -716,7 +734,10 @@ fn get_erc20_sender_trade_preimage() { } assert_eq!( actual, - expected_trade_fee(360_000, GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE) + expected_trade_fee( + swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND + APPROVE_GAS_LIMIT, + GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE + ) ); } @@ -725,7 +746,8 @@ fn get_receiver_trade_preimage() { EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(GAS_PRICE.into())))); let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &["http://dummy.dummy"], None); - let amount = u256_to_big_decimal((ETH_MAX_TRADE_GAS * GAS_PRICE).into(), 18).expect("!u256_to_big_decimal"); + let amount = + u256_to_big_decimal((swap_gas::ETH_RECEIVER_SPEND * GAS_PRICE).into(), 18).expect("!u256_to_big_decimal"); let expected_fee = TradeFee { coin: "ETH".to_owned(), amount: amount.into(), From 431c5ce244cb9bb40f95ad2006d0e3409afacd84 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 23 Mar 2024 23:19:39 +0500 Subject: [PATCH 33/71] fix gas limit consts for watcher; swap_watcher_tests with eth erc20 are okay --- mm2src/coins/eth.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index da8523ac2f..9f9b60d055 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -3705,7 +3705,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_SENDER_REFUND), + U256::from(swap_gas::ETH_RECEIVER_SPEND), ) }), ) @@ -3753,7 +3753,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_RECEIVER_SPEND), + U256::from(swap_gas::ERC20_RECEIVER_SPEND), ) }), ) @@ -3827,7 +3827,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_RECEIVER_SPEND), + U256::from(swap_gas::ETH_SENDER_REFUND), ) }), ) @@ -3878,7 +3878,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_SENDER_REFUND), + U256::from(swap_gas::ERC20_SENDER_REFUND), ) }), ) @@ -3948,7 +3948,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_SENDER_REFUND), + U256::from(swap_gas::ETH_RECEIVER_SPEND), ) }), ) @@ -4000,7 +4000,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_RECEIVER_SPEND), + U256::from(swap_gas::ERC20_RECEIVER_SPEND), ) }), ) @@ -4071,7 +4071,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_RECEIVER_SPEND), + U256::from(swap_gas::ETH_SENDER_REFUND), ) }), ) @@ -4123,7 +4123,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_SENDER_REFUND), + U256::from(swap_gas::ERC20_SENDER_REFUND), ) }), ) From 67214d265db5e9d8f0f11366ffe41db140a32447 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 24 Mar 2024 23:24:20 +0500 Subject: [PATCH 34/71] add eth type1 and type2 support in signed_tx_from_web3_tx fn --- Cargo.lock | 10 +- mm2src/coins/Cargo.toml | 3 +- mm2src/coins/eth.rs | 144 ++++++++++++++---- mm2src/crypto/Cargo.toml | 3 +- mm2src/mm2_eth/Cargo.toml | 3 +- mm2src/mm2_main/Cargo.toml | 3 +- .../tests/docker_tests/eth_docker_tests.rs | 9 +- mm2src/mm2_metamask/Cargo.toml | 3 +- 8 files changed, 140 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dec5226491..f1582941a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,7 +2174,7 @@ dependencies = [ [[package]] name = "ethcore-transaction" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" dependencies = [ "ethereum-types", "ethkey", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" dependencies = [ "byteorder", "edit-distance", @@ -4109,7 +4109,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" [[package]] name = "memchr" @@ -8647,7 +8647,7 @@ dependencies = [ [[package]] name = "unexpected" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#ea2690a7057f433bf2105d9f545b207526dbdbeb" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" [[package]] name = "unicode-bidi" @@ -9009,7 +9009,7 @@ dependencies = [ [[package]] name = "web3" version = "0.19.0" -source = "git+https://github.com/KomodoPlatform/rust-web3?tag=v0.19.0#ec5e72a5c95e3935ea0c9ab77b501e3926686fa9" +source = "git+https://github.com/dimxy/rust-web3?branch=add-chain-id#09f3b2818adfebd80985377c5fa7088dd7b11489" dependencies = [ "arrayvec 0.7.1", "base64 0.13.0", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 8efe11e9e3..9a721071a7 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -106,7 +106,8 @@ url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } # One of web3 dependencies is the old `tokio-uds 0.1.7` which fails cross-compiling to ARM. # We don't need the default web3 features at all since we added our own web3 transport using shared HYPER instance. -web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } +# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } +web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, branch = "add-chain-id" } zbase32 = "0.1.2" zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.0" } zcash_extras = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.0" } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9f9b60d055..f2ca81cd0c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -44,8 +44,9 @@ use derive_more::Display; use enum_derives::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; -use ethcore_transaction::{Action, LegacyTransaction, TransactionWrapperBuilder as UnSignedEthTxBuilder, - UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; +use ethcore_transaction::{Action, Eip1559Transaction, Eip2930Transaction, LegacyTransaction, + TransactionWrapperBuilder as UnSignedEthTxBuilder, TxType, UnverifiedEip1559Transaction, + UnverifiedEip2930Transaction, UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; use ethereum_types::{Address, H160, H256, U256}; use ethkey::{public_to_address, KeyPair, Public, Signature}; use ethkey::{sign, verify_address}; @@ -492,6 +493,19 @@ pub enum EthAddressFormat { MixedCase, } +/// get tx type from pay_for_gas_option +/// currently only type2 and legacy supported +/// if for Eth Classic we also want support for type 1 then use a fn +macro_rules! tx_type_from_pay_for_gas_option { + ($pay_for_gas_option: expr) => { + if matches!($pay_for_gas_option, PayForGasOption::Eip1559(..)) { + TxType::Type2 + } else { + TxType::Legacy + } + } +} + impl EthCoinImpl { #[cfg(not(target_arch = "wasm32"))] fn eth_traces_path(&self, ctx: &MmArc) -> PathBuf { @@ -715,7 +729,8 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { .await? .map_to_mm(WithdrawError::Transport)?; - let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); + let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, Action::Call(call_addr), eth_value, data); let tx_builder = tx_builder_with_pay_for_gas_option(&coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder .build() @@ -882,7 +897,8 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .await? .map_to_mm(WithdrawError::Transport)?; - let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); + let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, Action::Call(call_addr), eth_value, data); let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder .build() @@ -966,7 +982,8 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .await? .map_to_mm(WithdrawError::Transport)?; - let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, Action::Call(call_addr), eth_value, data); + let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, Action::Call(call_addr), eth_value, data); let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder .build() @@ -2406,7 +2423,9 @@ async fn sign_transaction_with_keypair( .compat() .await ); - let tx_builder = UnSignedEthTxBuilder::new(nonce, gas, action, value, data); + + let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, action, value, data); let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, &pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; let tx = tx_builder.build().map_err(|e| TransactionErr::Plain(e.to_string()))?; @@ -5799,34 +5818,107 @@ impl Transaction for SignedEthTx { } fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { + let type_1: ethereum_types::U64 = 1.into(); + let type_2: ethereum_types::U64 = 2.into(); + let map_access_list = |web3_access_list: &Option>| match web3_access_list { + Some(list) => { + let v = list + .iter() + .map(|item| ethcore_transaction::AccessListItem { + address: item.address, + storage_keys: item.storage_keys.clone(), + }) + .collect::>(); + ethcore_transaction::AccessList(v) + }, + None => ethcore_transaction::AccessList(vec![]), + }; + let r = transaction.r.ok_or_else(|| ERRL!("'Transaction::r' is not set"))?; let s = transaction.s.ok_or_else(|| ERRL!("'Transaction::s' is not set"))?; let v = transaction .v .ok_or_else(|| ERRL!("'Transaction::v' is not set"))? .as_u64(); - let gas_price = transaction - .gas_price - .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; - - let unverified = UnverifiedTransactionWrapper::Legacy(UnverifiedLegacyTransaction { - r, - s, - network_v: v, - hash: transaction.hash, - unsigned: LegacyTransaction { - data: transaction.input.0, - gas_price, - gas: transaction.gas, - value: transaction.value, - nonce: transaction.nonce, - action: match transaction.to { - Some(addr) => Action::Call(addr), - None => Action::Create, + + let unverified = match transaction.transaction_type { + None => UnverifiedTransactionWrapper::Legacy(UnverifiedLegacyTransaction { + r, + s, + network_v: v, + hash: transaction.hash, + unsigned: LegacyTransaction { + data: transaction.input.0, + gas_price: transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?, + gas: transaction.gas, + value: transaction.value, + nonce: transaction.nonce, + action: match transaction.to { + Some(addr) => Action::Call(addr), + None => Action::Create, + }, }, + }), + Some(tx_type) => { + let chain_id_s = transaction + .chain_id + .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? + .to_string(); + let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; + if tx_type == type_1 { + UnverifiedTransactionWrapper::Eip2930(UnverifiedEip2930Transaction { + r, + s, + v, + hash: transaction.hash, + unsigned: Eip2930Transaction { + chain_id, + data: transaction.input.0, + gas_price: transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?, + gas: transaction.gas, + value: transaction.value, + nonce: transaction.nonce, + action: match transaction.to { + Some(addr) => Action::Call(addr), + None => Action::Create, + }, + access_list: map_access_list(&transaction.access_list), + }, + }) + } else if tx_type == type_2 { + UnverifiedTransactionWrapper::Eip1559(UnverifiedEip1559Transaction { + r, + s, + v, + hash: transaction.hash, + unsigned: Eip1559Transaction { + chain_id, + data: transaction.input.0, + max_priority_fee_per_gas: transaction + .max_priority_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?, + max_fee_per_gas: transaction + .max_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?, + gas: transaction.gas, + value: transaction.value, + nonce: transaction.nonce, + action: match transaction.to { + Some(addr) => Action::Call(addr), + None => Action::Create, + }, + access_list: map_access_list(&transaction.access_list), + }, + }) + } else { + return Err(ERRL!("'Transaction::transaction_type' unsupported")); + } }, - }); - + }; Ok(try_s!(SignedEthTx::new(unverified))) } diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index 80fd38a212..fff3f9e6f0 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -43,7 +43,8 @@ trezor = { path = "../trezor" } mm2_eth = { path = "../mm2_eth" } mm2_metamask = { path = "../mm2_metamask" } wasm-bindgen-test = { version = "0.3.2" } -web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } +# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } +web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, branch = "add-chain-id" } [features] trezor-udp = ["trezor/trezor-udp"] diff --git a/mm2src/mm2_eth/Cargo.toml b/mm2src/mm2_eth/Cargo.toml index 5343b90f6a..0bea2e7c77 100644 --- a/mm2src/mm2_eth/Cargo.toml +++ b/mm2src/mm2_eth/Cargo.toml @@ -16,4 +16,5 @@ mm2_err_handle = { path = "../mm2_err_handle" } secp256k1 = { version = "0.20", features = ["recovery"] } serde = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } -web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } +# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } +web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, branch = "add-chain-id" } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 3b037954f3..21cc7eaceb 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -124,7 +124,8 @@ winapi = "0.3" mm2_test_helpers = { path = "../mm2_test_helpers" } mocktopus = "0.8.0" testcontainers = "0.15.0" -web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false, features = ["http"] } +# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false, features = ["http"] } +web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, features = ["http"], branch = "add-chain-id" } [build-dependencies] chrono = "0.4" diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index bc3ee7ceb6..bcf7d45021 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -3,8 +3,9 @@ use crate::docker_tests::docker_tests_common::{random_secp256k1_secret, GETH_ACC GETH_WEB3, MM_CTX}; use bitcrypto::dhash160; use coins::eth::{checksum_address, eth_coin_from_conf_and_request, EthCoin, ERC20_ABI}; -use coins::{CoinProtocol, ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, PrivKeyBuildPolicy, RefundPaymentArgs, - SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash}; +use coins::{CoinProtocol, ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, PrivKeyBuildPolicy, + RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, + SwapTxTypeWithSecretHash}; use common::{block_on, now_sec}; use ethereum_types::U256; use futures01::Future; @@ -223,6 +224,9 @@ fn send_and_spend_eth_maker_payment() { let maker_eth_coin = eth_coin_with_random_privkey(swap_contract()); let taker_eth_coin = eth_coin_with_random_privkey(swap_contract()); + maker_eth_coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); + taker_eth_coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); + let time_lock = now_sec() + 1000; let maker_pubkey = maker_eth_coin.derive_htlc_pubkey(&[]); let taker_pubkey = taker_eth_coin.derive_htlc_pubkey(&[]); @@ -299,6 +303,7 @@ fn send_and_spend_eth_maker_payment() { #[test] fn send_and_refund_erc20_maker_payment() { let erc20_coin = erc20_coin_with_random_privkey(swap_contract()); + erc20_coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); let time_lock = now_sec() - 100; let other_pubkey = &[ diff --git a/mm2src/mm2_metamask/Cargo.toml b/mm2src/mm2_metamask/Cargo.toml index 71551c43ad..5064c60680 100644 --- a/mm2src/mm2_metamask/Cargo.toml +++ b/mm2src/mm2_metamask/Cargo.toml @@ -20,4 +20,5 @@ serde_json = { version = "1", features = ["preserve_order", "raw_value"] } serde_derive = "1.0" wasm-bindgen = "0.2.86" wasm-bindgen-futures = { version = "0.4.1" } -web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false, features = ["eip-1193"] } +# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false, features = ["eip-1193"] } +web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, features = ["eip-1193"], branch = "add-chain-id" } From dae664549d4f15c2f4ee48ffa9ec9af0d8456e00 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 24 Mar 2024 23:28:06 +0500 Subject: [PATCH 35/71] fix fmt --- mm2src/coins/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index f2ca81cd0c..d085713d12 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -503,7 +503,7 @@ macro_rules! tx_type_from_pay_for_gas_option { } else { TxType::Legacy } - } + }; } impl EthCoinImpl { From df5daa2b8143d088a19346db2d9254f50b596b2e Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 25 Mar 2024 20:10:53 +0500 Subject: [PATCH 36/71] fix parse web3 tx type0 --- mm2src/coins/eth.rs | 116 ++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index d085713d12..3bbd80a0ef 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5818,6 +5818,7 @@ impl Transaction for SignedEthTx { } fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { + let type_0: ethereum_types::U64 = 0.into(); let type_1: ethereum_types::U64 = 1.into(); let type_2: ethereum_types::U64 = 2.into(); let map_access_list = |web3_access_list: &Option>| match web3_access_list { @@ -5841,8 +5842,8 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result UnverifiedTransactionWrapper::Legacy(UnverifiedLegacyTransaction { + let unverified = if transaction.transaction_type.is_none() || transaction.transaction_type.unwrap() == type_0 { + UnverifiedTransactionWrapper::Legacy(UnverifiedLegacyTransaction { r, s, network_v: v, @@ -5860,64 +5861,63 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result Action::Create, }, }, - }), - Some(tx_type) => { - let chain_id_s = transaction - .chain_id - .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? - .to_string(); - let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; - if tx_type == type_1 { - UnverifiedTransactionWrapper::Eip2930(UnverifiedEip2930Transaction { - r, - s, - v, - hash: transaction.hash, - unsigned: Eip2930Transaction { - chain_id, - data: transaction.input.0, - gas_price: transaction - .gas_price - .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?, - gas: transaction.gas, - value: transaction.value, - nonce: transaction.nonce, - action: match transaction.to { - Some(addr) => Action::Call(addr), - None => Action::Create, - }, - access_list: map_access_list(&transaction.access_list), + }) + } else { + let chain_id_s = transaction + .chain_id + .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? + .to_string(); + let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; + if transaction.transaction_type.unwrap() == type_1 { + UnverifiedTransactionWrapper::Eip2930(UnverifiedEip2930Transaction { + r, + s, + v, + hash: transaction.hash, + unsigned: Eip2930Transaction { + chain_id, + data: transaction.input.0, + gas_price: transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?, + gas: transaction.gas, + value: transaction.value, + nonce: transaction.nonce, + action: match transaction.to { + Some(addr) => Action::Call(addr), + None => Action::Create, }, - }) - } else if tx_type == type_2 { - UnverifiedTransactionWrapper::Eip1559(UnverifiedEip1559Transaction { - r, - s, - v, - hash: transaction.hash, - unsigned: Eip1559Transaction { - chain_id, - data: transaction.input.0, - max_priority_fee_per_gas: transaction - .max_priority_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?, - max_fee_per_gas: transaction - .max_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?, - gas: transaction.gas, - value: transaction.value, - nonce: transaction.nonce, - action: match transaction.to { - Some(addr) => Action::Call(addr), - None => Action::Create, - }, - access_list: map_access_list(&transaction.access_list), + access_list: map_access_list(&transaction.access_list), + }, + }) + } else if transaction.transaction_type.unwrap() == type_2 { + UnverifiedTransactionWrapper::Eip1559(UnverifiedEip1559Transaction { + r, + s, + v, + hash: transaction.hash, + unsigned: Eip1559Transaction { + chain_id, + data: transaction.input.0, + max_priority_fee_per_gas: transaction + .max_priority_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?, + max_fee_per_gas: transaction + .max_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?, + gas: transaction.gas, + value: transaction.value, + nonce: transaction.nonce, + action: match transaction.to { + Some(addr) => Action::Call(addr), + None => Action::Create, }, - }) - } else { - return Err(ERRL!("'Transaction::transaction_type' unsupported")); - } - }, + access_list: map_access_list(&transaction.access_list), + }, + }) + } else { + return Err(ERRL!("'Transaction::transaction_type' unsupported")); + } }; Ok(try_s!(SignedEthTx::new(unverified))) } From 098eaa2877df909bc1b8e4b54cf362789e74d143 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 25 Mar 2024 20:12:06 +0500 Subject: [PATCH 37/71] add tests for type0 and type2 eth swap add delay in test fn wait_for_confirmation to avoid false err --- .../tests/docker_tests/eth_docker_tests.rs | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index bcf7d45021..8d275bf01e 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -40,6 +40,7 @@ pub fn erc20_contract() -> Address { unsafe { GETH_ERC20_CONTRACT } } pub fn erc20_contract_checksum() -> String { checksum_address(&format!("{:02x}", erc20_contract())) } fn wait_for_confirmation(tx_hash: H256) { + thread::sleep(Duration::from_millis(2000)); loop { match block_on(GETH_WEB3.eth().transaction_receipt(tx_hash)) { Ok(Some(r)) => match r.block_hash { @@ -145,9 +146,9 @@ pub fn erc20_coin_with_random_privkey(swap_contract: Address) -> EthCoin { erc20_coin } -#[test] -fn send_and_refund_eth_maker_payment() { +fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let eth_coin = eth_coin_with_random_privkey(swap_contract()); + eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy); let time_lock = now_sec() - 100; let other_pubkey = &[ @@ -220,12 +221,21 @@ fn send_and_refund_eth_maker_payment() { } #[test] -fn send_and_spend_eth_maker_payment() { +fn send_and_refund_eth_maker_payment_internal_gas_policy() { + send_and_refund_eth_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_refund_eth_maker_payment_priority_fee() { + send_and_refund_eth_maker_payment_impl(SwapTxFeePolicy::Medium); +} + +fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let maker_eth_coin = eth_coin_with_random_privkey(swap_contract()); let taker_eth_coin = eth_coin_with_random_privkey(swap_contract()); - maker_eth_coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); - taker_eth_coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); + maker_eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy.clone()); + taker_eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy); let time_lock = now_sec() + 1000; let maker_pubkey = maker_eth_coin.derive_htlc_pubkey(&[]); @@ -301,9 +311,18 @@ fn send_and_spend_eth_maker_payment() { } #[test] -fn send_and_refund_erc20_maker_payment() { +fn send_and_spend_eth_maker_payment_internal_gas_policy() { + send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_spend_eth_maker_payment_priority_fee() { + send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Medium); +} + +fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let erc20_coin = erc20_coin_with_random_privkey(swap_contract()); - erc20_coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); + erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy); let time_lock = now_sec() - 100; let other_pubkey = &[ @@ -377,10 +396,22 @@ fn send_and_refund_erc20_maker_payment() { } #[test] -fn send_and_spend_erc20_maker_payment() { +fn send_and_refund_erc20_maker_payment_internal_gas_policy() { + send_and_refund_erc20_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_refund_erc20_maker_payment_priority_fee() { + send_and_refund_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); +} + +fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let maker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); let taker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); + maker_erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy.clone()); + taker_erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy); + let time_lock = now_sec() + 1000; let maker_pubkey = maker_erc20_coin.derive_htlc_pubkey(&[]); let taker_pubkey = taker_erc20_coin.derive_htlc_pubkey(&[]); @@ -453,3 +484,13 @@ fn send_and_spend_erc20_maker_payment() { let expected = FoundSwapTxSpend::Spent(payment_spend); assert_eq!(expected, search_tx); } + +#[test] +fn send_and_spend_erc20_maker_payment_internal_gas_policy() { + send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_spend_erc20_maker_payment_priority_fee() { + send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); +} \ No newline at end of file From 86a8172ab7ca55a01a3c8c255d2848e0f188c91e Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 25 Mar 2024 20:22:23 +0500 Subject: [PATCH 38/71] fix fmt --- mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 8d275bf01e..91789e8433 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -226,9 +226,7 @@ fn send_and_refund_eth_maker_payment_internal_gas_policy() { } #[test] -fn send_and_refund_eth_maker_payment_priority_fee() { - send_and_refund_eth_maker_payment_impl(SwapTxFeePolicy::Medium); -} +fn send_and_refund_eth_maker_payment_priority_fee() { send_and_refund_eth_maker_payment_impl(SwapTxFeePolicy::Medium); } fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let maker_eth_coin = eth_coin_with_random_privkey(swap_contract()); @@ -316,9 +314,7 @@ fn send_and_spend_eth_maker_payment_internal_gas_policy() { } #[test] -fn send_and_spend_eth_maker_payment_priority_fee() { - send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Medium); -} +fn send_and_spend_eth_maker_payment_priority_fee() { send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Medium); } fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let erc20_coin = erc20_coin_with_random_privkey(swap_contract()); @@ -493,4 +489,4 @@ fn send_and_spend_erc20_maker_payment_internal_gas_policy() { #[test] fn send_and_spend_erc20_maker_payment_priority_fee() { send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); -} \ No newline at end of file +} From a62d7a4cd2f28c089a8dbd991c42e1cf7a45bdd3 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 30 Mar 2024 22:04:48 +0500 Subject: [PATCH 39/71] add handling of errors from updated mm2-parity-ethereum lib --- Cargo.lock | 8 +-- mm2src/coins/eth.rs | 144 ++++++++++++++++++++++----------------- mm2src/coins/lp_coins.rs | 5 +- 3 files changed, 91 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1582941a0..1d42783cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,7 +2174,7 @@ dependencies = [ [[package]] name = "ethcore-transaction" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" dependencies = [ "ethereum-types", "ethkey", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" dependencies = [ "byteorder", "edit-distance", @@ -4109,7 +4109,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" [[package]] name = "memchr" @@ -8647,7 +8647,7 @@ dependencies = [ [[package]] name = "unexpected" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#78000fabd9a03e3087667053692a340439dde2d6" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" [[package]] name = "unicode-bidi" diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 3bbd80a0ef..9d806de01b 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -43,6 +43,7 @@ use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy, Stand use derive_more::Display; use enum_derives::EnumFromStringify; use ethabi::{Contract, Function, Token}; +use ethcore_transaction::tx_builders::TxBuilderError; pub use ethcore_transaction::SignedTransaction as SignedEthTx; use ethcore_transaction::{Action, Eip1559Transaction, Eip2930Transaction, LegacyTransaction, TransactionWrapperBuilder as UnSignedEthTxBuilder, TxType, UnverifiedEip1559Transaction, @@ -314,6 +315,10 @@ impl From for WithdrawError { } } +impl From for WithdrawError { + fn from(e: ethcore_transaction::Error) -> Self { WithdrawError::SigningError(e.to_string()) } +} + impl From for TradePreimageError { fn from(e: web3::Error) -> Self { TradePreimageError::Transport(e.to_string()) } } @@ -364,6 +369,14 @@ impl From for BalanceError { } } +impl From for TransactionErr { + fn from(e: TxBuilderError) -> Self { TransactionErr::Plain(e.to_string()) } +} + +impl From for TransactionErr { + fn from(e: ethcore_transaction::Error) -> Self { TransactionErr::Plain(e.to_string()) } +} + #[derive(Debug, Deserialize, Serialize)] struct SavedTraces { /// ETH traces for my_address @@ -735,7 +748,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { let tx = tx_builder .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let signed = tx.sign(key_pair.secret(), coin.chain_id); + let signed = tx.sign(key_pair.secret(), coin.chain_id)?; let bytes = rlp::encode(&signed); (signed.tx_hash(), BytesJson::from(bytes.to_vec())) @@ -904,7 +917,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); - let signed = tx.sign(secret, eth_coin.chain_id); + let signed = tx.sign(secret, eth_coin.chain_id)?; let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, pay_for_gas_option, fee_coin)?; @@ -989,7 +1002,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .build() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); - let signed = tx.sign(secret, eth_coin.chain_id); + let signed = tx.sign(secret, eth_coin.chain_id)?; let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, pay_for_gas_option, fee_coin)?; @@ -2428,10 +2441,10 @@ async fn sign_transaction_with_keypair( let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, action, value, data); let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, &pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; - let tx = tx_builder.build().map_err(|e| TransactionErr::Plain(e.to_string()))?; + let tx = tx_builder.build()?; Ok(( - tx.sign(key_pair.secret(), coin.chain_id), + tx.sign(key_pair.secret(), coin.chain_id)?, web3_instances_with_latest_nonce, )) } @@ -5843,39 +5856,9 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result Action::Call(addr), - None => Action::Create, - }, - }, - }) - } else { - let chain_id_s = transaction - .chain_id - .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? - .to_string(); - let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; - if transaction.transaction_type.unwrap() == type_1 { - UnverifiedTransactionWrapper::Eip2930(UnverifiedEip2930Transaction { - r, - s, - v, - hash: transaction.hash, - unsigned: Eip2930Transaction { - chain_id, + UnverifiedTransactionWrapper::Legacy( + UnverifiedLegacyTransaction::new_with_network_v( + LegacyTransaction { data: transaction.input.0, gas_price: transaction .gas_price @@ -5887,34 +5870,73 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result Action::Call(addr), None => Action::Create, }, - access_list: map_access_list(&transaction.access_list), }, - }) - } else if transaction.transaction_type.unwrap() == type_2 { - UnverifiedTransactionWrapper::Eip1559(UnverifiedEip1559Transaction { r, s, v, - hash: transaction.hash, - unsigned: Eip1559Transaction { - chain_id, - data: transaction.input.0, - max_priority_fee_per_gas: transaction - .max_priority_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?, - max_fee_per_gas: transaction - .max_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?, - gas: transaction.gas, - value: transaction.value, - nonce: transaction.nonce, - action: match transaction.to { - Some(addr) => Action::Call(addr), - None => Action::Create, + transaction.hash, + ) + .map_err(|err| ERRL!("'Transaction::new' error {}", err.to_string()))?, + ) + } else { + let chain_id_s = transaction + .chain_id + .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? + .to_string(); + let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; + if transaction.transaction_type.unwrap() == type_1 { + UnverifiedTransactionWrapper::Eip2930( + UnverifiedEip2930Transaction::new( + Eip2930Transaction { + chain_id, + data: transaction.input.0, + gas_price: transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?, + gas: transaction.gas, + value: transaction.value, + nonce: transaction.nonce, + action: match transaction.to { + Some(addr) => Action::Call(addr), + None => Action::Create, + }, + access_list: map_access_list(&transaction.access_list), }, - access_list: map_access_list(&transaction.access_list), - }, - }) + r, + s, + v, + transaction.hash, + ) + .map_err(|err| ERRL!("'Transaction::new' error {}", err.to_string()))?, + ) + } else if transaction.transaction_type.unwrap() == type_2 { + UnverifiedTransactionWrapper::Eip1559( + UnverifiedEip1559Transaction::new( + Eip1559Transaction { + chain_id, + data: transaction.input.0, + max_priority_fee_per_gas: transaction + .max_priority_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?, + max_fee_per_gas: transaction + .max_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?, + gas: transaction.gas, + value: transaction.value, + nonce: transaction.nonce, + action: match transaction.to { + Some(addr) => Action::Call(addr), + None => Action::Create, + }, + access_list: map_access_list(&transaction.access_list), + }, + r, + s, + v, + transaction.hash, + ) + .map_err(|err| ERRL!("'Transaction::new' error {}", err.to_string()))?, + ) } else { return Err(ERRL!("'Transaction::transaction_type' unsupported")); } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index aa6f276a07..3e68bc83b6 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -2759,6 +2759,8 @@ pub enum WithdrawError { NoChainIdSet { coin: String, }, + #[display(fmt = "Signing error {}", _0)] + SigningError(String), } impl HttpStatusCode for WithdrawError { @@ -2785,7 +2787,8 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::CoinDoesntSupportNftWithdraw { .. } | WithdrawError::NotEnoughNftsAmount { .. } | WithdrawError::MyAddressNotNftOwner { .. } - | WithdrawError::NoChainIdSet { .. } => StatusCode::BAD_REQUEST, + | WithdrawError::NoChainIdSet { .. } + | WithdrawError::SigningError(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST, From 8230a8903b29f0f2492ccc4f101e4a72c09e09ab Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 30 Mar 2024 22:05:22 +0500 Subject: [PATCH 40/71] refactor TransactionRequest creation --- mm2src/coins/eth.rs | 129 ++++++++++-------- .../coins/rpc_command/get_estimated_fees.rs | 2 +- 2 files changed, 75 insertions(+), 56 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9d806de01b..e1ad57fbc8 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -765,25 +765,31 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { return MmError::err(WithdrawError::BroadcastExpected(error)); } + let gas_price = if let PayForGasOption::Legacy(ref legacy_gas_price) = pay_for_gas_option { + Some(legacy_gas_price.gas_price) + } else { + None + }; + + let max_priority_fee_per_gas = if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { + Some(fee_per_gas.max_priority_fee_per_gas) + } else { + None + }; + + let max_fee_per_gas = if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { + Some(fee_per_gas.max_fee_per_gas) + } else { + None + }; + let tx_to_send = TransactionRequest { from: coin.my_address, to: Some(to_addr), gas: Some(gas), - gas_price: if let PayForGasOption::Legacy(ref legacy_gas_price) = pay_for_gas_option { - Some(legacy_gas_price.gas_price) - } else { - None - }, - max_priority_fee_per_gas: if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { - Some(fee_per_gas.max_priority_fee_per_gas) - } else { - None - }, - max_fee_per_gas: if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { - Some(fee_per_gas.max_fee_per_gas) - } else { - None - }, + gas_price, + max_priority_fee_per_gas, + max_fee_per_gas, value: Some(eth_value), data: Some(data.clone().into()), nonce: None, @@ -2499,30 +2505,36 @@ async fn sign_and_send_transaction_with_metamask( .await ); + let gas_price = if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { + Some(gas_price) + } else { + None + }; + + let max_priority_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + .. + }) = pay_for_gas_option + { + Some(max_priority_fee_per_gas) + } else { + None + }; + + let max_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option + { + Some(max_fee_per_gas) + } else { + None + }; + let tx_to_send = TransactionRequest { from: coin.my_address, to, gas: Some(gas), - gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { - Some(gas_price) - } else { - None - }, - max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - .. - }) = pay_for_gas_option - { - Some(max_priority_fee_per_gas) - } else { - None - }, - max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option - { - Some(max_fee_per_gas) - } else { - None - }, + gas_price, + max_priority_fee_per_gas, + max_fee_per_gas, value: Some(value), data: Some(data.clone().into()), nonce: None, @@ -6438,6 +6450,30 @@ async fn get_eth_gas_details_from_withdraw_fee( } else { eth_value }; + + let gas_price = if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { + Some(gas_price) + } else { + None + }; + + let max_priority_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + .. + }) = pay_for_gas_option + { + Some(max_priority_fee_per_gas) + } else { + None + }; + + let max_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option + { + Some(max_fee_per_gas) + } else { + None + }; + let estimate_gas_req = CallRequest { value: Some(eth_value_for_estimate), data: Some(data), @@ -6446,26 +6482,9 @@ async fn get_eth_gas_details_from_withdraw_fee( gas: None, // gas price must be supplied because some smart contracts base their // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { - Some(gas_price) - } else { - None - }, - max_priority_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - .. - }) = pay_for_gas_option - { - Some(max_priority_fee_per_gas) - } else { - None - }, - max_fee_per_gas: if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option - { - Some(max_fee_per_gas) - } else { - None - }, + gas_price, + max_priority_fee_per_gas, + max_fee_per_gas, ..CallRequest::default() }; // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 2cc13c8eca..f62146906b 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -12,7 +12,7 @@ use mm2_err_handle::prelude::*; use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; -const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is suppported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) +const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is supported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) // To support fee estimations for other chains add a FeeEstimatorContext for a new chain #[derive(Debug, Display, Serialize, SerializeErrorType)] From 90f362a6bfd53ce202ca7a0808376da4a34d8827 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 30 Mar 2024 22:06:11 +0500 Subject: [PATCH 41/71] add eth wasm test to sign priority fee tx --- mm2src/coins/eth/eth_wasm_tests.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index 97a2879b5e..395be801a5 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -78,6 +78,7 @@ async fn init_eth_coin_helper() -> Result<(MmArc, MmCoinEnum), String> { "protocol":{ "type": "ETH" }, + "chain_id": 1, "rpcport": 80, "mm2": 1 }] @@ -118,3 +119,23 @@ async fn wasm_test_sign_eth_tx() { console::log_1(&format!("res={:?}", res).into()); assert!(res.is_ok()); } + +#[wasm_bindgen_test] +async fn wasm_test_sign_eth_tx_with_priority_fee() { + // we need to hold ref to _ctx until the end of the test (because of the weak ref to MmCtx in EthCoinImpl) + let (_ctx, coin) = init_eth_coin_helper().await.unwrap(); + coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); + let sign_req = json::from_value(json!({ + "coin": "ETH", + "type": "ETH", + "tx": { + "to": "0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94".to_string(), + "value": "1.234", + "gas_limit": "21000" + } + })) + .unwrap(); + let res = coin.sign_raw_tx(&sign_req).await; + console::log_1(&format!("res={:?}", res).into()); + assert!(res.is_ok()); +} From b21a18681471c4f59d1382d22edabc7b0cc3d186 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 30 Mar 2024 23:13:31 +0500 Subject: [PATCH 42/71] fix comment --- mm2src/coins/eth.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e1ad57fbc8..cf107c3757 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -3009,10 +3009,10 @@ impl EthCoin { let fee_details: Option = match receipt { Some(r) => { let gas_used = r.gas_used.unwrap_or_default(); - let gas_price = web3_tx.gas_price.unwrap_or_default(); // TODO: create and use EthTxFeeDetails::from(web3_tx) - // It's relatively safe to unwrap `EthTxFeeDetails::new` as it may fail - // due to `u256_to_big_decimal` only. - // Also TX history is not used by any GUI and has significant disadvantages. + let gas_price = web3_tx.gas_price.unwrap_or_default(); + // TODO: create and use EthTxFeeDetails::from(web3_tx) + // It's relatively safe to unwrap `EthTxFeeDetails::new` as it may fail due to `u256_to_big_decimal` only. + // Also TX history is not used by any GUI and has significant disadvantages. Some( EthTxFeeDetails::new( gas_used, From bc67d5bf1825e8329cd7cee78c9604fc6c71cddd Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 31 Mar 2024 09:27:55 +0500 Subject: [PATCH 43/71] refactor fee per gas determination --- mm2src/coins/eth.rs | 64 ++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index cf107c3757..407609883c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -771,16 +771,14 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { None }; - let max_priority_fee_per_gas = if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { - Some(fee_per_gas.max_priority_fee_per_gas) - } else { - None - }; - - let max_fee_per_gas = if let PayForGasOption::Eip1559(ref fee_per_gas) = pay_for_gas_option { - Some(fee_per_gas.max_fee_per_gas) + let (max_priority_fee_per_gas, max_fee_per_gas) = if let PayForGasOption::Eip1559(Eip1559FeePerGas { + max_priority_fee_per_gas, + max_fee_per_gas, + }) = pay_for_gas_option + { + (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) } else { - None + (None, None) }; let tx_to_send = TransactionRequest { @@ -2511,21 +2509,14 @@ async fn sign_and_send_transaction_with_metamask( None }; - let max_priority_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { + let (max_priority_fee_per_gas, max_fee_per_gas) = if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, - .. + max_fee_per_gas, }) = pay_for_gas_option { - Some(max_priority_fee_per_gas) + (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) } else { - None - }; - - let max_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option - { - Some(max_fee_per_gas) - } else { - None + (None, None) }; let tx_to_send = TransactionRequest { @@ -5281,15 +5272,17 @@ impl EthTxFeeDetails { }) => (max_fee_per_gas, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)), }; let gas_price = u256_to_big_decimal(gas_price, ETH_DECIMALS)?; - let max_fee_per_gas = if let Some(max_fee_per_gas) = max_fee_per_gas { - Some(u256_to_big_decimal(max_fee_per_gas, ETH_DECIMALS)?) - } else { - None - }; - let max_priority_fee_per_gas = if let Some(max_priority_fee_per_gas) = max_priority_fee_per_gas { - Some(u256_to_big_decimal(max_priority_fee_per_gas, ETH_DECIMALS)?) + let (max_fee_per_gas, max_priority_fee_per_gas) = if let ( + Some(max_fee_per_gas), + Some(max_priority_fee_per_gas), + ) = (max_fee_per_gas, max_priority_fee_per_gas) + { + ( + Some(u256_to_big_decimal(max_fee_per_gas, ETH_DECIMALS)?), + Some(u256_to_big_decimal(max_priority_fee_per_gas, ETH_DECIMALS)?), + ) } else { - None + (None, None) }; let gas_u64 = u64::try_from(gas).map_to_mm(|e| NumConversError::new(e.to_string()))?; @@ -6457,21 +6450,14 @@ async fn get_eth_gas_details_from_withdraw_fee( None }; - let max_priority_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { + let (max_priority_fee_per_gas, max_fee_per_gas) = if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas, - .. + max_fee_per_gas, }) = pay_for_gas_option { - Some(max_priority_fee_per_gas) + (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) } else { - None - }; - - let max_fee_per_gas = if let PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, .. }) = pay_for_gas_option - { - Some(max_fee_per_gas) - } else { - None + (None, None) }; let estimate_gas_req = CallRequest { From a16f2ae81e2d74d38d5c512592dd1a4292f52c60 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 31 Mar 2024 13:39:54 +0500 Subject: [PATCH 44/71] refactor signed_tx_from_web3_tx fn using tx builder --- Cargo.lock | 8 +-- mm2src/coins/eth.rs | 155 ++++++++++++++++++++------------------------ 2 files changed, 73 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d42783cb5..bd2a9837fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,7 +2174,7 @@ dependencies = [ [[package]] name = "ethcore-transaction" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" dependencies = [ "ethereum-types", "ethkey", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" dependencies = [ "byteorder", "edit-distance", @@ -4109,7 +4109,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" [[package]] name = "memchr" @@ -8647,7 +8647,7 @@ dependencies = [ [[package]] name = "unexpected" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#b6064ceec79ebf0ff896889196e2991efee2d58d" +source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" [[package]] name = "unicode-bidi" diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 407609883c..f9035e642b 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -45,9 +45,9 @@ use enum_derives::EnumFromStringify; use ethabi::{Contract, Function, Token}; use ethcore_transaction::tx_builders::TxBuilderError; pub use ethcore_transaction::SignedTransaction as SignedEthTx; -use ethcore_transaction::{Action, Eip1559Transaction, Eip2930Transaction, LegacyTransaction, - TransactionWrapperBuilder as UnSignedEthTxBuilder, TxType, UnverifiedEip1559Transaction, - UnverifiedEip2930Transaction, UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; +use ethcore_transaction::{Action, TransactionWrapper, TransactionWrapperBuilder as UnSignedEthTxBuilder, TxType, + UnverifiedEip1559Transaction, UnverifiedEip2930Transaction, UnverifiedLegacyTransaction, + UnverifiedTransactionWrapper}; use ethereum_types::{Address, H160, H256, U256}; use ethkey::{public_to_address, KeyPair, Public, Signature}; use ethkey::{sign, verify_address}; @@ -5839,6 +5839,20 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result Action::Call(addr), + None => Action::Create, + }; let map_access_list = |web3_access_list: &Option>| match web3_access_list { Some(list) => { let v = list @@ -5853,99 +5867,68 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result ethcore_transaction::AccessList(vec![]), }; - let r = transaction.r.ok_or_else(|| ERRL!("'Transaction::r' is not set"))?; - let s = transaction.s.ok_or_else(|| ERRL!("'Transaction::s' is not set"))?; - let v = transaction - .v - .ok_or_else(|| ERRL!("'Transaction::v' is not set"))? - .as_u64(); + let tx_builder = UnSignedEthTxBuilder::new( + tx_type.clone(), + transaction.nonce, + transaction.gas, + action, + transaction.value, + transaction.input.0, + ); + let tx_builder = if tx_type == TxType::Legacy { + let gas_price = transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; - let unverified = if transaction.transaction_type.is_none() || transaction.transaction_type.unwrap() == type_0 { - UnverifiedTransactionWrapper::Legacy( - UnverifiedLegacyTransaction::new_with_network_v( - LegacyTransaction { - data: transaction.input.0, - gas_price: transaction - .gas_price - .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?, - gas: transaction.gas, - value: transaction.value, - nonce: transaction.nonce, - action: match transaction.to { - Some(addr) => Action::Call(addr), - None => Action::Create, - }, - }, - r, - s, - v, - transaction.hash, - ) - .map_err(|err| ERRL!("'Transaction::new' error {}", err.to_string()))?, - ) + tx_builder.with_gas_price(gas_price) } else { let chain_id_s = transaction .chain_id .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? .to_string(); let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; - if transaction.transaction_type.unwrap() == type_1 { - UnverifiedTransactionWrapper::Eip2930( - UnverifiedEip2930Transaction::new( - Eip2930Transaction { - chain_id, - data: transaction.input.0, - gas_price: transaction - .gas_price - .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?, - gas: transaction.gas, - value: transaction.value, - nonce: transaction.nonce, - action: match transaction.to { - Some(addr) => Action::Call(addr), - None => Action::Create, - }, - access_list: map_access_list(&transaction.access_list), - }, - r, - s, - v, - transaction.hash, - ) + let tx_builder = if tx_type == TxType::Type1 { + let gas_price = transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; + tx_builder.with_gas_price(gas_price) + } else { + let max_fee_per_gas = transaction + .max_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?; + let max_priority_fee_per_gas = transaction + .max_priority_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?; + tx_builder.with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) + }; + tx_builder + .with_chain_id(chain_id) + .with_access_list(map_access_list(&transaction.access_list)) + }; + let unsigned = tx_builder.build().map_err(|err| err.to_string())?; + + let r = transaction.r.ok_or_else(|| ERRL!("'Transaction::r' is not set"))?; + let s = transaction.s.ok_or_else(|| ERRL!("'Transaction::s' is not set"))?; + let v = transaction + .v + .ok_or_else(|| ERRL!("'Transaction::v' is not set"))? + .as_u64(); + + let unverified = match unsigned { + TransactionWrapper::Legacy(unsigned) => UnverifiedTransactionWrapper::Legacy( + UnverifiedLegacyTransaction::new_with_network_v(unsigned, r, s, v, transaction.hash) .map_err(|err| ERRL!("'Transaction::new' error {}", err.to_string()))?, - ) - } else if transaction.transaction_type.unwrap() == type_2 { - UnverifiedTransactionWrapper::Eip1559( - UnverifiedEip1559Transaction::new( - Eip1559Transaction { - chain_id, - data: transaction.input.0, - max_priority_fee_per_gas: transaction - .max_priority_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?, - max_fee_per_gas: transaction - .max_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?, - gas: transaction.gas, - value: transaction.value, - nonce: transaction.nonce, - action: match transaction.to { - Some(addr) => Action::Call(addr), - None => Action::Create, - }, - access_list: map_access_list(&transaction.access_list), - }, - r, - s, - v, - transaction.hash, - ) + ), + TransactionWrapper::Eip2930(unsigned) => UnverifiedTransactionWrapper::Eip2930( + UnverifiedEip2930Transaction::new(unsigned, r, s, v, transaction.hash) .map_err(|err| ERRL!("'Transaction::new' error {}", err.to_string()))?, - ) - } else { - return Err(ERRL!("'Transaction::transaction_type' unsupported")); - } + ), + TransactionWrapper::Eip1559(unsigned) => UnverifiedTransactionWrapper::Eip1559( + UnverifiedEip1559Transaction::new(unsigned, r, s, v, transaction.hash) + .map_err(|err| ERRL!("'Transaction::new' error {}", err.to_string()))?, + ), }; + Ok(try_s!(SignedEthTx::new(unverified))) } From 66f8ff76558e8330d08c6694d9e4efc6dbc3b7a3 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 31 Mar 2024 16:40:01 +0500 Subject: [PATCH 45/71] more refactor signed_tx_from_web3_tx fn: use match --- mm2src/coins/eth.rs | 54 ++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index f9035e642b..1c19c1e548 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5875,35 +5875,39 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { let gas_price = transaction .gas_price .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; + tx_builder.with_gas_price(gas_price) - } else { - let max_fee_per_gas = transaction - .max_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?; - let max_priority_fee_per_gas = transaction - .max_priority_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?; - tx_builder.with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) - }; - tx_builder - .with_chain_id(chain_id) - .with_access_list(map_access_list(&transaction.access_list)) + }, + TxType::Type1 | TxType::Type2 => { + let chain_id_s = transaction + .chain_id + .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? + .to_string(); + let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; + let tx_builder = if tx_type == TxType::Type1 { + let gas_price = transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; + tx_builder.with_gas_price(gas_price) + } else { + let max_fee_per_gas = transaction + .max_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?; + let max_priority_fee_per_gas = transaction + .max_priority_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?; + tx_builder.with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) + }; + tx_builder + .with_chain_id(chain_id) + .with_access_list(map_access_list(&transaction.access_list)) + }, + TxType::Invalid => return Err(ERRL!("Internal error: 'tx_type' invalid")), }; let unsigned = tx_builder.build().map_err(|err| err.to_string())?; From 0ec27e914bd8d3eb0dd40d1ff9500dad459ff742 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 1 Apr 2024 18:04:37 +0500 Subject: [PATCH 46/71] refactor getting gas price and fee per gas --- mm2src/coins/eth.rs | 122 +++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 75 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 1c19c1e548..bb356cb206 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -203,13 +203,13 @@ type EthPrivKeyPolicy = PrivKeyPolicy; #[derive(Clone, Debug)] pub(crate) struct LegacyGasPrice { - pub gas_price: U256, + pub(crate) gas_price: U256, } #[derive(Clone, Debug)] pub(crate) struct Eip1559FeePerGas { - pub max_priority_fee_per_gas: U256, - pub max_fee_per_gas: U256, + pub(crate) max_fee_per_gas: U256, + pub(crate) max_priority_fee_per_gas: U256, } #[derive(Clone, Debug)] @@ -218,6 +218,25 @@ pub(crate) enum PayForGasOption { Eip1559(Eip1559FeePerGas), } +impl PayForGasOption { + fn get_gas_price(&self) -> Option { + match self { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => Some(*gas_price), + PayForGasOption::Eip1559(..) => None, + } + } + + fn get_fee_per_gas(&self) -> (Option, Option) { + match self { + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas, + max_priority_fee_per_gas, + }) => (Some(*max_fee_per_gas), Some(*max_priority_fee_per_gas)), + PayForGasOption::Legacy(..) => (None, None), + } + } +} + type GasDetails = (U256, PayForGasOption); #[derive(Debug, Display)] @@ -765,29 +784,15 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { return MmError::err(WithdrawError::BroadcastExpected(error)); } - let gas_price = if let PayForGasOption::Legacy(ref legacy_gas_price) = pay_for_gas_option { - Some(legacy_gas_price.gas_price) - } else { - None - }; - - let (max_priority_fee_per_gas, max_fee_per_gas) = if let PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - max_fee_per_gas, - }) = pay_for_gas_option - { - (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) - } else { - (None, None) - }; - + let gas_price = pay_for_gas_option.get_gas_price(); + let (max_fee_per_gas, max_priority_fee_per_gas) = pay_for_gas_option.get_fee_per_gas(); let tx_to_send = TransactionRequest { from: coin.my_address, to: Some(to_addr), gas: Some(gas), gas_price, - max_priority_fee_per_gas, max_fee_per_gas, + max_priority_fee_per_gas, value: Some(eth_value), data: Some(data.clone().into()), nonce: None, @@ -2503,29 +2508,15 @@ async fn sign_and_send_transaction_with_metamask( .await ); - let gas_price = if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { - Some(gas_price) - } else { - None - }; - - let (max_priority_fee_per_gas, max_fee_per_gas) = if let PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - max_fee_per_gas, - }) = pay_for_gas_option - { - (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) - } else { - (None, None) - }; - + let gas_price = pay_for_gas_option.get_gas_price(); + let (max_fee_per_gas, max_priority_fee_per_gas) = pay_for_gas_option.get_fee_per_gas(); let tx_to_send = TransactionRequest { from: coin.my_address, to, gas: Some(gas), gas_price, - max_priority_fee_per_gas, max_fee_per_gas, + max_priority_fee_per_gas, value: Some(value), data: Some(data.clone().into()), nonce: None, @@ -4990,34 +4981,34 @@ impl EthCoin { let pay_result = (|| -> MmResult { match swap_fee_policy { SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.low.max_priority_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, max_fee_per_gas: wei_from_big_decimal( &fee_per_gas.low.max_fee_per_gas, ETH_GWEI_DECIMALS, )?, - })), - SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.medium.max_priority_fee_per_gas, + &fee_per_gas.low.max_priority_fee_per_gas, ETH_GWEI_DECIMALS, )?, + })), + SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas: wei_from_big_decimal( &fee_per_gas.medium.max_fee_per_gas, ETH_GWEI_DECIMALS, )?, - })), - _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.high.max_priority_fee_per_gas, + &fee_per_gas.medium.max_priority_fee_per_gas, ETH_GWEI_DECIMALS, )?, + })), + _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas: wei_from_big_decimal( &fee_per_gas.high.max_fee_per_gas, ETH_GWEI_DECIMALS, )?, + max_priority_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.high.max_priority_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, })), } })(); @@ -5272,17 +5263,12 @@ impl EthTxFeeDetails { }) => (max_fee_per_gas, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)), }; let gas_price = u256_to_big_decimal(gas_price, ETH_DECIMALS)?; - let (max_fee_per_gas, max_priority_fee_per_gas) = if let ( - Some(max_fee_per_gas), - Some(max_priority_fee_per_gas), - ) = (max_fee_per_gas, max_priority_fee_per_gas) - { - ( + let (max_fee_per_gas, max_priority_fee_per_gas) = match (max_fee_per_gas, max_priority_fee_per_gas) { + (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => ( Some(u256_to_big_decimal(max_fee_per_gas, ETH_DECIMALS)?), Some(u256_to_big_decimal(max_priority_fee_per_gas, ETH_DECIMALS)?), - ) - } else { - (None, None) + ), + (_, _) => (None, None), }; let gas_u64 = u64::try_from(gas).map_to_mm(|e| NumConversError::new(e.to_string()))?; @@ -6431,22 +6417,8 @@ async fn get_eth_gas_details_from_withdraw_fee( eth_value }; - let gas_price = if let PayForGasOption::Legacy(LegacyGasPrice { gas_price }) = pay_for_gas_option { - Some(gas_price) - } else { - None - }; - - let (max_priority_fee_per_gas, max_fee_per_gas) = if let PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, - max_fee_per_gas, - }) = pay_for_gas_option - { - (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) - } else { - (None, None) - }; - + let gas_price = pay_for_gas_option.get_gas_price(); + let (max_fee_per_gas, max_priority_fee_per_gas) = pay_for_gas_option.get_fee_per_gas(); let estimate_gas_req = CallRequest { value: Some(eth_value_for_estimate), data: Some(data), @@ -6515,17 +6487,17 @@ fn call_request_with_pay_for_gas_option(call_request: CallRequest, pay_for_gas_o match pay_for_gas_option { PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => CallRequest { gas_price: Some(gas_price), - max_priority_fee_per_gas: None, max_fee_per_gas: None, + max_priority_fee_per_gas: None, ..call_request }, PayForGasOption::Eip1559(Eip1559FeePerGas { - max_priority_fee_per_gas, max_fee_per_gas, + max_priority_fee_per_gas, }) => CallRequest { gas_price: None, - max_priority_fee_per_gas: Some(max_priority_fee_per_gas), max_fee_per_gas: Some(max_fee_per_gas), + max_priority_fee_per_gas: Some(max_priority_fee_per_gas), ..call_request }, } From e473517812d3ca9c03ca45e7d340a4e48b0067be Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 4 Apr 2024 19:53:21 +0500 Subject: [PATCH 47/71] fix comment --- mm2src/coins/rpc_command/get_estimated_fees.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index f62146906b..67183f8a6d 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -12,8 +12,12 @@ use mm2_err_handle::prelude::*; use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; -const ETH_SUPPORTED_CHAIN_ID: u64 = 1; // only eth mainnet is supported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) - // To support fee estimations for other chains add a FeeEstimatorContext for a new chain + +/// Chain id for which fee per gas estimations are supported. +/// Only eth mainnet currently is supported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) +/// TODO: make a setting in the coins file (platform coin) to indicate which chains support: +/// typed transactions, fee estimations by fee history and/or a gas provider. +const ETH_SUPPORTED_CHAIN_ID: u64 = 1; #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] From 7fa8d1f4a503a9e48ff117177ea395cf5726dba2 Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 4 Apr 2024 19:54:21 +0500 Subject: [PATCH 48/71] fix due to changes in gas api provider response --- mm2src/coins/eth.rs | 5 +++ mm2src/coins/eth/eip1559_gas_fee.rs | 53 ++--------------------------- 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index bb356cb206..5f79256ce8 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4942,6 +4942,7 @@ impl EthCoin { .ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; let gas_api_conf = ctx.conf["gas_api"].clone(); if gas_api_conf.is_null() { + debug!("No eth gas api provider config, using only history estimator"); return history_estimator_fut .await .map_err(|e| MmError::new(Web3RpcError::Internal(e.to_string()))); @@ -4956,6 +4957,10 @@ impl EthCoin { }; provider_estimator_fut .or_else(|provider_estimator_err| { + debug!( + "Call to eth gas api provider failed {}, using internal fee estimator", + provider_estimator_err + ); history_estimator_fut.map_err(move |history_estimator_err| { MmError::new(Web3RpcError::Internal(format!( "All gas api requests failed, provider estimator error: {}, history estimator error: {}", diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index bceb321540..296a1ea73c 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -306,16 +306,12 @@ impl FeePerGasSimpleEstimator { mod gas_api { use super::FeePerGasEstimated; use crate::eth::{Web3RpcError, Web3RpcResult}; - use common::log::debug; use http::StatusCode; use mm2_err_handle::mm_error::MmError; use mm2_err_handle::prelude::*; use mm2_net::transport::slurp_url_with_headers; use mm2_number::BigDecimal; - use serde::de::{Deserializer, SeqAccess, Visitor}; use serde_json::{self as json}; - use std::collections::HashMap; - use std::fmt; use url::Url; lazy_static! { @@ -383,7 +379,6 @@ mod gas_api { .mm_err(|e| e.to_string())?; if resp.0 != StatusCode::OK { let error = format!("{} failed with status code {}", url, resp.0); - debug!("infura gas api error: {}", error); return MmError::err(error); } let estimated_fees = json::from_slice(&resp.2).map_to_mm(|e| e.to_string())?; @@ -422,51 +417,13 @@ mod gas_api { #[derive(Clone, Debug, Deserialize)] pub(crate) struct BlocknativeEstimatedPrices { pub confidence: u32, - pub price: u64, + pub price: BigDecimal, #[serde(rename = "maxPriorityFeePerGas")] pub max_priority_fee_per_gas: BigDecimal, #[serde(rename = "maxFeePerGas")] pub max_fee_per_gas: BigDecimal, } - #[allow(dead_code)] - #[derive(Clone, Debug, Deserialize)] - pub(crate) struct BlocknativeBaseFee { - pub confidence: u32, - #[serde(rename = "baseFee")] - pub base_fee: BigDecimal, - } - - struct BlocknativeEstimatedBaseFees {} - - impl BlocknativeEstimatedBaseFees { - /// Parse blocknative's base_fees in pending blocks : '[ "pending+1" : {}, "pending+2" : {}, ..]' removing 'pending+n' - fn parse_pending<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - struct PendingBlockFeeParser; - impl<'de> Visitor<'de> for PendingBlockFeeParser { - type Value = Vec>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("[u32, BigDecimal]") - } - - fn visit_seq>(self, mut seq: A) -> Result { - let mut pending_block_fees = Vec::>::new(); - while let Some(fees) = seq.next_element::>>()? { - if let Some(fees) = fees.iter().next() { - pending_block_fees.push(fees.1.clone()); - } - } - Ok(pending_block_fees) - } - } - deserializer.deserialize_any(PendingBlockFeeParser {}) - } - } - /// Blocknative gas prices response /// see https://docs.blocknative.com/gas-prediction/gas-platform #[allow(dead_code)] @@ -476,18 +433,13 @@ mod gas_api { pub network: String, pub unit: String, #[serde(rename = "maxPrice")] - pub max_price: u32, + pub max_price: BigDecimal, #[serde(rename = "currentBlockNumber")] pub current_block_number: u32, #[serde(rename = "msSinceLastBlock")] pub ms_since_last_block: u32, #[serde(rename = "blockPrices")] pub block_prices: Vec, - #[serde( - rename = "estimatedBaseFees", - deserialize_with = "BlocknativeEstimatedBaseFees::parse_pending" - )] - pub estimated_base_fees: Vec>, } /// Blocknative gas api provider caller @@ -523,7 +475,6 @@ mod gas_api { .mm_err(|e| e.to_string())?; if resp.0 != StatusCode::OK { let error = format!("{} failed with status code {}", url, resp.0); - debug!("blocknative gas api error: {}", error); return MmError::err(error); } let block_prices = json::from_slice(&resp.2).map_err(|e| e.to_string())?; From a13fbfb129ebb6aac341d5c02840eac9ef8de62d Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 4 Apr 2024 21:02:34 +0500 Subject: [PATCH 49/71] moved FeeEstimatorContext to eth platform coin --- mm2src/coins/eth.rs | 17 +- mm2src/coins/eth/eth_tests.rs | 6 + mm2src/coins/eth/eth_wasm_tests.rs | 1 + mm2src/coins/eth/v2_activation.rs | 32 +++- .../coins/rpc_command/get_estimated_fees.rs | 159 +++++++++--------- mm2src/mm2_core/src/mm_ctx.rs | 3 - 6 files changed, 123 insertions(+), 95 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 5f79256ce8..605402d6f7 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -32,7 +32,7 @@ use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; use common::custom_futures::timeout::FutureTimerExt; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError, Timer}; -use common::executor::{AbortSettings, SpawnAbortable}; +use common::executor::{AbortOnDropHandle, AbortSettings, SpawnAbortable}; use common::log::{debug, error, info, warn}; use common::number_type_casting::SafeTypeCastingNumbers; use common::{get_utc_timestamp, now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -469,6 +469,16 @@ impl TryFrom for EthPrivKeyBuildPolicy { } } +/// Gas fee estimator loop context, runs a loop to estimate max fee and max priority fee per gas according to EIP-1559 for the next block +/// +/// This FeeEstimatorContext handles rpc requests which start and stop gas fee estimation loop and handles the loop itself. +/// FeeEstimatorContext keeps the latest estimated gas fees to return them on rpc request +pub(crate) struct FeeEstimatorContext { + /// Latest estimated gas fee values + pub(crate) estimated_fees: Arc>, + pub(crate) abort_handler: AsyncMutex>, +} + /// pImpl idiom. pub struct EthCoinImpl { ticker: String, @@ -496,6 +506,8 @@ pub struct EthCoinImpl { /// consisting of the token address and token ID, separated by a comma. This field is essential for tracking the NFT assets /// information (chain & contract type, amount etc.), where ownership and amount, in ERC1155 case, might change over time. pub nfts_infos: Arc>>, + /// Context for eth fee per gas estimator loop. Created if coin supports fee per gas estimation + pub(crate) platform_fee_estimator_ctx: Option>>, /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation /// and on [`MmArc::stop`]. pub abortable_system: AbortableQueue, @@ -6163,6 +6175,8 @@ pub async fn eth_coin_from_conf_and_request( // all spawned futures related to `ETH` coin will be aborted as well. let abortable_system = try_s!(ctx.abortable_system.create_subsystem()); + let platform_fee_estimator_ctx = FeeEstimatorContext::new(ctx, conf, &coin_type).await; + let coin = EthCoinImpl { priv_key_policy: key_pair, my_address, @@ -6183,6 +6197,7 @@ pub async fn eth_coin_from_conf_and_request( nonce_lock, erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx, abortable_system, }; diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 53daed40a6..48f394538e 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -156,6 +156,7 @@ fn eth_coin_from_keypair( nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx: None, abortable_system: AbortableQueue::default(), })); (ctx, eth_coin) @@ -365,6 +366,7 @@ fn test_nonce_several_urls() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx: None, abortable_system: AbortableQueue::default(), })); @@ -415,6 +417,7 @@ fn test_wait_for_payment_spend_timeout() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx: None, abortable_system: AbortableQueue::default(), }; @@ -1127,6 +1130,7 @@ fn test_message_hash() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx: None, abortable_system: AbortableQueue::default(), })); @@ -1173,6 +1177,7 @@ fn test_sign_verify_message() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx: None, abortable_system: AbortableQueue::default(), })); @@ -1228,6 +1233,7 @@ fn test_eth_extract_secret() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx: None, abortable_system: AbortableQueue::default(), })); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index 395be801a5..839960c855 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -48,6 +48,7 @@ async fn test_send() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx: None, abortable_system: AbortableQueue::default(), })); let maker_payment_args = SendPaymentArgs { diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index f9adfc7817..130fdd03fe 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -285,13 +285,16 @@ impl EthCoin { // all spawned futures related to `ERC20` coin will be aborted as well. let abortable_system = ctx.abortable_system.create_subsystem()?; + let coin_type = EthCoinType::Erc20 { + platform: protocol.platform, + token_addr: protocol.token_addr, + }; + let platform_fee_estimator_ctx = FeeEstimatorContext::new(&ctx, &conf, &coin_type).await; + let token = EthCoinImpl { priv_key_policy: self.priv_key_policy.clone(), my_address: self.my_address, - coin_type: EthCoinType::Erc20 { - platform: protocol.platform, - token_addr: protocol.token_addr, - }, + coin_type, sign_message_prefix: self.sign_message_prefix.clone(), swap_contract_address: self.swap_contract_address, fallback_swap_contract: self.fallback_swap_contract, @@ -308,6 +311,7 @@ impl EthCoin { nonce_lock: self.nonce_lock.clone(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx, abortable_system, }; @@ -326,17 +330,25 @@ impl EthCoin { let chain = Chain::from_ticker(self.ticker())?; let ticker = chain.to_nft_ticker().to_string(); + let ctx = MmArc::from_weak(&self.ctx) + .ok_or_else(|| String::from("No context")) + .map_err(EthTokenActivationError::InternalError)?; + + let conf = coin_conf(&ctx, &ticker); + // Create an abortable system linked to the `platform_coin` (which is self) so if the platform coin is disabled, // all spawned futures related to global Non-Fungible Token will be aborted as well. let abortable_system = self.abortable_system.create_subsystem()?; let nft_infos = get_nfts_for_activation(&chain, &self.my_address, url).await?; + let coin_type = EthCoinType::Nft { + platform: self.ticker.clone(), + }; + let platform_fee_estimator_ctx = FeeEstimatorContext::new(&ctx, &conf, &coin_type).await; let global_nft = EthCoinImpl { ticker, - coin_type: EthCoinType::Nft { - platform: self.ticker.clone(), - }, + coin_type, priv_key_policy: self.priv_key_policy.clone(), my_address: self.my_address, sign_message_prefix: self.sign_message_prefix.clone(), @@ -354,6 +366,7 @@ impl EthCoin { nonce_lock: self.nonce_lock.clone(), erc20_tokens_infos: Default::default(), nfts_infos: Arc::new(AsyncMutex::new(nft_infos)), + platform_fee_estimator_ctx, abortable_system, }; Ok(EthCoin(Arc::new(global_nft))) @@ -439,11 +452,13 @@ pub async fn eth_coin_from_conf_and_request_v2( // Create an abortable system linked to the `MmCtx` so if the app is stopped on `MmArc::stop`, // all spawned futures related to `ETH` coin will be aborted as well. let abortable_system = ctx.abortable_system.create_subsystem()?; + let coin_type = EthCoinType::Eth; + let platform_fee_estimator_ctx = FeeEstimatorContext::new(ctx, conf, &coin_type).await; let coin = EthCoinImpl { priv_key_policy, my_address, - coin_type: EthCoinType::Eth, + coin_type, sign_message_prefix, swap_contract_address: req.swap_contract_address, fallback_swap_contract: req.fallback_swap_contract, @@ -460,6 +475,7 @@ pub async fn eth_coin_from_conf_and_request_v2( nonce_lock, erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + platform_fee_estimator_ctx, abortable_system, }; diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 67183f8a6d..c6eae5b086 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,14 +1,14 @@ //! RPCs to start/stop gas fee estimator and get estimated base and priority fee per gas -use crate::eth::{EthCoin, FeePerGasEstimated}; -use crate::{from_ctx, NumConversError}; -use crate::{lp_coinfind_or_err, AsyncMutex, CoinFindError, MmCoinEnum}; -use common::executor::{spawn_abortable, AbortOnDropHandle, Timer}; +use crate::eth::{EthCoin, EthCoinType, FeeEstimatorContext, FeePerGasEstimated}; +use crate::{lp_coinfind_or_err, AsyncMutex, CoinFindError, MmCoinEnum, NumConversError}; +use common::executor::{spawn_abortable, Timer}; use common::log::debug; use common::{HttpStatusCode, StatusCode}; use futures::compat::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use serde_json::Value as Json; use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; @@ -24,10 +24,8 @@ const ETH_SUPPORTED_CHAIN_ID: u64 = 1; pub enum FeeEstimatorError { #[display(fmt = "No such coin {}", coin)] NoSuchCoin { coin: String }, - #[display(fmt = "Gas fee estimation not supported for this coin")] + #[display(fmt = "Gas fee estimation not supported for this coin or chain id")] CoinNotSupported, - #[display(fmt = "Chain id not supported")] - ChainNotSupported, #[display(fmt = "Gas fee estimator is already started")] AlreadyStarted, #[display(fmt = "Transport error: {}", _0)] @@ -43,7 +41,6 @@ impl HttpStatusCode for FeeEstimatorError { match self { FeeEstimatorError::NoSuchCoin { .. } | FeeEstimatorError::CoinNotSupported - | FeeEstimatorError::ChainNotSupported | FeeEstimatorError::AlreadyStarted | FeeEstimatorError::NotRunning => StatusCode::BAD_REQUEST, FeeEstimatorError::Transport(_) | FeeEstimatorError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, @@ -67,50 +64,52 @@ impl From for FeeEstimatorError { } } -/// Gas fee estimator loop context, -/// runs a loop to estimate max fee and max priority fee per gas according to EIP-1559 for the next block -/// -/// This FeeEstimatorContext handles rpc requests which start and stop gas fee estimation loop and handles the loop itself. -/// FeeEstimatorContext maintains a list of eth coins or tokens which connected and use the estimator. -/// The loop estimation starts when first eth coin or token calls the start rpc and stops when the last coin or token, using it, calls the stop rpc. -/// FeeEstimatorContext keeps the latest estimated gas fees in the context and returns them as rpc response -pub struct FeeEstimatorContext { - estimated_fees: Arc>, - abort_handler: AsyncMutex>, -} - impl FeeEstimatorContext { - fn new() -> Result { - Ok(Self { - estimated_fees: Default::default(), - abort_handler: AsyncMutex::new(None), - }) - } - - fn from_ctx(ctx: MmArc) -> Result, String> { - Ok(try_s!(from_ctx(&ctx.fee_estimator_ctx, move || { - FeeEstimatorContext::new() - }))) + pub(crate) async fn new(ctx: &MmArc, conf: &Json, coin_type: &EthCoinType) -> Option>> { + let chain_id = conf["chain_id"].as_u64()?; + if chain_id != ETH_SUPPORTED_CHAIN_ID { + return None; + }; + match coin_type { + EthCoinType::Eth => Some(Arc::new(AsyncMutex::new(Self { + estimated_fees: Default::default(), + abort_handler: AsyncMutex::new(None), + }))), + EthCoinType::Erc20 { platform, .. } | EthCoinType::Nft { platform, .. } => { + let platform_coin = lp_coinfind_or_err(ctx, platform).await.ok()?; + match platform_coin { + MmCoinEnum::EthCoin(eth_coin) => eth_coin.platform_fee_estimator_ctx.as_ref().cloned(), + _ => None, + } + }, + } } /// Fee estimation update period in secs, basically equals to eth blocktime const fn get_refresh_interval() -> f64 { 15.0 } - async fn start_if_not_running(ctx: MmArc, coin: &EthCoin) -> Result<(), MmError> { - let estimator_ctx = Self::from_ctx(ctx.clone())?; + fn get_estimator_ctx(coin: &EthCoin) -> Result>, MmError> { + Ok(coin + .platform_fee_estimator_ctx + .as_ref() + .ok_or_else(|| MmError::new(FeeEstimatorError::CoinNotSupported))? + .clone()) + } + + async fn start_if_not_running(coin: &EthCoin) -> Result<(), MmError> { + let estimator_ctx = Self::get_estimator_ctx(coin)?; + let estimator_ctx = estimator_ctx.lock().await; let mut handler = estimator_ctx.abort_handler.lock().await; if handler.is_some() { return MmError::err(FeeEstimatorError::AlreadyStarted); } - *handler = Some(spawn_abortable(fee_estimator_loop( - estimator_ctx.estimated_fees.clone(), - coin.clone(), - ))); + *handler = Some(spawn_abortable(Self::fee_estimator_loop(coin.clone()))); Ok(()) } - async fn request_to_stop(ctx: MmArc) -> Result<(), MmError> { - let estimator_ctx = Self::from_ctx(ctx)?; + async fn request_to_stop(coin: &EthCoin) -> Result<(), MmError> { + let estimator_ctx = Self::get_estimator_ctx(coin)?; + let estimator_ctx = estimator_ctx.lock().await; let mut handle_guard = estimator_ctx.abort_handler.lock().await; // Handler will be dropped here, stopping the spawned loop immediately handle_guard @@ -119,55 +118,49 @@ impl FeeEstimatorContext { .or_mm_err(|| FeeEstimatorError::NotRunning) } - async fn get_estimated_fees(ctx: MmArc) -> Result> { - let estimator_ctx = Self::from_ctx(ctx.clone())?; + async fn get_estimated_fees(coin: &EthCoin) -> Result> { + let estimator_ctx = Self::get_estimator_ctx(coin)?; + let estimator_ctx = estimator_ctx.lock().await; let estimated_fees = estimator_ctx.estimated_fees.lock().await; Ok(estimated_fees.clone()) } - fn check_if_chain_id_supported(coin: &EthCoin) -> Result<(), MmError> { - if let Some(chain_id) = coin.chain_id { - if chain_id != ETH_SUPPORTED_CHAIN_ID { - return MmError::err(FeeEstimatorError::ChainNotSupported); - } - } - Ok(()) - } - - async fn check_if_coin_supported(ctx: &MmArc, ticker: &str) -> Result> { + async fn check_if_estimator_supported(ctx: &MmArc, ticker: &str) -> Result> { let eth_coin = match lp_coinfind_or_err(ctx, ticker).await? { MmCoinEnum::EthCoin(eth) => eth, _ => return MmError::err(FeeEstimatorError::CoinNotSupported), }; - - Self::check_if_chain_id_supported(ð_coin)?; + if eth_coin.platform_fee_estimator_ctx.is_none() { + return MmError::err(FeeEstimatorError::CoinNotSupported); + } Ok(eth_coin) } -} -/// Loop polling gas fee estimator -/// -/// This loop periodically calls get_eip1559_gas_fee which fetches fee per gas estimations from a gas api provider or calculates them internally -/// The retrieved data are stored in the fee estimator context -/// To connect to the chain and gas api provider the web3 instances are used from an EthCoin coin passed in the start rpc param, -/// so this coin must be enabled first. -/// Once the loop started any other EthCoin in mainnet may request fee estimations. -/// It is up to GUI to start and stop the loop when it needs it (considering that the data in context may be used -/// for any coin with Eth or Erc20 type from the mainnet). -async fn fee_estimator_loop(estimated_fees: Arc>, coin: EthCoin) { - loop { - let started = common::now_float(); - *estimated_fees.lock().await = coin.get_eip1559_gas_fee().compat().await.unwrap_or_default(); - - let elapsed = common::now_float() - started; - debug!( - "{FEE_ESTIMATOR_NAME} getting estimated values processed in {} seconds", - elapsed - ); - - let wait_secs = FeeEstimatorContext::get_refresh_interval() - elapsed; - let wait_secs = if wait_secs < 0.0 { 0.0 } else { wait_secs }; - Timer::sleep(wait_secs).await; + /// Loop polling gas fee estimator + /// + /// This loop periodically calls get_eip1559_gas_fee which fetches fee per gas estimations from a gas api provider or calculates them internally + /// The retrieved data are stored in the fee estimator context + /// To connect to the chain and gas api provider the web3 instances are used from an EthCoin coin passed in the start rpc param, + /// so this coin must be enabled first. + /// Once the loop started any other EthCoin in mainnet may request fee estimations. + /// It is up to GUI to start and stop the loop when it needs it (considering that the data in context may be used + /// for any coin with Eth or Erc20 type from the mainnet). + async fn fee_estimator_loop(coin: EthCoin) { + loop { + let started = common::now_float(); + if let Ok(estimator_ctx) = Self::get_estimator_ctx(&coin) { + let estimated_fees = coin.get_eip1559_gas_fee().compat().await.unwrap_or_default(); + let estimator_ctx = estimator_ctx.lock().await; + *estimator_ctx.estimated_fees.lock().await = estimated_fees; + } + + let elapsed = common::now_float() - started; + debug!("{FEE_ESTIMATOR_NAME} call to provider processed in {} seconds", elapsed); + + let wait_secs = FeeEstimatorContext::get_refresh_interval() - elapsed; + let wait_secs = if wait_secs < 0.0 { 0.0 } else { wait_secs }; + Timer::sleep(wait_secs).await; + } } } @@ -201,8 +194,8 @@ pub type FeeEstimatorResult = Result FeeEstimatorStartStopResult { - let coin = FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; - FeeEstimatorContext::start_if_not_running(ctx, &coin).await?; + let coin = FeeEstimatorContext::check_if_estimator_supported(&ctx, &req.coin).await?; + FeeEstimatorContext::start_if_not_running(&coin).await?; Ok(FeeEstimatorStartStopResponse { result: "Success".to_string(), }) @@ -210,8 +203,8 @@ pub async fn start_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopReque /// Stop gas priority fee estimator loop pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopRequest) -> FeeEstimatorStartStopResult { - FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; - FeeEstimatorContext::request_to_stop(ctx).await?; + let coin = FeeEstimatorContext::check_if_estimator_supported(&ctx, &req.coin).await?; + FeeEstimatorContext::request_to_stop(&coin).await?; Ok(FeeEstimatorStartStopResponse { result: "Success".to_string(), }) @@ -224,7 +217,7 @@ pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopReques /// /// Returns latest estimated fee per gas for the next block pub async fn get_eth_estimated_fee_per_gas(ctx: MmArc, req: FeeEstimatorRequest) -> FeeEstimatorResult { - FeeEstimatorContext::check_if_coin_supported(&ctx, &req.coin).await?; - let estimated_fees = FeeEstimatorContext::get_estimated_fees(ctx).await?; + let coin = FeeEstimatorContext::check_if_estimator_supported(&ctx, &req.coin).await?; + let estimated_fees = FeeEstimatorContext::get_estimated_fees(&coin).await?; Ok(estimated_fees) } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index 76a50f3f0d..04de2e4d87 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -135,8 +135,6 @@ pub struct MmCtx { /// asynchronous handle for rusqlite connection. #[cfg(not(target_arch = "wasm32"))] pub async_sqlite_connection: Constructible>>, - /// Context for eth fee per gas estimator loop - pub fee_estimator_ctx: Mutex>>, } impl MmCtx { @@ -183,7 +181,6 @@ impl MmCtx { nft_ctx: Mutex::new(None), #[cfg(not(target_arch = "wasm32"))] async_sqlite_connection: Constructible::default(), - fee_estimator_ctx: Mutex::new(None), } } From 04352738749a38eb03e08c1c93463ddcf70ee1d5 Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 4 Apr 2024 21:14:27 +0500 Subject: [PATCH 50/71] added comment --- mm2src/coins/rpc_command/get_estimated_fees.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index c6eae5b086..203756cc3c 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -65,6 +65,9 @@ impl From for FeeEstimatorError { } impl FeeEstimatorContext { + /// Creates gas fee estimator context if supported for this coin and chain id. Otherwise returns None. + /// When gas fee estimator rpc is called and no fee estimator was created + /// it is assumed it is not supported for the coin or chain or coin config is inappropriate pub(crate) async fn new(ctx: &MmArc, conf: &Json, coin_type: &EthCoinType) -> Option>> { let chain_id = conf["chain_id"].as_u64()?; if chain_id != ETH_SUPPORTED_CHAIN_ID { From d607ae333f8edcb431fb41cbe1c6eb9a6d60540f Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 4 Apr 2024 22:10:32 +0500 Subject: [PATCH 51/71] added memo comments --- mm2src/coins/eth/eip1559_gas_fee.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index 296a1ea73c..b50587dedd 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -281,6 +281,9 @@ impl FeePerGasSimpleEstimator { /// estimate priority fees by fee history fn calculate_with_history(fee_history: &FeeHistoryResult) -> Web3RpcResult { + // For estimation of max fee and max priority fee we use latest block base_fee but adjusted. + // Apparently for this simple fee estimator for assured high priority we should assume + // that the real base_fee may go up by 1,25 (i.e. if the block is full). This is covered by high priority ADJUST_MAX_FEE multiplier let latest_base_fee = fee_history .base_fee_per_gas .first() @@ -288,6 +291,9 @@ impl FeePerGasSimpleEstimator { .unwrap_or_else(|| U256::from(0)); let latest_base_fee = u256_to_big_decimal(latest_base_fee, ETH_GWEI_DECIMALS).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) let predicted_base_fee = Self::predict_base_fee(&fee_history.base_fee_per_gas); Ok(FeePerGasEstimated { base_fee: u256_to_big_decimal(predicted_base_fee, ETH_GWEI_DECIMALS) From 342d3f4b8cfc50b9c4310ca262bd3314de0d2e9d Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 8 Apr 2024 15:51:12 +0500 Subject: [PATCH 52/71] fix updated eth and web3 lib dependency urls --- Cargo.lock | 10 +++++----- mm2src/coins/Cargo.toml | 7 +++---- mm2src/crypto/Cargo.toml | 3 +-- mm2src/mm2_eth/Cargo.toml | 5 ++--- mm2src/mm2_main/Cargo.toml | 3 +-- mm2src/mm2_metamask/Cargo.toml | 3 +-- mm2src/mm2_net/Cargo.toml | 2 +- 7 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd2a9837fa..f4fab49ea7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,7 +2174,7 @@ dependencies = [ [[package]] name = "ethcore-transaction" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#931d2f6093ef4a9584e137cdff29504acd8e5955" dependencies = [ "ethereum-types", "ethkey", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#931d2f6093ef4a9584e137cdff29504acd8e5955" dependencies = [ "byteorder", "edit-distance", @@ -4109,7 +4109,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#931d2f6093ef4a9584e137cdff29504acd8e5955" [[package]] name = "memchr" @@ -8647,7 +8647,7 @@ dependencies = [ [[package]] name = "unexpected" version = "0.1.0" -source = "git+https://github.com/dimxy/mm2-parity-ethereum.git?branch=eip1559-support#7fbfac76c6a2b889ae334aaa770782321d4c6b2b" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#931d2f6093ef4a9584e137cdff29504acd8e5955" [[package]] name = "unicode-bidi" @@ -9009,7 +9009,7 @@ dependencies = [ [[package]] name = "web3" version = "0.19.0" -source = "git+https://github.com/dimxy/rust-web3?branch=add-chain-id#09f3b2818adfebd80985377c5fa7088dd7b11489" +source = "git+https://github.com/KomodoPlatform/rust-web3?tag=v0.20.0#01de1d732e61c920cfb2fb1533db7d7110c8a457" dependencies = [ "arrayvec 0.7.1", "base64 0.13.0", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 9a721071a7..f10db94c6c 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -44,9 +44,9 @@ derive_more = "0.99" ed25519-dalek = "1.0.1" enum_derives = { path = "../derives/enum_derives" } ethabi = { version = "17.0.0" } -ethcore-transaction = { git = "https://github.com/dimxy/mm2-parity-ethereum.git", branch = "eip1559-support" } +ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } ethereum-types = { version = "0.13", default-features = false, features = ["std", "serialize"] } -ethkey = { git = "https://github.com/dimxy/mm2-parity-ethereum.git", branch = "eip1559-support" } +ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } # Waiting for https://github.com/rust-lang/rust/issues/54725 to use on Stable. #enum_dispatch = "0.1" futures01 = { version = "0.1", package = "futures" } @@ -106,8 +106,7 @@ url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } # One of web3 dependencies is the old `tokio-uds 0.1.7` which fails cross-compiling to ARM. # We don't need the default web3 features at all since we added our own web3 transport using shared HYPER instance. -# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } -web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, branch = "add-chain-id" } +web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false } zbase32 = "0.1.2" zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.0" } zcash_extras = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.0" } diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index fff3f9e6f0..8fd9a26cde 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -43,8 +43,7 @@ trezor = { path = "../trezor" } mm2_eth = { path = "../mm2_eth" } mm2_metamask = { path = "../mm2_metamask" } wasm-bindgen-test = { version = "0.3.2" } -# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } -web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, branch = "add-chain-id" } +web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false } [features] trezor-udp = ["trezor/trezor-udp"] diff --git a/mm2src/mm2_eth/Cargo.toml b/mm2src/mm2_eth/Cargo.toml index 0bea2e7c77..bf4f328aae 100644 --- a/mm2src/mm2_eth/Cargo.toml +++ b/mm2src/mm2_eth/Cargo.toml @@ -8,7 +8,7 @@ doctest = false [dependencies] ethabi = { version = "17.0.0" } -ethkey = { git = "https://github.com/dimxy/mm2-parity-ethereum.git", branch = "eip1559-support" } +ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } hex = "0.4.2" indexmap = "1.7.0" itertools = "0.10" @@ -16,5 +16,4 @@ mm2_err_handle = { path = "../mm2_err_handle" } secp256k1 = { version = "0.20", features = ["recovery"] } serde = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } -# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } -web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, branch = "add-chain-id" } +web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 21cc7eaceb..ef3c6cc049 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -124,8 +124,7 @@ winapi = "0.3" mm2_test_helpers = { path = "../mm2_test_helpers" } mocktopus = "0.8.0" testcontainers = "0.15.0" -# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false, features = ["http"] } -web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, features = ["http"], branch = "add-chain-id" } +web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false, features = ["http"] } [build-dependencies] chrono = "0.4" diff --git a/mm2src/mm2_metamask/Cargo.toml b/mm2src/mm2_metamask/Cargo.toml index 5064c60680..e26be8c434 100644 --- a/mm2src/mm2_metamask/Cargo.toml +++ b/mm2src/mm2_metamask/Cargo.toml @@ -20,5 +20,4 @@ serde_json = { version = "1", features = ["preserve_order", "raw_value"] } serde_derive = "1.0" wasm-bindgen = "0.2.86" wasm-bindgen-futures = { version = "0.4.1" } -# web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false, features = ["eip-1193"] } -web3 = { git = "https://github.com/dimxy/rust-web3", default-features = false, features = ["eip-1193"], branch = "add-chain-id" } +web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false, features = ["eip-1193"] } diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index cbea9a86bf..fd42e034ac 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -17,7 +17,7 @@ bytes = "1.1" cfg-if = "1.0" common = { path = "../common" } derive_more = "0.99" -ethkey = { git = "https://github.com/dimxy/mm2-parity-ethereum.git", branch = "eip1559-support" } +ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } http = "0.2" lazy_static = "1.4" From 531f1afb6832974583ae7110575769b952393e77 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 8 Apr 2024 16:25:48 +0500 Subject: [PATCH 53/71] refactor on review notes --- mm2src/coins/eth.rs | 437 +++++++++--------- mm2src/coins/eth/eth_tests.rs | 10 +- .../coins/rpc_command/get_estimated_fees.rs | 3 +- 3 files changed, 226 insertions(+), 224 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 605402d6f7..479e4020bf 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2454,7 +2454,6 @@ async fn sign_transaction_with_keypair( status.status(tags!(), "get_gas_price…"); let pay_for_gas_option = try_tx_s!( coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) - .compat() .await ); @@ -2516,7 +2515,6 @@ async fn sign_and_send_transaction_with_metamask( let pay_for_gas_option = try_tx_s!( coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) - .compat() .await ); @@ -4307,27 +4305,25 @@ impl EthCoin { /// /// Also, note that the contract call has to be initiated by my wallet address, /// because [`CallRequest::from`] is set to [`EthCoinImpl::my_address`]. - fn estimate_gas_for_contract_call(&self, contract_addr: Address, call_data: Bytes) -> Web3RpcFut { + async fn estimate_gas_for_contract_call(&self, contract_addr: Address, call_data: Bytes) -> Web3RpcResult { let coin = self.clone(); let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(self.get_swap_transaction_fee_policy()); - Box::new( - coin.get_swap_pay_for_gas_option(fee_policy_for_estimate) - .and_then(move |pay_for_gas_option| { - let eth_value = U256::zero(); - let estimate_gas_req = CallRequest { - value: Some(eth_value), - data: Some(call_data), - from: Some(coin.my_address), - to: Some(contract_addr), - ..CallRequest::default() - }; - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - let estimate_gas_req = call_request_with_pay_for_gas_option(estimate_gas_req, pay_for_gas_option); - coin.estimate_gas_wrapper(estimate_gas_req) - .map_to_mm_fut(Web3RpcError::from) - }), - ) + let pay_for_gas_option = coin.get_swap_pay_for_gas_option(fee_policy_for_estimate).await?; + let eth_value = U256::zero(); + let estimate_gas_req = CallRequest { + value: Some(eth_value), + data: Some(call_data), + from: Some(coin.my_address), + to: Some(contract_addr), + ..CallRequest::default() + }; + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + let estimate_gas_req = call_request_with_pay_for_gas_option(estimate_gas_req, pay_for_gas_option); + coin.estimate_gas_wrapper(estimate_gas_req) + .compat() + .await + .map_to_mm(Web3RpcError::from) } fn eth_balance(&self) -> BalanceFut { @@ -4430,7 +4426,6 @@ impl EthCoin { let gas_limit = try_tx_s!( coin.estimate_gas_for_contract_call(token_addr, Bytes::from(data.clone())) - .compat() .await ); @@ -4889,7 +4884,6 @@ impl EthCoin { pub async fn get_watcher_reward_amount(&self, wait_until: u64) -> Result> { let pay_for_gas_option = repeatable!(async { self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()) - .compat() .await .retry_on_err() }) @@ -4906,137 +4900,123 @@ impl EthCoin { } /// Get gas price - pub fn get_gas_price(&self) -> Web3RpcFut { + pub async fn get_gas_price(&self) -> Web3RpcResult { let coin = self.clone(); - let fut = async move { - let eth_gas_price_fut = async { - match coin.gas_price().await { - Ok(eth_gas) => Some(eth_gas), - Err(e) => { - error!("Error {} on eth_gasPrice request", e); - None - }, - } + let eth_gas_price_fut = async { + match coin.gas_price().await { + Ok(eth_gas) => Some(eth_gas), + Err(e) => { + error!("Error {} on eth_gasPrice request", e); + None + }, } - .boxed(); - - let eth_fee_history_price_fut = async { - match coin.eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]).await { - Ok(res) => res - .base_fee_per_gas - .first() - .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)), - Err(e) => { - debug!("Error {} on eth_feeHistory request", e); - None - }, - } + } + .boxed(); + + let eth_fee_history_price_fut = async { + match coin.eth_fee_history(U256::from(1u64), BlockNumber::Latest, &[]).await { + Ok(res) => res + .base_fee_per_gas + .first() + .map(|val| increase_by_percent_one_gwei(*val, BASE_BLOCK_FEE_DIFF_PCT)), + Err(e) => { + debug!("Error {} on eth_feeHistory request", e); + None + }, } - .boxed(); - - let (eth_gas_price, eth_fee_history_price) = join(eth_gas_price_fut, eth_fee_history_price_fut).await; - // on editions < 2021 the compiler will resolve array.into_iter() as (&array).into_iter() - // https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterator-for-arrays.html#details - IntoIterator::into_iter([eth_gas_price, eth_fee_history_price]) - .flatten() - .max() - .or_mm_err(|| Web3RpcError::Internal("All requests failed".into())) - }; - Box::new(fut.boxed().compat()) + } + .boxed(); + + let (eth_gas_price, eth_fee_history_price) = join(eth_gas_price_fut, eth_fee_history_price_fut).await; + // on editions < 2021 the compiler will resolve array.into_iter() as (&array).into_iter() + // https://doc.rust-lang.org/edition-guide/rust-2021/IntoIterator-for-arrays.html#details + IntoIterator::into_iter([eth_gas_price, eth_fee_history_price]) + .flatten() + .max() + .or_mm_err(|| Web3RpcError::Internal("All requests failed".into())) } /// Get gas base fee and suggest priority tip fees for the next block (see EIP-1559) - pub fn get_eip1559_gas_fee(&self) -> Web3RpcFut { + pub async fn get_eip1559_gas_fee(&self) -> Web3RpcResult { let coin = self.clone(); - let fut = async move { - let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(&coin); - let ctx = MmArc::from_weak(&coin.ctx) - .ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; - let gas_api_conf = ctx.conf["gas_api"].clone(); - if gas_api_conf.is_null() { - debug!("No eth gas api provider config, using only history estimator"); - return history_estimator_fut - .await - .map_err(|e| MmError::new(Web3RpcError::Internal(e.to_string()))); - } - let gas_api_conf: GasApiConfig = json::from_value(gas_api_conf) - .map_err(|e| MmError::new(Web3RpcError::InvalidGasApiConfig(e.to_string())))?; - let provider_estimator_fut = match gas_api_conf.provider { - GasApiProvider::Infura => InfuraGasApiCaller::fetch_infura_fee_estimation(&gas_api_conf.url).boxed(), - GasApiProvider::Blocknative => { - BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(&gas_api_conf.url).boxed() - }, - }; - provider_estimator_fut - .or_else(|provider_estimator_err| { - debug!( - "Call to eth gas api provider failed {}, using internal fee estimator", - provider_estimator_err - ); - history_estimator_fut.map_err(move |history_estimator_err| { - MmError::new(Web3RpcError::Internal(format!( - "All gas api requests failed, provider estimator error: {}, history estimator error: {}", - provider_estimator_err, history_estimator_err - ))) - }) - }) + let history_estimator_fut = FeePerGasSimpleEstimator::estimate_fee_by_history(&coin); + let ctx = + MmArc::from_weak(&coin.ctx).ok_or_else(|| MmError::new(Web3RpcError::Internal("ctx is null".into())))?; + let gas_api_conf = ctx.conf["gas_api"].clone(); + if gas_api_conf.is_null() { + debug!("No eth gas api provider config, using only history estimator"); + return history_estimator_fut .await + .map_err(|e| MmError::new(Web3RpcError::Internal(e.to_string()))); + } + let gas_api_conf: GasApiConfig = json::from_value(gas_api_conf) + .map_err(|e| MmError::new(Web3RpcError::InvalidGasApiConfig(e.to_string())))?; + let provider_estimator_fut = match gas_api_conf.provider { + GasApiProvider::Infura => InfuraGasApiCaller::fetch_infura_fee_estimation(&gas_api_conf.url).boxed(), + GasApiProvider::Blocknative => { + BlocknativeGasApiCaller::fetch_blocknative_fee_estimation(&gas_api_conf.url).boxed() + }, }; - Box::new(fut.boxed().compat()) + provider_estimator_fut + .or_else(|provider_estimator_err| { + debug!( + "Call to eth gas api provider failed {}, using internal fee estimator", + provider_estimator_err + ); + history_estimator_fut.map_err(move |history_estimator_err| { + MmError::new(Web3RpcError::Internal(format!( + "All gas api requests failed, provider estimator error: {}, history estimator error: {}", + provider_estimator_err, history_estimator_err + ))) + }) + }) + .await } - fn get_swap_pay_for_gas_option(&self, swap_fee_policy: SwapTxFeePolicy) -> Web3RpcFut { + async fn get_swap_pay_for_gas_option(&self, swap_fee_policy: SwapTxFeePolicy) -> Web3RpcResult { let coin = self.clone(); - let fut = async move { - match swap_fee_policy { - SwapTxFeePolicy::Internal => { - let gas_price = coin.get_gas_price().compat().await?; - Ok(PayForGasOption::Legacy(LegacyGasPrice { gas_price })) - }, - SwapTxFeePolicy::Low | SwapTxFeePolicy::Medium | SwapTxFeePolicy::High => { - let fee_per_gas = coin.get_eip1559_gas_fee().compat().await?; - let pay_result = (|| -> MmResult { - match swap_fee_policy { - SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.low.max_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.low.max_priority_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - })), - SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.medium.max_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.medium.max_priority_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - })), - _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.high.max_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.high.max_priority_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - })), - } - })(); - pay_result.mm_err(|e| Web3RpcError::Internal(format!("gas api result conversion error: {}", e))) - }, - SwapTxFeePolicy::Unsupported => { - Err(MmError::new(Web3RpcError::Internal("swap fee policy not set".into()))) - }, - } - }; - Box::new(fut.boxed().compat()) + match swap_fee_policy { + SwapTxFeePolicy::Internal => { + let gas_price = coin.get_gas_price().await?; + Ok(PayForGasOption::Legacy(LegacyGasPrice { gas_price })) + }, + SwapTxFeePolicy::Low | SwapTxFeePolicy::Medium | SwapTxFeePolicy::High => { + let fee_per_gas = coin.get_eip1559_gas_fee().await?; + let pay_result = (|| -> MmResult { + match swap_fee_policy { + SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.low.max_fee_per_gas, ETH_GWEI_DECIMALS)?, + max_priority_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.low.max_priority_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + })), + SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.medium.max_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + max_priority_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.medium.max_priority_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + })), + _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.high.max_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + max_priority_fee_per_gas: wei_from_big_decimal( + &fee_per_gas.high.max_priority_fee_per_gas, + ETH_GWEI_DECIMALS, + )?, + })), + } + })(); + pay_result.mm_err(|e| Web3RpcError::Internal(format!("gas api result conversion error: {}", e))) + }, + SwapTxFeePolicy::Unsupported => Err(MmError::new(Web3RpcError::Internal("swap fee policy not set".into()))), + } } /// Checks every second till at least one ETH node recognizes that nonce is increased. @@ -5380,22 +5360,27 @@ impl MmCoin for EthCoin { fn get_trade_fee(&self) -> Box + Send> { let coin = self.clone(); Box::new( - self.get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()) - .map_err(|e| e.to_string()) - .and_then(move |pay_for_gas_option| { - let fee = calc_total_fee(U256::from(ETH_MAX_TRADE_GAS), &pay_for_gas_option) - .map_err(|e| e.to_string())?; - let fee_coin = match &coin.coin_type { - EthCoinType::Eth => &coin.ticker, - EthCoinType::Erc20 { platform, .. } => platform, - EthCoinType::Nft { .. } => return ERR!("Nft Protocol is not supported yet!"), - }; - Ok(TradeFee { - coin: fee_coin.into(), - amount: try_s!(u256_to_big_decimal(fee, ETH_DECIMALS)).into(), - paid_from_trading_vol: false, - }) - }), + async move { + let pay_for_gas_option = coin + .get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) + .await + .map_err(|e| e.to_string())?; + + let fee = + calc_total_fee(U256::from(ETH_MAX_TRADE_GAS), &pay_for_gas_option).map_err(|e| e.to_string())?; + let fee_coin = match &coin.coin_type { + EthCoinType::Eth => &coin.ticker, + EthCoinType::Erc20 { platform, .. } => platform, + EthCoinType::Nft { .. } => return ERR!("Nft Protocol is not supported yet!"), + }; + Ok(TradeFee { + coin: fee_coin.into(), + amount: try_s!(u256_to_big_decimal(fee, ETH_DECIMALS)).into(), + paid_from_trading_vol: false, + }) + } + .boxed() + .compat(), ) } @@ -5407,7 +5392,6 @@ impl MmCoin for EthCoin { ) -> TradePreimageResult { let pay_for_gas_option = self .get_swap_pay_for_gas_option(self.get_swap_transaction_fee_policy()) - .compat() .await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let gas_limit = match self.coin_type { @@ -5436,7 +5420,6 @@ impl MmCoin for EthCoin { let approve_data = approve_function.encode_input(&[Token::Address(spender), Token::Uint(value)])?; let approve_gas_limit = self .estimate_gas_for_contract_call(token_addr, Bytes::from(approve_data)) - .compat() .await?; // this gas_limit includes gas for `approve`, `erc20Payment` contract calls @@ -5469,7 +5452,6 @@ impl MmCoin for EthCoin { let fut = async move { let pay_for_gas_option = coin .get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) - .compat() .await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let (fee_coin, total_fee) = match &coin.coin_type { @@ -5514,10 +5496,7 @@ impl MmCoin for EthCoin { }; let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(self.get_swap_transaction_fee_policy()); - let pay_for_gas_option = self - .get_swap_pay_for_gas_option(fee_policy_for_estimate) - .compat() - .await?; + let pay_for_gas_option = self.get_swap_pay_for_gas_option(fee_policy_for_estimate).await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); let estimate_gas_req = CallRequest { value: Some(eth_value), @@ -5839,37 +5818,42 @@ impl Transaction for SignedEthTx { } fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { + // Local function to map the access list + fn map_access_list(web3_access_list: &Option>) -> ethcore_transaction::AccessList { + match web3_access_list { + Some(list) => ethcore_transaction::AccessList( + list.iter() + .map(|item| ethcore_transaction::AccessListItem { + address: item.address, + storage_keys: item.storage_keys.clone(), + }) + .collect(), + ), + None => ethcore_transaction::AccessList(vec![]), + } + } + + // Define transaction types let type_0: ethereum_types::U64 = 0.into(); let type_1: ethereum_types::U64 = 1.into(); let type_2: ethereum_types::U64 = 2.into(); - let tx_type = if transaction.transaction_type.is_none() || transaction.transaction_type.unwrap() == type_0 { - TxType::Legacy - } else if transaction.transaction_type.unwrap() == type_1 { - TxType::Type1 - } else if transaction.transaction_type.unwrap() == type_2 { - TxType::Type2 - } else { - return Err(ERRL!("'Transaction::transaction_type' unsupported")); + // Determine the transaction type + let tx_type = match transaction.transaction_type { + None => TxType::Legacy, + Some(t) if t == type_0 => TxType::Legacy, + Some(t) if t == type_1 => TxType::Type1, + Some(t) if t == type_2 => TxType::Type2, + _ => return Err(ERRL!("'Transaction::transaction_type' unsupported")), }; + + // Determine the action based on the presence of 'to' field let action = match transaction.to { Some(addr) => Action::Call(addr), None => Action::Create, }; - let map_access_list = |web3_access_list: &Option>| match web3_access_list { - Some(list) => { - let v = list - .iter() - .map(|item| ethcore_transaction::AccessListItem { - address: item.address, - storage_keys: item.storage_keys.clone(), - }) - .collect::>(); - ethcore_transaction::AccessList(v) - }, - None => ethcore_transaction::AccessList(vec![]), - }; + // Initialize the transaction builder let tx_builder = UnSignedEthTxBuilder::new( tx_type.clone(), transaction.nonce, @@ -5878,42 +5862,55 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { let gas_price = transaction .gas_price .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; - tx_builder.with_gas_price(gas_price) }, - TxType::Type1 | TxType::Type2 => { - let chain_id_s = transaction + TxType::Type1 => { + let gas_price = transaction + .gas_price + .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; + let chain_id = transaction .chain_id .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? - .to_string(); - let chain_id = chain_id_s.parse().map_err(|e: std::num::ParseIntError| e.to_string())?; - let tx_builder = if tx_type == TxType::Type1 { - let gas_price = transaction - .gas_price - .ok_or_else(|| ERRL!("'Transaction::gas_price' is not set"))?; - tx_builder.with_gas_price(gas_price) - } else { - let max_fee_per_gas = transaction - .max_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?; - let max_priority_fee_per_gas = transaction - .max_priority_fee_per_gas - .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?; - tx_builder.with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) - }; + .to_string() + .parse() + .map_err(|e: std::num::ParseIntError| e.to_string())?; tx_builder + .with_gas_price(gas_price) + .with_chain_id(chain_id) + .with_access_list(map_access_list(&transaction.access_list)) + }, + TxType::Type2 => { + let max_fee_per_gas = transaction + .max_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_fee_per_gas' is not set"))?; + let max_priority_fee_per_gas = transaction + .max_priority_fee_per_gas + .ok_or_else(|| ERRL!("'Transaction::max_priority_fee_per_gas' is not set"))?; + let chain_id = transaction + .chain_id + .ok_or_else(|| ERRL!("'Transaction::chain_id' is not set"))? + .to_string() + .parse() + .map_err(|e: std::num::ParseIntError| e.to_string())?; + tx_builder + .with_priority_fee_per_gas(max_fee_per_gas, max_priority_fee_per_gas) .with_chain_id(chain_id) .with_access_list(map_access_list(&transaction.access_list)) }, TxType::Invalid => return Err(ERRL!("Internal error: 'tx_type' invalid")), }; + + // Build the unsigned transaction let unsigned = tx_builder.build().map_err(|err| err.to_string())?; + // Extract signature components let r = transaction.r.ok_or_else(|| ERRL!("'Transaction::r' is not set"))?; let s = transaction.s.ok_or_else(|| ERRL!("'Transaction::s' is not set"))?; let v = transaction @@ -5921,6 +5918,7 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result UnverifiedTransactionWrapper::Legacy( UnverifiedLegacyTransaction::new_with_network_v(unsigned, r, s, v, transaction.hash) @@ -5936,6 +5934,7 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { let max_fee_per_gas = wei_from_big_decimal(&max_fee_per_gas, ETH_GWEI_DECIMALS)?; let max_priority_fee_per_gas = wei_from_big_decimal(&max_priority_fee_per_gas, ETH_GWEI_DECIMALS)?; - if let EthGasLimitOption::Set(gas) = gas_limit { - return Ok(( - gas.into(), + match gas_limit { + EthGasLimitOption::Set(gas) => { + return Ok(( + gas.into(), + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas, + max_priority_fee_per_gas, + }), + )) + }, + EthGasLimitOption::Calc => + // go to gas estimate code + { PayForGasOption::Eip1559(Eip1559FeePerGas { max_fee_per_gas, max_priority_fee_per_gas, - }), - )); - } else { - // go to gas estimate code - PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas, - max_priority_fee_per_gas, - }) + }) + }, } }, Some(fee_policy) => { @@ -6424,7 +6427,7 @@ async fn get_eth_gas_details_from_withdraw_fee( }, None => { // If WithdrawFee not set use legacy gas price (?) - let gas_price = eth_coin.get_gas_price().compat().await?; + let gas_price = eth_coin.get_gas_price().await?; // go to gas estimate code PayForGasOption::Legacy(LegacyGasPrice { gas_price }) }, diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 48f394538e..98f93f02eb 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -600,7 +600,7 @@ fn get_sender_trade_preimage() { } } - EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(GAS_PRICE.into())))); + EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::pin(futures::future::ok(GAS_PRICE.into())))); let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &["http://dummy.dummy"], None); @@ -652,7 +652,7 @@ fn get_erc20_sender_trade_preimage() { EthCoin::allowance .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(unsafe { ALLOWANCE.into() })))); - EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(GAS_PRICE.into())))); + EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::pin(futures::future::ok(GAS_PRICE.into())))); EthCoin::estimate_gas_wrapper.mock_safe(|_, _| { unsafe { ESTIMATE_GAS_CALLED = true }; MockResult::Return(Box::new(futures01::future::ok(APPROVE_GAS_LIMIT.into()))) @@ -746,7 +746,7 @@ fn get_erc20_sender_trade_preimage() { #[test] fn get_receiver_trade_preimage() { - EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(GAS_PRICE.into())))); + EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::pin(futures::future::ok(GAS_PRICE.into())))); let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &["http://dummy.dummy"], None); let amount = @@ -769,7 +769,7 @@ fn test_get_fee_to_send_taker_fee() { const DEX_FEE_AMOUNT: u64 = 100_000; const TRANSFER_GAS_LIMIT: u64 = 40_000; - EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(GAS_PRICE.into())))); + EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::pin(futures::future::ok(GAS_PRICE.into())))); EthCoin::estimate_gas_wrapper .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(TRANSFER_GAS_LIMIT.into())))); @@ -818,7 +818,7 @@ fn test_get_fee_to_send_taker_fee() { fn test_get_fee_to_send_taker_fee_insufficient_balance() { const DEX_FEE_AMOUNT: u64 = 100_000_000_000; - EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(40.into())))); + EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::pin(futures::future::ok(40.into())))); let (_ctx, coin) = eth_coin_for_test( EthCoinType::Erc20 { platform: "ETH".to_string(), diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 203756cc3c..4f342874b2 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -5,7 +5,6 @@ use crate::{lp_coinfind_or_err, AsyncMutex, CoinFindError, MmCoinEnum, NumConver use common::executor::{spawn_abortable, Timer}; use common::log::debug; use common::{HttpStatusCode, StatusCode}; -use futures::compat::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use serde_json::Value as Json; @@ -152,7 +151,7 @@ impl FeeEstimatorContext { loop { let started = common::now_float(); if let Ok(estimator_ctx) = Self::get_estimator_ctx(&coin) { - let estimated_fees = coin.get_eip1559_gas_fee().compat().await.unwrap_or_default(); + let estimated_fees = coin.get_eip1559_gas_fee().await.unwrap_or_default(); let estimator_ctx = estimator_ctx.lock().await; *estimator_ctx.estimated_fees.lock().await = estimated_fees; } From 789ec7683331ef23b44441626440c220442d41d4 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 9 Apr 2024 19:03:58 +0500 Subject: [PATCH 54/71] refactor on review notes: --- mm2src/coins/eth/eip1559_gas_fee.rs | 49 +++++++++++++---------------- mm2src/coins/qrc20.rs | 18 +++++------ 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index b50587dedd..f41906784e 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -66,7 +66,7 @@ pub struct GasApiConfig { } /// Priority level estimated max fee per gas -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Default, Serialize)] pub struct FeePerGasLevel { /// estimated max priority tip fee per gas in gwei pub max_priority_fee_per_gas: BigDecimal, @@ -100,17 +100,6 @@ pub struct FeePerGasEstimated { pub priority_fee_trend: String, } -impl Default for FeePerGasLevel { - fn default() -> Self { - Self { - max_priority_fee_per_gas: BigDecimal::from(0), - max_fee_per_gas: BigDecimal::from(0), - min_wait_time: None, - max_wait_time: None, - } - } -} - impl From for FeePerGasEstimated { fn from(infura_fees: InfuraFeePerGas) -> Self { Self { @@ -249,33 +238,37 @@ impl FeePerGasSimpleEstimator { base_fee: BigDecimal, fee_history: &FeeHistoryResult, ) -> Web3RpcResult { - let level_i = level as usize; + let level_index = level as usize; let level_rewards = fee_history .priority_rewards .as_ref() .or_mm_err(|| Web3RpcError::Internal("expected reward in eth_feeHistory".into()))? .iter() - .map(|rewards| { - if level_i < rewards.len() { - rewards[level_i] - } else { - U256::from(0) - } - }) + .map(|rewards| rewards.get(level_index).copied().unwrap_or_else(|| U256::from(0))) .collect::>(); - let max_priority_fee_per_gas = Self::percentile_of(&level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_i]); - let max_priority_fee_per_gas = + // Calculate the max priority fee per gas based on the rewards percentile. + let max_priority_fee_per_gas = Self::percentile_of(&level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_index]); + // Convert the priority fee to a BigDecimal, falling back to 0 on error. + let max_priority_fee_per_gas_decimal = u256_to_big_decimal(max_priority_fee_per_gas, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); - let max_fee_per_gas = base_fee - * BigDecimal::from_f64(Self::ADJUST_MAX_FEE[level_i]).unwrap_or_else(|| BigDecimal::from(0)) - + max_priority_fee_per_gas.clone() - * BigDecimal::from_f64(Self::ADJUST_MAX_PRIORITY_FEE[level_i]).unwrap_or_else(|| BigDecimal::from(0)); // TODO maybe use checked ops + + // Calculate the max fee per gas by adjusting the base fee and adding the priority fee. + let adjust_max_fee = + BigDecimal::from_f64(Self::ADJUST_MAX_FEE[level_index]).unwrap_or_else(|| BigDecimal::from(0)); + let adjust_max_priority_fee = + BigDecimal::from_f64(Self::ADJUST_MAX_PRIORITY_FEE[level_index]).unwrap_or_else(|| BigDecimal::from(0)); + + // TODO: consider use checked ops + let max_fee_per_gas = + base_fee * adjust_max_fee + max_priority_fee_per_gas_decimal.clone() * adjust_max_priority_fee; + Ok(FeePerGasLevel { - max_priority_fee_per_gas, + max_priority_fee_per_gas: max_priority_fee_per_gas_decimal, max_fee_per_gas, + // TODO: Consider adding default wait times if applicable (and mark them as uncertain). min_wait_time: None, - max_wait_time: None, // TODO: maybe fill with some default values (and mark them as uncertain)? + max_wait_time: None, }) } diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 7ceb61a944..a6a338dc01 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1399,18 +1399,18 @@ impl MmCoin for Qrc20Coin { .await? }; - let total_fee = if include_refund_fee { - let sender_refund_fee = { - let sender_refund_output = - self.sender_refund_output(&self.swap_contract_address, swap_id, value, secret_hash, receiver_addr)?; - self.preimage_trade_fee_required_to_send_outputs(vec![sender_refund_output], &stage) - .await? - }; - erc20_payment_fee + sender_refund_fee + // Optionally calculate refund fee. + let sender_refund_fee = if include_refund_fee { + let sender_refund_output = + self.sender_refund_output(&self.swap_contract_address, swap_id, value, secret_hash, receiver_addr)?; + self.preimage_trade_fee_required_to_send_outputs(vec![sender_refund_output], &stage) + .await? } else { - erc20_payment_fee + BigDecimal::from(0) // No refund fee if not included. }; + let total_fee = erc20_payment_fee + sender_refund_fee; + Ok(TradeFee { coin: self.platform.clone(), amount: total_fee.into(), From 1793bb7e52d6af8080c3b83ac0b307062509dbdb Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 9 Apr 2024 20:22:47 +0500 Subject: [PATCH 55/71] fix fmt --- mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index a51db2cd81..6f826aa066 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -5,7 +5,10 @@ use super::docker_tests_common::{random_secp256k1_secret, ERC1155_TEST_ABI, ERC7 use bitcrypto::{dhash160, sha256}; use coins::eth::{checksum_address, eth_addr_to_hex, eth_coin_from_conf_and_request, EthCoin, ERC20_ABI}; use coins::nft::nft_structs::{Chain, ContractType, NftInfo}; -use coins::{CoinProtocol, ConfirmPaymentInput, FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, MmCoin, NftSwapInfo, ParseCoinAssocTypes, PrivKeyBuildPolicy, RefundPaymentArgs, SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, Transaction, ValidateNftMakerPaymentArgs}; +use coins::{CoinProtocol, ConfirmPaymentInput, FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, MmCoin, + NftSwapInfo, ParseCoinAssocTypes, PrivKeyBuildPolicy, RefundPaymentArgs, SearchForSwapTxSpendInput, + SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, SpendPaymentArgs, SwapOps, + SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, Transaction, ValidateNftMakerPaymentArgs}; use common::{block_on, now_sec}; use ethereum_types::U256; use futures01::Future; From 086a711256bfa39d6d504b23daddba6203cbbbe7 Mon Sep 17 00:00:00 2001 From: dimxy Date: Tue, 9 Apr 2024 20:51:54 +0500 Subject: [PATCH 56/71] fix clippy err --- mm2src/coins/eth/nft_swap_v2/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index c110a38fc5..61cda005f1 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -138,7 +138,7 @@ impl EthCoin { let contract_type = args.contract_type; let (decoded, index_bytes) = try_tx_s!(get_decoded_tx_data_and_index_bytes( contract_type, - &args.maker_payment_tx.unsigned().data() + args.maker_payment_tx.unsigned().data() )); let (state, htlc_params) = try_tx_s!( From 7229e3441e53252155703d0a5faef0620050e6bd Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 13 Apr 2024 19:21:04 +0500 Subject: [PATCH 57/71] add 'gas_fee_estimator' param to 'coins' conf --- mm2src/coins/eth.rs | 20 +++- mm2src/coins/eth/eth_tests.rs | 12 +-- mm2src/coins/eth/eth_wasm_tests.rs | 2 +- mm2src/coins/eth/v2_activation.rs | 12 +-- .../coins/rpc_command/get_estimated_fees.rs | 100 ++++++++++++------ 5 files changed, 95 insertions(+), 51 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 90d2f3e97d..e551e004d0 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -495,9 +495,22 @@ impl TryFrom for EthPrivKeyBuildPolicy { pub(crate) struct FeeEstimatorContext { /// Latest estimated gas fee values pub(crate) estimated_fees: Arc>, + /// Handler for estimator loop graceful shutdown pub(crate) abort_handler: AsyncMutex>, } +/// Gas fee estimator creation state +pub(crate) enum FeeEstimatorState { + /// Gas fee estimation not supported for this coin + CoinNotSupported, + /// Platform coin required to be enabled for gas fee estimation for this coin + PlatformCoinRequired, + /// Fee estimator created, use simple internal estimator + Simple(AsyncMutex), + /// Fee estimator created, use provider or simple internal estimator (if provider fails) + Provider(AsyncMutex), +} + /// pImpl idiom. pub struct EthCoinImpl { ticker: String, @@ -513,6 +526,7 @@ pub struct EthCoinImpl { history_sync_state: Mutex, required_confirmations: AtomicU64, swap_txfee_policy: Mutex, + max_eth_tx_type: Option, /// Coin needs access to the context in order to reuse the logging and shutdown facilities. /// Using a weak reference by default in order to avoid circular references and leaks. pub ctx: MmWeak, @@ -526,7 +540,7 @@ pub struct EthCoinImpl { /// information (chain & contract type, amount etc.), where ownership and amount, in ERC1155 case, might change over time. pub nfts_infos: Arc>>, /// Context for eth fee per gas estimator loop. Created if coin supports fee per gas estimation - pub(crate) platform_fee_estimator_ctx: Option>>, + pub(crate) platform_fee_estimator_state: Arc, /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation /// and on [`MmArc::stop`]. pub abortable_system: AbortableQueue, @@ -6198,7 +6212,7 @@ pub async fn eth_coin_from_conf_and_request( // all spawned futures related to `ETH` coin will be aborted as well. let abortable_system = try_s!(ctx.abortable_system.create_subsystem()); - let platform_fee_estimator_ctx = FeeEstimatorContext::new(ctx, conf, &coin_type).await; + let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; let coin = EthCoinImpl { priv_key_policy: key_pair, @@ -6220,7 +6234,7 @@ pub async fn eth_coin_from_conf_and_request( nonce_lock, erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx, + platform_fee_estimator_state, abortable_system, }; diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 98f93f02eb..3270a34aef 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -156,7 +156,7 @@ fn eth_coin_from_keypair( nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx: None, + platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); (ctx, eth_coin) @@ -366,7 +366,7 @@ fn test_nonce_several_urls() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx: None, + platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); @@ -417,7 +417,7 @@ fn test_wait_for_payment_spend_timeout() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx: None, + platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), }; @@ -1130,7 +1130,7 @@ fn test_message_hash() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx: None, + platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); @@ -1177,7 +1177,7 @@ fn test_sign_verify_message() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx: None, + platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); @@ -1233,7 +1233,7 @@ fn test_eth_extract_secret() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx: None, + platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index 839960c855..f47634799a 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -48,7 +48,7 @@ async fn test_send() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx: None, + platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); let maker_payment_args = SendPaymentArgs { diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 130fdd03fe..e0d4e50543 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -289,7 +289,7 @@ impl EthCoin { platform: protocol.platform, token_addr: protocol.token_addr, }; - let platform_fee_estimator_ctx = FeeEstimatorContext::new(&ctx, &conf, &coin_type).await; + let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; let token = EthCoinImpl { priv_key_policy: self.priv_key_policy.clone(), @@ -311,7 +311,7 @@ impl EthCoin { nonce_lock: self.nonce_lock.clone(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx, + platform_fee_estimator_state, abortable_system, }; @@ -344,7 +344,7 @@ impl EthCoin { let coin_type = EthCoinType::Nft { platform: self.ticker.clone(), }; - let platform_fee_estimator_ctx = FeeEstimatorContext::new(&ctx, &conf, &coin_type).await; + let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; let global_nft = EthCoinImpl { ticker, @@ -366,7 +366,7 @@ impl EthCoin { nonce_lock: self.nonce_lock.clone(), erc20_tokens_infos: Default::default(), nfts_infos: Arc::new(AsyncMutex::new(nft_infos)), - platform_fee_estimator_ctx, + platform_fee_estimator_state, abortable_system, }; Ok(EthCoin(Arc::new(global_nft))) @@ -453,7 +453,7 @@ pub async fn eth_coin_from_conf_and_request_v2( // all spawned futures related to `ETH` coin will be aborted as well. let abortable_system = ctx.abortable_system.create_subsystem()?; let coin_type = EthCoinType::Eth; - let platform_fee_estimator_ctx = FeeEstimatorContext::new(ctx, conf, &coin_type).await; + let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; let coin = EthCoinImpl { priv_key_policy, @@ -475,7 +475,7 @@ pub async fn eth_coin_from_conf_and_request_v2( nonce_lock, erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), - platform_fee_estimator_ctx, + platform_fee_estimator_state, abortable_system, }; diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 4f342874b2..8ed0090921 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,7 +1,7 @@ //! RPCs to start/stop gas fee estimator and get estimated base and priority fee per gas -use crate::eth::{EthCoin, EthCoinType, FeeEstimatorContext, FeePerGasEstimated}; use crate::{lp_coinfind_or_err, AsyncMutex, CoinFindError, MmCoinEnum, NumConversError}; +use crate::eth::{EthCoin, EthCoinType, FeeEstimatorContext, FeeEstimatorState, FeePerGasEstimated}; use common::executor::{spawn_abortable, Timer}; use common::log::debug; use common::{HttpStatusCode, StatusCode}; @@ -12,19 +12,16 @@ use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; -/// Chain id for which fee per gas estimations are supported. -/// Only eth mainnet currently is supported (Blocknative gas platform currently supports Ethereum and Polygon/Matic mainnets.) -/// TODO: make a setting in the coins file (platform coin) to indicate which chains support: -/// typed transactions, fee estimations by fee history and/or a gas provider. -const ETH_SUPPORTED_CHAIN_ID: u64 = 1; #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum FeeEstimatorError { #[display(fmt = "No such coin {}", coin)] NoSuchCoin { coin: String }, - #[display(fmt = "Gas fee estimation not supported for this coin or chain id")] + #[display(fmt = "Gas fee estimation not supported for this coin")] CoinNotSupported, + #[display(fmt = "Platform coin needs to be enabled for gas fee estimation")] + PlatformCoinRequired, #[display(fmt = "Gas fee estimator is already started")] AlreadyStarted, #[display(fmt = "Transport error: {}", _0)] @@ -40,6 +37,7 @@ impl HttpStatusCode for FeeEstimatorError { match self { FeeEstimatorError::NoSuchCoin { .. } | FeeEstimatorError::CoinNotSupported + | FeeEstimatorError::PlatformCoinRequired | FeeEstimatorError::AlreadyStarted | FeeEstimatorError::NotRunning => StatusCode::BAD_REQUEST, FeeEstimatorError::Transport(_) | FeeEstimatorError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, @@ -63,39 +61,73 @@ impl From for FeeEstimatorError { } } -impl FeeEstimatorContext { - /// Creates gas fee estimator context if supported for this coin and chain id. Otherwise returns None. - /// When gas fee estimator rpc is called and no fee estimator was created - /// it is assumed it is not supported for the coin or chain or coin config is inappropriate - pub(crate) async fn new(ctx: &MmArc, conf: &Json, coin_type: &EthCoinType) -> Option>> { - let chain_id = conf["chain_id"].as_u64()?; - if chain_id != ETH_SUPPORTED_CHAIN_ID { - return None; +/// Gas fee estimator configuration +#[derive(Deserialize)] +enum FeeEstimatorConf { + NotConfigured, + #[serde(rename = "simple")] + Simple, + #[serde(rename = "provider")] + Provider, +} + +impl Default for FeeEstimatorConf { + fn default() -> Self { Self::NotConfigured } +} + +impl FeeEstimatorState { + /// Creates gas FeeEstimatorContext if configured for this coin and chain id, otherwise returns None. + /// The created context object (or None) is wrapped into a FeeEstimatorState so a gas fee rpc caller may know the reason why it was not created + pub(crate) async fn init_fee_estimator( + ctx: &MmArc, + conf: &Json, + coin_type: &EthCoinType, + ) -> Result, String> { + let fee_estimator_json = conf["gas_fee_estimator"].clone(); + let fee_estimator_conf: FeeEstimatorConf = if !fee_estimator_json.is_null() { + try_s!(json::from_value(fee_estimator_json)) + } else { + Default::default() }; - match coin_type { - EthCoinType::Eth => Some(Arc::new(AsyncMutex::new(Self { - estimated_fees: Default::default(), - abort_handler: AsyncMutex::new(None), - }))), - EthCoinType::Erc20 { platform, .. } | EthCoinType::Nft { platform, .. } => { - let platform_coin = lp_coinfind_or_err(ctx, platform).await.ok()?; - match platform_coin { - MmCoinEnum::EthCoin(eth_coin) => eth_coin.platform_fee_estimator_ctx.as_ref().cloned(), - _ => None, - } + match fee_estimator_conf { + FeeEstimatorConf::Simple | FeeEstimatorConf::Provider => match coin_type { + EthCoinType::Eth => { + let fee_estimator_ctx = AsyncMutex::new(FeeEstimatorContext { + estimated_fees: Default::default(), + abort_handler: AsyncMutex::new(None), + }); + let fee_estimator_state = if matches!(fee_estimator_conf, FeeEstimatorConf::Simple) { + FeeEstimatorState::Simple(fee_estimator_ctx) + } else { + FeeEstimatorState::Provider(fee_estimator_ctx) + }; + Ok(Arc::new(fee_estimator_state)) + }, + EthCoinType::Erc20 { platform, .. } | EthCoinType::Nft { platform, .. } => { + let platform_coin = lp_coinfind_or_err(ctx, platform).await; + match platform_coin { + Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.platform_fee_estimator_state.clone()), + _ => Ok(Arc::new(FeeEstimatorState::PlatformCoinRequired)), + } + }, }, + FeeEstimatorConf::NotConfigured => Ok(Arc::new(FeeEstimatorState::CoinNotSupported)), } } +} +impl FeeEstimatorContext { /// Fee estimation update period in secs, basically equals to eth blocktime const fn get_refresh_interval() -> f64 { 15.0 } - fn get_estimator_ctx(coin: &EthCoin) -> Result>, MmError> { - Ok(coin - .platform_fee_estimator_ctx - .as_ref() - .ok_or_else(|| MmError::new(FeeEstimatorError::CoinNotSupported))? - .clone()) + fn get_estimator_ctx(coin: &EthCoin) -> Result<&AsyncMutex, MmError> { + match coin.platform_fee_estimator_state.deref() { + FeeEstimatorState::CoinNotSupported => MmError::err(FeeEstimatorError::CoinNotSupported), + FeeEstimatorState::PlatformCoinRequired => MmError::err(FeeEstimatorError::PlatformCoinRequired), + FeeEstimatorState::Simple(fee_estimator_ctx) | FeeEstimatorState::Provider(fee_estimator_ctx) => { + Ok(fee_estimator_ctx) + }, + } } async fn start_if_not_running(coin: &EthCoin) -> Result<(), MmError> { @@ -132,9 +164,7 @@ impl FeeEstimatorContext { MmCoinEnum::EthCoin(eth) => eth, _ => return MmError::err(FeeEstimatorError::CoinNotSupported), }; - if eth_coin.platform_fee_estimator_ctx.is_none() { - return MmError::err(FeeEstimatorError::CoinNotSupported); - } + let _ = Self::get_estimator_ctx(ð_coin)?; Ok(eth_coin) } From 48db16c1ffa94af0fafa338b1bfa344f7563b912 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 13 Apr 2024 19:43:25 +0500 Subject: [PATCH 58/71] add 'max_eth_tx_type' to coins file and check tx type on signing --- mm2src/coins/eth.rs | 47 ++++++++++++++++++++++++++++++ mm2src/coins/eth/eth_tests.rs | 6 ++++ mm2src/coins/eth/eth_wasm_tests.rs | 1 + mm2src/coins/eth/v2_activation.rs | 14 +++++++++ mm2src/coins/lp_coins.rs | 3 ++ 5 files changed, 71 insertions(+) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e551e004d0..621203613e 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -215,6 +215,9 @@ const ETH_MAX_TRADE_GAS: u64 = 150_000; /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; +/// Max transaction type according to EIP-2718 +const ETH_MAX_TX_TYPE: u64 = 0x7f; + lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); @@ -807,6 +810,9 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { .map_to_mm(WithdrawError::Transport)?; let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + if !coin.is_tx_type_supported(&tx_type) { + return MmError::err(WithdrawError::TxTypeNotSupported); + } let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, Action::Call(call_addr), eth_value, data); let tx_builder = tx_builder_with_pay_for_gas_option(&coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder @@ -965,6 +971,9 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .map_to_mm(WithdrawError::Transport)?; let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + if !eth_coin.is_tx_type_supported(&tx_type) { + return MmError::err(WithdrawError::TxTypeNotSupported); + } let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, Action::Call(call_addr), eth_value, data); let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder @@ -1050,6 +1059,9 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .map_to_mm(WithdrawError::Transport)?; let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + if !eth_coin.is_tx_type_supported(&tx_type) { + return MmError::err(WithdrawError::TxTypeNotSupported); + } let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, Action::Call(call_addr), eth_value, data); let tx_builder = tx_builder_with_pay_for_gas_option(ð_coin, tx_builder, &pay_for_gas_option)?; let tx = tx_builder @@ -2491,6 +2503,9 @@ async fn sign_transaction_with_keypair( ); let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + if !coin.is_tx_type_supported(&tx_type) { + return Err(TransactionErr::Plain("Eth transaction type not supported".into())); + } let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, action, value, data); let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, &pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; @@ -3505,6 +3520,17 @@ impl EthCoin { } } } + + fn is_tx_type_supported(&self, tx_type: &TxType) -> bool { + let tx_type_as_num = match tx_type { + TxType::Legacy => 0_u64, + TxType::Type1 => 1_u64, + TxType::Type2 => 2_u64, + TxType::Invalid => return false, + }; + let max_tx_type = self.max_eth_tx_type.unwrap_or(0_u64); + tx_type_as_num <= max_tx_type + } } #[cfg_attr(test, mockable)] @@ -6051,6 +6077,25 @@ fn rpc_event_handlers_for_eth_transport(ctx: &MmArc, ticker: String) -> Vec Result, String> { + let max_eth_tx_type = match &coin_type { + EthCoinType::Eth => conf["max_eth_tx_type"].as_u64(), + EthCoinType::Erc20 { platform, .. } | EthCoinType::Nft { platform } => { + let platform_coin = lp_coinfind_or_err(ctx, platform).await; + match platform_coin { + Ok(MmCoinEnum::EthCoin(eth_coin)) => eth_coin.max_eth_tx_type, + _ => None, + } + }, + }; + if let Some(max_eth_tx_type) = max_eth_tx_type { + if max_eth_tx_type > ETH_MAX_TX_TYPE { + return Err("eth tx type too big in coins file".into()); + } + } + Ok(max_eth_tx_type) +} + #[inline] fn new_nonce_lock() -> Arc> { Arc::new(AsyncMutex::new(())) } @@ -6213,6 +6258,7 @@ pub async fn eth_coin_from_conf_and_request( let abortable_system = try_s!(ctx.abortable_system.create_subsystem()); let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(ctx, conf, &coin_type).await?; let coin = EthCoinImpl { priv_key_policy: key_pair, @@ -6227,6 +6273,7 @@ pub async fn eth_coin_from_conf_and_request( web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(initial_history_state), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), + max_eth_tx_type, ctx: ctx.weak(), required_confirmations, chain_id: conf["chain_id"].as_u64(), diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 3270a34aef..25b6e2210d 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -156,6 +156,7 @@ fn eth_coin_from_keypair( nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + max_eth_tx_type: None, platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); @@ -366,6 +367,7 @@ fn test_nonce_several_urls() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + max_eth_tx_type: None, platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); @@ -410,6 +412,7 @@ fn test_wait_for_payment_spend_timeout() { ticker: "ETH".into(), web3_instances: AsyncMutex::new(vec![Web3Instance { web3, is_parity: false }]), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), + max_eth_tx_type: None, ctx: ctx.weak(), required_confirmations: 1.into(), chain_id: None, @@ -1130,6 +1133,7 @@ fn test_message_hash() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + max_eth_tx_type: None, platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); @@ -1177,6 +1181,7 @@ fn test_sign_verify_message() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + max_eth_tx_type: None, platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); @@ -1233,6 +1238,7 @@ fn test_eth_extract_secret() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + max_eth_tx_type: None, platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index f47634799a..c7e28b8103 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -48,6 +48,7 @@ async fn test_send() { nonce_lock: new_nonce_lock(), erc20_tokens_infos: Default::default(), nfts_infos: Default::default(), + max_eth_tx_type: None, platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), abortable_system: AbortableQueue::default(), })); diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index e0d4e50543..999d2a0680 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -85,6 +85,10 @@ impl From for EthActivationV2Error { fn from(e: ParseChainTypeError) -> Self { EthActivationV2Error::InternalError(e.to_string()) } } +impl From for EthActivationV2Error { + fn from(e: String) -> Self { EthActivationV2Error::InternalError(e) } +} + /// An alternative to `crate::PrivKeyActivationPolicy`, typical only for ETH coin. #[derive(Clone, Deserialize)] pub enum EthPrivKeyActivationPolicy { @@ -183,6 +187,10 @@ impl From for EthTokenActivationError { fn from(e: ParseChainTypeError) -> Self { EthTokenActivationError::InternalError(e.to_string()) } } +impl From for EthTokenActivationError { + fn from(e: String) -> Self { EthTokenActivationError::InternalError(e) } +} + /// Represents the parameters required for activating either an ERC-20 token or an NFT on the Ethereum platform. #[derive(Clone, Deserialize)] #[serde(untagged)] @@ -290,6 +298,7 @@ impl EthCoin { token_addr: protocol.token_addr, }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; let token = EthCoinImpl { priv_key_policy: self.priv_key_policy.clone(), @@ -304,6 +313,7 @@ impl EthCoin { web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), + max_eth_tx_type, ctx: self.ctx.clone(), required_confirmations, chain_id: self.chain_id, @@ -345,6 +355,7 @@ impl EthCoin { platform: self.ticker.clone(), }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; let global_nft = EthCoinImpl { ticker, @@ -359,6 +370,7 @@ impl EthCoin { decimals: self.decimals, history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), + max_eth_tx_type, required_confirmations: AtomicU64::new(self.required_confirmations.load(Ordering::Relaxed)), ctx: self.ctx.clone(), chain_id: self.chain_id, @@ -454,6 +466,7 @@ pub async fn eth_coin_from_conf_and_request_v2( let abortable_system = ctx.abortable_system.create_subsystem()?; let coin_type = EthCoinType::Eth; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(ctx, conf, &coin_type).await?; let coin = EthCoinImpl { priv_key_policy, @@ -468,6 +481,7 @@ pub async fn eth_coin_from_conf_and_request_v2( web3_instances: AsyncMutex::new(web3_instances), history_sync_state: Mutex::new(HistorySyncState::NotEnabled), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), + max_eth_tx_type, ctx: ctx.weak(), required_confirmations, chain_id, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 817fda70c1..ea4b2769c9 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -2834,6 +2834,8 @@ pub enum WithdrawError { }, #[display(fmt = "Signing error {}", _0)] SigningError(String), + #[display(fmt = "Eth transaction type not supported")] + TxTypeNotSupported, } impl HttpStatusCode for WithdrawError { @@ -2861,6 +2863,7 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::NotEnoughNftsAmount { .. } | WithdrawError::MyAddressNotNftOwner { .. } | WithdrawError::NoChainIdSet { .. } + | WithdrawError::TxTypeNotSupported | WithdrawError::SigningError(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] From f0371e5b922e6c1532fd8ee0bc527ef3d8c53495 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 13 Apr 2024 19:53:19 +0500 Subject: [PATCH 59/71] refactor estimated gas fee code to use wei internally (to make it like get_gas_price fn) and convert to gwei decimal for gui --- mm2src/coins/coin_errors.rs | 1 + mm2src/coins/eth.rs | 85 +++++----- mm2src/coins/eth/eip1559_gas_fee.rs | 156 +++++++++--------- .../coins/rpc_command/get_estimated_fees.rs | 88 +++++++++- 4 files changed, 215 insertions(+), 115 deletions(-) diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index 73fea8aff9..34b0c46486 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -73,6 +73,7 @@ impl From for ValidatePaymentError { Web3RpcError::InvalidResponse(resp) => ValidatePaymentError::InvalidRpcResponse(resp), Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) + | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => ValidatePaymentError::InternalError(internal), Web3RpcError::NftProtocolNotSupported => ValidatePaymentError::NftProtocolNotSupported, } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 621203613e..f442a8ce32 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -230,6 +230,20 @@ pub type Web3RpcFut = Box> pub type Web3RpcResult = Result>; type EthPrivKeyPolicy = PrivKeyPolicy; +#[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, @@ -283,6 +297,8 @@ pub enum Web3RpcError { InvalidGasApiConfig(String), #[display(fmt = "Nft Protocol is not supported yet!")] NftProtocolNotSupported, + #[display(fmt = "Number conversion: {}", _0)] + NumConversError(String), } impl From for Web3RpcError { @@ -306,6 +322,7 @@ impl From for RawTransactionError { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => RawTransactionError::Transport(tr), Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) + | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => RawTransactionError::InternalError(internal), Web3RpcError::NftProtocolNotSupported => { RawTransactionError::InternalError("Nft Protocol is not supported yet!".to_string()) @@ -332,6 +349,10 @@ impl From for Web3RpcError { } } +impl From for Web3RpcError { + fn from(e: NumConversError) -> Self { Web3RpcError::NumConversError(e.to_string()) } +} + impl From for WithdrawError { fn from(e: ethabi::Error) -> Self { // Currently, we use the `ethabi` crate to work with a smart contract ABI known at compile time. @@ -350,6 +371,7 @@ impl From for WithdrawError { Web3RpcError::Transport(err) | Web3RpcError::InvalidResponse(err) => WithdrawError::Transport(err), Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) + | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => WithdrawError::InternalError(internal), Web3RpcError::NftProtocolNotSupported => WithdrawError::NftProtocolNotSupported, } @@ -370,6 +392,7 @@ impl From for TradePreimageError { Web3RpcError::Transport(err) | Web3RpcError::InvalidResponse(err) => TradePreimageError::Transport(err), Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) + | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => TradePreimageError::InternalError(internal), Web3RpcError::NftProtocolNotSupported => TradePreimageError::NftProtocolNotSupported, } @@ -402,6 +425,7 @@ impl From for BalanceError { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => BalanceError::Transport(tr), Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) + | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => BalanceError::Internal(internal), Web3RpcError::NftProtocolNotSupported => { BalanceError::Internal("Nft Protocol is not supported yet!".to_string()) @@ -5046,38 +5070,21 @@ impl EthCoin { }, SwapTxFeePolicy::Low | SwapTxFeePolicy::Medium | SwapTxFeePolicy::High => { let fee_per_gas = coin.get_eip1559_gas_fee().await?; - let pay_result = (|| -> MmResult { - match swap_fee_policy { - SwapTxFeePolicy::Low => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas: wei_from_big_decimal(&fee_per_gas.low.max_fee_per_gas, ETH_GWEI_DECIMALS)?, - max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.low.max_priority_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - })), - SwapTxFeePolicy::Medium => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.medium.max_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.medium.max_priority_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - })), - _ => Ok(PayForGasOption::Eip1559(Eip1559FeePerGas { - max_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.high.max_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - max_priority_fee_per_gas: wei_from_big_decimal( - &fee_per_gas.high.max_priority_fee_per_gas, - ETH_GWEI_DECIMALS, - )?, - })), - } - })(); - pay_result.mm_err(|e| Web3RpcError::Internal(format!("gas api result conversion error: {}", e))) + let pay_result = match swap_fee_policy { + SwapTxFeePolicy::Low => PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas: fee_per_gas.low.max_fee_per_gas, + max_priority_fee_per_gas: fee_per_gas.low.max_priority_fee_per_gas, + }), + SwapTxFeePolicy::Medium => PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas: fee_per_gas.medium.max_fee_per_gas, + max_priority_fee_per_gas: fee_per_gas.medium.max_priority_fee_per_gas, + }), + _ => PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas: fee_per_gas.high.max_fee_per_gas, + max_priority_fee_per_gas: fee_per_gas.high.max_priority_fee_per_gas, + }), + }; + Ok(pay_result) }, SwapTxFeePolicy::Unsupported => Err(MmError::new(Web3RpcError::Internal("swap fee policy not set".into()))), } @@ -5302,9 +5309,12 @@ impl EthCoin { pub struct EthTxFeeDetails { pub coin: String, pub gas: u64, - /// WEI units per 1 gas - pub gas_price: BigDecimal, // if fee per gas is used we set gas_price as max_fee_per_gas for compatibility with GUI - pub max_fee_per_gas: Option, // + /// Gas price in ETH per gas unit + /// if 'max_fee_per_gas' and 'max_priority_fee_per_gas' are used we set 'gas_price' as 'max_fee_per_gas' for compatibility with GUI + pub gas_price: BigDecimal, + /// Max fee per gas in ETH per gas unit + pub max_fee_per_gas: Option, + /// Max priority fee per gas in ETH per gas unit pub max_priority_fee_per_gas: Option, pub total_fee: BigDecimal, } @@ -5430,8 +5440,8 @@ impl MmCoin for EthCoin { .await .map_err(|e| e.to_string())?; - let fee = - calc_total_fee(U256::from(ETH_MAX_TRADE_GAS), &pay_for_gas_option).map_err(|e| e.to_string())?; + let fee = calc_total_fee(U256::from(gas_limit::ETH_MAX_TRADE_GAS), &pay_for_gas_option) + .map_err(|e| e.to_string())?; let fee_coin = match &coin.coin_type { EthCoinType::Eth => &coin.ticker, EthCoinType::Erc20 { platform, .. } => platform, @@ -6460,6 +6470,7 @@ impl From for EthGasDetailsErr { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => EthGasDetailsErr::Transport(tr), Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) + | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => EthGasDetailsErr::Internal(internal), Web3RpcError::NftProtocolNotSupported => EthGasDetailsErr::NftProtocolNotSupported, } diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index f41906784e..2f7a69ba8f 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -1,13 +1,14 @@ //! Provides estimations of base and priority fee per gas or fetch estimations from a gas api provider use super::web3_transport::FeeHistoryResult; -use super::{u256_to_big_decimal, Web3RpcError, Web3RpcResult, ETH_GWEI_DECIMALS}; -use crate::EthCoin; +use super::{Web3RpcError, Web3RpcResult}; +use crate::{wei_from_gwei_decimal, wei_to_gwei_decimal, EthCoin, NumConversError}; use ethereum_types::U256; use mm2_err_handle::mm_error::MmError; use mm2_err_handle::or_mm_error::OrMmError; use mm2_number::BigDecimal; use num_traits::FromPrimitive; +use std::convert::TryFrom; use url::Url; use web3::types::BlockNumber; @@ -20,7 +21,7 @@ use gas_api::{BlocknativeBlockPricesResponse, InfuraFeePerGas}; const FEE_PER_GAS_LEVELS: usize = 3; /// Indicates which provider was used to get fee per gas estimations -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug)] pub enum EstimationSource { /// filled by default values Empty, @@ -30,18 +31,19 @@ pub enum EstimationSource { Blocknative, } -impl Default for EstimationSource { - fn default() -> Self { Self::Empty } -} - -/// Estimated fee per gas units -#[derive(Clone, Debug, Serialize)] -pub enum EstimationUnits { - Gwei, +impl ToString for EstimationSource { + fn to_string(&self) -> String { + match self { + EstimationSource::Empty => "empty".into(), + EstimationSource::Simple => "simple".into(), + EstimationSource::Infura => "infura".into(), + EstimationSource::Blocknative => "blocknative".into(), + } + } } -impl Default for EstimationUnits { - fn default() -> Self { Self::Gwei } +impl Default for EstimationSource { + fn default() -> Self { Self::Empty } } enum PriorityLevelId { @@ -66,24 +68,24 @@ pub struct GasApiConfig { } /// Priority level estimated max fee per gas -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Default)] pub struct FeePerGasLevel { - /// estimated max priority tip fee per gas in gwei - pub max_priority_fee_per_gas: BigDecimal, - /// estimated max fee per gas in gwei - pub max_fee_per_gas: BigDecimal, + /// estimated max priority tip fee per gas in wei + pub max_priority_fee_per_gas: U256, + /// estimated max fee per gas in wei + pub max_fee_per_gas: U256, /// estimated transaction min wait time in mempool in ms for this priority level pub min_wait_time: Option, /// estimated transaction max wait time in mempool in ms for this priority level pub max_wait_time: Option, } -/// Estimated gas price for several priority levels -/// we support low/medium/high levels as we can use api providers which normally support such levels -#[derive(Default, Debug, Clone, Serialize)] +/// Internal struct for estimated fee per gas for several priority levels, in wei +/// low/medium/high levels are supported +#[derive(Default, Debug, Clone)] pub struct FeePerGasEstimated { - /// base fee for the next block in gwei - pub base_fee: BigDecimal, + /// base fee for the next block in wei + pub base_fee: U256, /// estimated low priority fee pub low: FeePerGasLevel, /// estimated medium priority fee @@ -92,84 +94,92 @@ pub struct FeePerGasEstimated { pub high: FeePerGasLevel, /// which estimator used pub source: EstimationSource, - /// fee units - pub units: EstimationUnits, /// base trend (up or down) pub base_fee_trend: String, /// priority trend (up or down) pub priority_fee_trend: String, } -impl From for FeePerGasEstimated { - fn from(infura_fees: InfuraFeePerGas) -> Self { - Self { - base_fee: infura_fees.estimated_base_fee, +impl TryFrom for FeePerGasEstimated { + fn try_from(infura_fees: InfuraFeePerGas) -> Result { + Ok(Self { + base_fee: wei_from_gwei_decimal!(&infura_fees.estimated_base_fee)?, low: FeePerGasLevel { - max_fee_per_gas: infura_fees.low.suggested_max_fee_per_gas, - max_priority_fee_per_gas: 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: infura_fees.medium.suggested_max_fee_per_gas, - max_priority_fee_per_gas: 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: infura_fees.high.suggested_max_fee_per_gas, - max_priority_fee_per_gas: 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), }, source: EstimationSource::Infura, - units: EstimationUnits::Gwei, base_fee_trend: infura_fees.base_fee_trend, priority_fee_trend: infura_fees.priority_fee_trend, - } + }) } + + type Error = MmError; } -impl From for FeePerGasEstimated { - fn from(block_prices: BlocknativeBlockPricesResponse) -> Self { +impl TryFrom for FeePerGasEstimated { + fn try_from(block_prices: BlocknativeBlockPricesResponse) -> Result { if block_prices.block_prices.is_empty() { - return FeePerGasEstimated::default(); + return Ok(FeePerGasEstimated::default()); } if block_prices.block_prices[0].estimated_prices.len() < FEE_PER_GAS_LEVELS { - return FeePerGasEstimated::default(); + return Ok(FeePerGasEstimated::default()); } - Self { - base_fee: block_prices.block_prices[0].base_fee_per_gas.clone(), + Ok(Self { + base_fee: wei_from_gwei_decimal!(&block_prices.block_prices[0].base_fee_per_gas)?, low: FeePerGasLevel { - max_fee_per_gas: block_prices.block_prices[0].estimated_prices[2].max_fee_per_gas.clone(), - max_priority_fee_per_gas: block_prices.block_prices[0].estimated_prices[2] - .max_priority_fee_per_gas - .clone(), + 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 + )?, min_wait_time: None, max_wait_time: None, }, medium: FeePerGasLevel { - max_fee_per_gas: block_prices.block_prices[0].estimated_prices[1].max_fee_per_gas.clone(), - max_priority_fee_per_gas: block_prices.block_prices[0].estimated_prices[1] - .max_priority_fee_per_gas - .clone(), + 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 + )?, min_wait_time: None, max_wait_time: None, }, high: FeePerGasLevel { - max_fee_per_gas: block_prices.block_prices[0].estimated_prices[0].max_fee_per_gas.clone(), - max_priority_fee_per_gas: block_prices.block_prices[0].estimated_prices[0] - .max_priority_fee_per_gas - .clone(), + 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 + )?, min_wait_time: None, max_wait_time: None, }, source: EstimationSource::Blocknative, - units: EstimationUnits::Gwei, base_fee_trend: String::default(), priority_fee_trend: String::default(), - } + }) } + + type Error = MmError; } /// Simple priority fee per gas estimator based on fee history @@ -249,9 +259,9 @@ impl FeePerGasSimpleEstimator { // Calculate the max priority fee per gas based on the rewards percentile. let max_priority_fee_per_gas = Self::percentile_of(&level_rewards, Self::PRIORITY_FEE_PERCENTILES[level_index]); - // Convert the priority fee to a BigDecimal, falling back to 0 on error. - let max_priority_fee_per_gas_decimal = - u256_to_big_decimal(max_priority_fee_per_gas, ETH_GWEI_DECIMALS).unwrap_or_else(|_| BigDecimal::from(0)); + // 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)); // Calculate the max fee per gas by adjusting the base fee and adding the priority fee. let adjust_max_fee = @@ -260,12 +270,11 @@ impl FeePerGasSimpleEstimator { BigDecimal::from_f64(Self::ADJUST_MAX_PRIORITY_FEE[level_index]).unwrap_or_else(|| BigDecimal::from(0)); // TODO: consider use checked ops - let max_fee_per_gas = - base_fee * adjust_max_fee + max_priority_fee_per_gas_decimal.clone() * adjust_max_priority_fee; + let max_fee_per_gas_dec = base_fee * adjust_max_fee + max_priority_fee_per_gas_gwei * adjust_max_priority_fee; Ok(FeePerGasLevel { - max_priority_fee_per_gas: max_priority_fee_per_gas_decimal, - max_fee_per_gas, + max_priority_fee_per_gas, + 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, @@ -282,20 +291,17 @@ impl FeePerGasSimpleEstimator { .first() .cloned() .unwrap_or_else(|| U256::from(0)); - let latest_base_fee = - u256_to_big_decimal(latest_base_fee, ETH_GWEI_DECIMALS).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) let predicted_base_fee = Self::predict_base_fee(&fee_history.base_fee_per_gas); Ok(FeePerGasEstimated { - base_fee: u256_to_big_decimal(predicted_base_fee, ETH_GWEI_DECIMALS) - .unwrap_or_else(|_| BigDecimal::from(0)), - low: Self::priority_fee_for_level(PriorityLevelId::Low, latest_base_fee.clone(), fee_history)?, - medium: Self::priority_fee_for_level(PriorityLevelId::Medium, latest_base_fee.clone(), fee_history)?, - high: Self::priority_fee_for_level(PriorityLevelId::High, latest_base_fee, fee_history)?, + base_fee: predicted_base_fee, + low: Self::priority_fee_for_level(PriorityLevelId::Low, latest_base_fee_dec.clone(), fee_history)?, + medium: Self::priority_fee_for_level(PriorityLevelId::Medium, latest_base_fee_dec.clone(), fee_history)?, + high: Self::priority_fee_for_level(PriorityLevelId::High, latest_base_fee_dec, fee_history)?, source: EstimationSource::Simple, - units: EstimationUnits::Gwei, base_fee_trend: String::default(), priority_fee_trend: String::default(), }) @@ -303,6 +309,8 @@ impl FeePerGasSimpleEstimator { } mod gas_api { + use std::convert::TryInto; + use super::FeePerGasEstimated; use crate::eth::{Web3RpcError, Web3RpcResult}; use http::StatusCode; @@ -390,7 +398,7 @@ mod gas_api { let infura_estimated_fees = Self::make_infura_gas_api_request(&url, headers) .await .mm_err(Web3RpcError::Transport)?; - Ok(infura_estimated_fees.into()) + infura_estimated_fees.try_into().mm_err(Into::into) } } @@ -486,7 +494,7 @@ mod gas_api { let block_prices = Self::make_blocknative_gas_api_request(&url, headers) .await .mm_err(Web3RpcError::Transport)?; - Ok(block_prices.into()) + block_prices.try_into().mm_err(Into::into) } } } diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 8ed0090921..0e74bc3d8e 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -1,17 +1,97 @@ //! RPCs to start/stop gas fee estimator and get estimated base and priority fee per gas -use crate::{lp_coinfind_or_err, AsyncMutex, CoinFindError, MmCoinEnum, NumConversError}; use crate::eth::{EthCoin, EthCoinType, FeeEstimatorContext, FeeEstimatorState, FeePerGasEstimated}; +use crate::{lp_coinfind_or_err, wei_to_gwei_decimal, AsyncMutex, CoinFindError, MmCoinEnum, NumConversError}; use common::executor::{spawn_abortable, Timer}; use common::log::debug; use common::{HttpStatusCode, StatusCode}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use serde_json::Value as Json; +use mm2_number::BigDecimal; +use serde::{Deserialize, Serialize}; +use serde_json::{self as json, Value as Json}; +use std::convert::{TryFrom, TryInto}; +use std::ops::Deref; use std::sync::Arc; const FEE_ESTIMATOR_NAME: &str = "eth_gas_fee_estimator_loop"; +/// Estimated fee per gas units +#[derive(Clone, Debug, Serialize)] +pub enum EstimationUnits { + Gwei, +} + +impl Default for EstimationUnits { + fn default() -> Self { Self::Gwei } +} + +/// Priority level estimated max fee per gas +#[derive(Clone, Debug, Default, Serialize)] +pub struct FeePerGasLevel { + /// estimated max priority tip fee per gas in gwei + pub max_priority_fee_per_gas: BigDecimal, + /// estimated max fee per gas in gwei + pub max_fee_per_gas: BigDecimal, + /// estimated transaction min wait time in mempool in ms for this priority level + pub min_wait_time: Option, + /// estimated transaction max wait time in mempool in ms for this priority level + pub max_wait_time: Option, +} + +/// External struct for estimated fee per gas for several priority levels, in gwei +/// low/medium/high levels are supported +#[derive(Default, Debug, Clone, Serialize)] +pub struct FeePerGasEstimatedExt { + /// base fee for the next block in gwei + pub base_fee: BigDecimal, + /// estimated low priority fee + pub low: FeePerGasLevel, + /// estimated medium priority fee + pub medium: FeePerGasLevel, + /// estimated high priority fee + pub high: FeePerGasLevel, + /// which estimator used + pub source: String, + /// base trend (up or down) + pub base_fee_trend: String, + /// priority trend (up or down) + pub priority_fee_trend: String, + /// fee units + pub units: EstimationUnits, +} + +impl TryFrom for FeePerGasEstimatedExt { + fn try_from(fees: FeePerGasEstimated) -> Result { + Ok(Self { + base_fee: wei_to_gwei_decimal!(fees.base_fee)?, + low: FeePerGasLevel { + max_fee_per_gas: wei_to_gwei_decimal!(fees.low.max_fee_per_gas)?, + max_priority_fee_per_gas: wei_to_gwei_decimal!(fees.low.max_priority_fee_per_gas)?, + min_wait_time: fees.low.min_wait_time, + max_wait_time: fees.low.max_wait_time, + }, + medium: FeePerGasLevel { + max_fee_per_gas: wei_to_gwei_decimal!(fees.medium.max_fee_per_gas)?, + max_priority_fee_per_gas: wei_to_gwei_decimal!(fees.medium.max_priority_fee_per_gas)?, + min_wait_time: fees.medium.min_wait_time, + max_wait_time: fees.medium.max_wait_time, + }, + high: FeePerGasLevel { + max_fee_per_gas: wei_to_gwei_decimal!(fees.high.max_fee_per_gas)?, + max_priority_fee_per_gas: wei_to_gwei_decimal!(fees.high.max_priority_fee_per_gas)?, + min_wait_time: fees.high.min_wait_time, + max_wait_time: fees.high.max_wait_time, + }, + source: fees.source.to_string(), + base_fee_trend: fees.base_fee_trend, + priority_fee_trend: fees.priority_fee_trend, + units: EstimationUnits::Gwei, + }) + } + + type Error = MmError; +} #[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] @@ -222,7 +302,7 @@ pub struct FeeEstimatorRequest { coin: String, } -pub type FeeEstimatorResult = Result>; +pub type FeeEstimatorResult = Result>; /// Start gas priority fee estimator loop pub async fn start_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopRequest) -> FeeEstimatorStartStopResult { @@ -251,5 +331,5 @@ pub async fn stop_eth_fee_estimator(ctx: MmArc, req: FeeEstimatorStartStopReques pub async fn get_eth_estimated_fee_per_gas(ctx: MmArc, req: FeeEstimatorRequest) -> FeeEstimatorResult { let coin = FeeEstimatorContext::check_if_estimator_supported(&ctx, &req.coin).await?; let estimated_fees = FeeEstimatorContext::get_estimated_fees(&coin).await?; - Ok(estimated_fees) + estimated_fees.try_into().mm_err(Into::into) } From b2e0677b25871f6315500fe427aaffe25a4109b4 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 13 Apr 2024 19:59:33 +0500 Subject: [PATCH 60/71] add eip1559 fee per gas option to sign_raw_transaction rpc --- Cargo.lock | 3 + mm2src/coins/eth.rs | 76 ++++++++++++++++---- mm2src/coins/lp_coins.rs | 23 ++++++ mm2src/mm2_main/Cargo.toml | 3 + mm2src/mm2_main/tests/mm2_tests/eth_tests.rs | 37 ++++++++++ mm2src/mm2_test_helpers/src/for_tests.rs | 2 + 6 files changed, 131 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a4ca1be26..310db97cfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4518,6 +4518,7 @@ dependencies = [ "enum-primitive-derive", "enum_derives", "ethabi", + "ethcore-transaction", "ethereum-types", "futures 0.1.29", "futures 0.3.28", @@ -4559,9 +4560,11 @@ dependencies = [ "rand 0.7.3", "rcgen", "regex", + "rlp", "rmp-serde", "rpc", "rpc_task", + "rustc-hex", "rustls 0.20.4", "rustls-pemfile 1.0.2", "script", diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index f442a8ce32..2fefba0c86 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -255,6 +255,8 @@ pub(crate) struct Eip1559FeePerGas { pub(crate) max_priority_fee_per_gas: U256, } +/// Internal structure describing how transaction pays for gas unit: +/// either legacy gas price or EIP-1559 fee per gas #[derive(Clone, Debug)] pub(crate) enum PayForGasOption { Legacy(LegacyGasPrice), @@ -280,6 +282,21 @@ impl PayForGasOption { } } +impl TryFrom for PayForGasOption { + fn try_from(param: PayForGasParams) -> Result { + match param { + PayForGasParams::Legacy(legacy) => Ok(Self::Legacy(LegacyGasPrice { + 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)?, + })), + } + } + type Error = MmError; +} + type GasDetails = (U256, PayForGasOption); #[derive(Debug, Display, EnumFromStringify)] @@ -2501,6 +2518,7 @@ lazy_static! { type EthTxFut = Box + Send + 'static>; +#[allow(clippy::too_many_arguments)] async fn sign_transaction_with_keypair( ctx: MmArc, coin: &EthCoin, @@ -2509,6 +2527,7 @@ async fn sign_transaction_with_keypair( action: Action, data: Vec, gas: U256, + pay_for_gas_option: &PayForGasOption, ) -> Result<(SignedEthTx, Vec), TransactionErr> { let mut status = ctx.log.status_handle(); macro_rules! tags { @@ -2520,18 +2539,13 @@ async fn sign_transaction_with_keypair( status.status(tags!(), "get_addr_nonce…"); let (nonce, web3_instances_with_latest_nonce) = try_tx_s!(coin.clone().get_addr_nonce(coin.my_address).compat().await); - status.status(tags!(), "get_gas_price…"); - let pay_for_gas_option = try_tx_s!( - coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) - .await - ); let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); if !coin.is_tx_type_supported(&tx_type) { return Err(TransactionErr::Plain("Eth transaction type not supported".into())); } let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, action, value, data); - let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, &pay_for_gas_option) + let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; let tx = tx_builder.build()?; @@ -2541,6 +2555,8 @@ async fn sign_transaction_with_keypair( )) } +/// Sign and send eth transaction with provided keypair, +/// This fn is primarily for swap transactions so it uses swap tx fee policy async fn sign_and_send_transaction_with_keypair( ctx: MmArc, coin: &EthCoin, @@ -2556,8 +2572,15 @@ async fn sign_and_send_transaction_with_keypair( &[&"sign-and-send"] }; } + + status.status(tags!(), "get_gas_price…"); + let pay_for_gas_option = try_tx_s!( + coin.get_swap_pay_for_gas_option(coin.get_swap_transaction_fee_policy()) + .await + ); + let (signed, web3_instances_with_latest_nonce) = - sign_transaction_with_keypair(ctx, coin, key_pair, value, action, data, gas).await?; + sign_transaction_with_keypair(ctx, coin, key_pair, value, action, data, gas, &pay_for_gas_option).await?; let bytes = Bytes(rlp::encode(&signed).to_vec()); status.status(tags!(), "send_raw_transaction…"); @@ -2572,6 +2595,8 @@ async fn sign_and_send_transaction_with_keypair( Ok(signed) } +/// Sign and send eth transaction with metamask API, +/// This fn is primarily for swap transactions so it uses swap tx fee policy #[cfg(target_arch = "wasm32")] async fn sign_and_send_transaction_with_metamask( coin: EthCoin, @@ -2646,12 +2671,35 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw activated_key: ref key_pair, .. } => { - return sign_transaction_with_keypair(ctx, coin, key_pair, value, action, data, args.gas_limit) - .await - .map(|(signed_tx, _)| RawTransactionRes { - tx_hex: signed_tx.tx_hex().into(), - }) - .map_to_mm(|err| RawTransactionError::TransactionError(err.get_plain_text_format())); + let pay_for_gas_option = if let Some(ref pay_for_gas) = args.pay_for_gas { + pay_for_gas.clone().try_into()? + } else { + let mut status = ctx.log.status_handle(); + macro_rules! tags { + () => { + &[&"sign-and-send"] + }; + } + // use legacy gas_price() if not set + status.status(tags!(), "get_gas_price…"); + let gas_price = coin.get_gas_price().await?; + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) + }; + return sign_transaction_with_keypair( + ctx, + coin, + key_pair, + value, + action, + data, + args.gas_limit, + &pay_for_gas_option, + ) + .await + .map(|(signed_tx, _)| RawTransactionRes { + tx_hex: signed_tx.tx_hex().into(), + }) + .map_to_mm(|err| RawTransactionError::TransactionError(err.get_plain_text_format())); }, #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => MmError::err(RawTransactionError::InvalidParam( @@ -3559,6 +3607,8 @@ impl EthCoin { #[cfg_attr(test, mockable)] impl EthCoin { + /// Sign and send eth transaction. + /// This function is primarily for swap transactions so internally it relies on the swap tx fee policy pub(crate) fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256) -> EthTxFut { let ctx = try_tx_fus!(MmArc::from_weak(&self.ctx).ok_or("!ctx")); let coin = self.clone(); diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index ea4b2769c9..0b810dc127 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -469,6 +469,27 @@ pub struct SignUtxoTransactionParams { // pub branch_id: Option, zcash or komodo optional consensus branch id, used for signing transactions ahead of current height } +#[derive(Clone, Debug, Deserialize)] +pub struct LegacyGasPrice { + /// Gas price in decimal gwei + pub gas_price: BigDecimal, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Eip1559FeePerGas { + /// Max fee per gas in decimal gwei + pub max_fee_per_gas: BigDecimal, + /// Max priority fee per gas in decimal gwei + pub max_priority_fee_per_gas: BigDecimal, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(tag = "tx_type")] +pub enum PayForGasParams { + Legacy(LegacyGasPrice), + Eip1559(Eip1559FeePerGas), +} + /// sign_raw_transaction RPC request's params for signing raw eth transactions #[derive(Clone, Debug, Deserialize)] pub struct SignEthTransactionParams { @@ -480,6 +501,8 @@ pub struct SignEthTransactionParams { data: Option, /// Eth gas use limit gas_limit: U256, + /// Optional gas price or fee per gas params + pay_for_gas: Option, } #[derive(Clone, Debug, Deserialize)] diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 556631b0e6..34c8643c16 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -126,6 +126,9 @@ mocktopus = "0.8.0" testcontainers = "0.15.0" web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false, features = ["http"] } ethabi = { version = "17.0.0" } +rlp = { version = "0.5" } +ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } +rustc-hex = "2" [build-dependencies] chrono = "0.4" diff --git a/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs b/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs index 6a14a02eda..0e7572ae3a 100644 --- a/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs @@ -1,4 +1,5 @@ use common::block_on; +use ethcore_transaction::UnverifiedTransactionWrapper; use http::StatusCode; use mm2_test_helpers::for_tests::{disable_coin, disable_coin_err, eth_jst_testnet_conf, eth_testnet_conf, get_passphrase, MarketMakerIt, Mm2TestConf, ETH_DEV_FALLBACK_CONTRACT, @@ -173,11 +174,45 @@ fn test_sign_eth_transaction() { "0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94", "1.234", "21000", + None, Some("ABCD1234"), )); assert!(signed_tx["result"]["tx_hex"].is_string()); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_sign_eth_transaction_eip1559() { + let passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let coins = json!([eth_testnet_conf()]); + let conf = Mm2TestConf::seednode(&passphrase, &coins); + let mm = block_on(MarketMakerIt::start_async(conf.conf, conf.rpc_password, None)).unwrap(); + block_on(enable_eth(&mm, "ETH", ETH_DEV_NODES)); + let signed_tx = block_on(call_sign_eth_transaction( + &mm, + "ETH", + "0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94", + "1.234", + "21000", + Some(json!({ + "tx_type": "Eip1559", + "max_fee_per_gas": "1234.567", // in gwei + "max_priority_fee_per_gas": "1.2", // in gwei + })), + Some("ABCD1234"), + )); + let bytes: Vec = ::rustc_hex::FromHex::from_hex( + signed_tx["result"]["tx_hex"] + .as_str() + .expect("returned signed tx in hex"), + ) + .unwrap(); + let tx: UnverifiedTransactionWrapper = rlp::decode(&bytes).expect("decoding signed tx okay"); + if !matches!(tx, UnverifiedTransactionWrapper::Eip1559(..)) { + panic!("expected eip1559 tx"); + } +} + #[cfg(not(target_arch = "wasm32"))] async fn enable_eth(mm: &MarketMakerIt, platform_coin: &str, nodes: &[&str]) -> Json { let enable = mm @@ -209,6 +244,7 @@ async fn call_sign_eth_transaction( to: &str, value: &str, gas_limit: &str, + pay_for_gas: Option, data: Option<&str>, ) -> Json { let signed_tx = mm @@ -223,6 +259,7 @@ async fn call_sign_eth_transaction( "to": to, "value": value, "gas_limit": gas_limit, + "pay_for_gas": pay_for_gas, "data": data } } diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 439a33813f..9256739270 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -774,6 +774,8 @@ pub fn eth_testnet_conf() -> Json { "coin": "ETH", "name": "ethereum", "mm2": 1, + "chain_id": 5, + "max_eth_tx_type": 2, "derivation_path": "m/44'/60'", "protocol": { "type": "ETH" From 8cb846c7dd58dd6bcc28d0392bc96fff15f3ba0c Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 13 Apr 2024 20:01:33 +0500 Subject: [PATCH 61/71] refactor gas limit constants --- mm2src/coins/eth.rs | 82 +++++++++++++++++++---------- mm2src/coins/eth/eth_tests.rs | 28 +++++----- mm2src/coins/eth/nft_swap_v2/mod.rs | 8 +-- 3 files changed, 71 insertions(+), 47 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 2fefba0c86..6e9626a235 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -26,7 +26,7 @@ use crate::eth::web3_transport::websocket_transport::{WebsocketTransport, Websoc use crate::lp_price::get_base_price_in_rel; use crate::nft::nft_structs::{ContractType, ConvertChain, NftInfo, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; -use crate::{DexFee, EthGasLimitOption, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, +use crate::{DexFee, EthGasLimitOption, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, PayForGasParams, RefundMakerPaymentArgs, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, ToBytes, ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; @@ -199,19 +199,33 @@ const GAS_PRICE_APPROXIMATION_PERCENT_ON_ORDER_ISSUE: u64 = 5; /// - it may increase by 3% during the swap. const GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE: u64 = 7; -/// Heuristic gas limits for swap operations (including extra margin value for possible changes in opcodes gas) -mod swap_gas { - pub(crate) const ETH_PAYMENT: u64 = 65_000; // real values are approx 48,6K by etherscan - pub(crate) const ERC20_PAYMENT: u64 = 120_000; // real values 98,9K - pub(crate) const ETH_RECEIVER_SPEND: u64 = 65_000; // real values 40,7K - pub(crate) const ERC20_RECEIVER_SPEND: u64 = 120_000; // real values 72,8K +/// Heuristic gas limits for withdraw and swap operations (for swaps also including extra margin value for possible changes in opcodes gas) +mod gas_limit { + /// Gas limit for sending coins + pub(crate) const ETH_SEND_COINS: u64 = 21_000; + /// Gas limit for transfer ERC20 tokens + /// TODO: maybe this is too much and 150K is okay + pub(crate) const ETH_SEND_ERC20: u64 = 210_000; + /// Gas limit for swap payment tx with coins + /// real values are approx 48,6K by etherscan + pub(crate) const ETH_PAYMENT: u64 = 65_000; + /// Gas limit for swap payment tx with ERC20 tokens + /// real values are 98,9K + pub(crate) const ERC20_PAYMENT: u64 = 120_000; + /// Gas limit for swap receiver spend tx with coins + /// real values are 40,7K + pub(crate) const ETH_RECEIVER_SPEND: u64 = 65_000; + /// Gas limit for swap receiver spend tx with ERC20 tokens + /// real values are 72,8K + pub(crate) const ERC20_RECEIVER_SPEND: u64 = 120_000; + /// Gas limit for swap refund tx with coins pub(crate) const ETH_SENDER_REFUND: u64 = 100_000; + /// Gas limit for swap refund tx with with ERC20 tokens pub(crate) const ERC20_SENDER_REFUND: u64 = 150_000; + /// Gas limit for other operations + pub(crate) const ETH_MAX_TRADE_GAS: u64 = 150_000; } -/// trade tx gas limit max for all operations -const ETH_MAX_TRADE_GAS: u64 = 150_000; - /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; @@ -3631,7 +3645,12 @@ impl EthCoin { pub fn send_to_address(&self, address: Address, value: U256) -> EthTxFut { match &self.coin_type { - EthCoinType::Eth => self.sign_and_send_transaction(value, Action::Call(address), vec![], U256::from(21000)), + EthCoinType::Eth => self.sign_and_send_transaction( + value, + Action::Call(address), + vec![], + U256::from(gas_limit::ETH_SEND_COINS), + ), EthCoinType::Erc20 { platform: _, token_addr, @@ -3639,7 +3658,12 @@ impl EthCoin { let abi = try_tx_fus!(Contract::load(ERC20_ABI.as_bytes())); let function = try_tx_fus!(abi.function("transfer")); let data = try_tx_fus!(function.encode_input(&[Token::Address(address), Token::Uint(value)])); - self.sign_and_send_transaction(0.into(), Action::Call(*token_addr), data, U256::from(210_000)) + self.sign_and_send_transaction( + 0.into(), + Action::Call(*token_addr), + data, + U256::from(gas_limit::ETH_SEND_ERC20), + ) }, EthCoinType::Nft { .. } => { return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( @@ -3693,7 +3717,7 @@ impl EthCoin { Token::Uint(time_lock), ])), }; - let gas = U256::from(swap_gas::ETH_PAYMENT); + let gas = U256::from(gas_limit::ETH_PAYMENT); self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas) }, EthCoinType::Erc20 { @@ -3764,7 +3788,7 @@ impl EthCoin { }; let wait_for_required_allowance_until = args.wait_for_confirmation_until; - let gas = U256::from(swap_gas::ERC20_PAYMENT); + let gas = U256::from(gas_limit::ERC20_PAYMENT); let arc = self.clone(); Box::new(allowance_fut.and_then(move |allowed| -> EthTxFut { @@ -3874,7 +3898,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_RECEIVER_SPEND), + U256::from(gas_limit::ETH_RECEIVER_SPEND), ) }), ) @@ -3922,7 +3946,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_RECEIVER_SPEND), + U256::from(gas_limit::ERC20_RECEIVER_SPEND), ) }), ) @@ -3996,7 +4020,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_SENDER_REFUND), + U256::from(gas_limit::ETH_SENDER_REFUND), ) }), ) @@ -4047,7 +4071,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_SENDER_REFUND), + U256::from(gas_limit::ERC20_SENDER_REFUND), ) }), ) @@ -4117,7 +4141,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_RECEIVER_SPEND), + U256::from(gas_limit::ETH_RECEIVER_SPEND), ) }), ) @@ -4169,7 +4193,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_RECEIVER_SPEND), + U256::from(gas_limit::ERC20_RECEIVER_SPEND), ) }), ) @@ -4240,7 +4264,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ETH_SENDER_REFUND), + U256::from(gas_limit::ETH_SENDER_REFUND), ) }), ) @@ -4292,7 +4316,7 @@ impl EthCoin { 0.into(), Action::Call(swap_contract_address), data, - U256::from(swap_gas::ERC20_SENDER_REFUND), + U256::from(gas_limit::ERC20_SENDER_REFUND), ) }), ) @@ -5522,13 +5546,13 @@ impl MmCoin for EthCoin { EthCoinType::Eth => { // this gas_limit includes gas for `ethPayment` and optionally `senderRefund` contract calls if include_refund_fee { - U256::from(swap_gas::ETH_PAYMENT) + U256::from(swap_gas::ETH_SENDER_REFUND) + U256::from(gas_limit::ETH_PAYMENT) + U256::from(gas_limit::ETH_SENDER_REFUND) } else { - U256::from(swap_gas::ETH_PAYMENT) + U256::from(gas_limit::ETH_PAYMENT) } }, EthCoinType::Erc20 { token_addr, .. } => { - let mut gas = U256::from(swap_gas::ERC20_PAYMENT); + let mut gas = U256::from(gas_limit::ERC20_PAYMENT); let value = match value { TradePreimageValue::Exact(value) | TradePreimageValue::UpperBound(value) => { wei_from_big_decimal(&value, self.decimals)? @@ -5550,7 +5574,7 @@ impl MmCoin for EthCoin { gas += approve_gas_limit; } if include_refund_fee { - gas += U256::from(swap_gas::ERC20_SENDER_REFUND); // add 'senderRefund' gas if requested + gas += U256::from(gas_limit::ERC20_SENDER_REFUND); // add 'senderRefund' gas if requested } gas }, @@ -5581,11 +5605,11 @@ impl MmCoin for EthCoin { let (fee_coin, total_fee) = match &coin.coin_type { EthCoinType::Eth => ( &coin.ticker, - calc_total_fee(U256::from(swap_gas::ETH_RECEIVER_SPEND), &pay_for_gas_option)?, + calc_total_fee(U256::from(gas_limit::ETH_RECEIVER_SPEND), &pay_for_gas_option)?, ), EthCoinType::Erc20 { platform, .. } => ( platform, - calc_total_fee(U256::from(swap_gas::ERC20_RECEIVER_SPEND), &pay_for_gas_option)?, + calc_total_fee(U256::from(gas_limit::ERC20_RECEIVER_SPEND), &pay_for_gas_option)?, ), EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; @@ -6581,7 +6605,7 @@ async fn get_eth_gas_details_from_withdraw_fee( // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH let eth_value_for_estimate = if fungible_max && eth_coin.coin_type == EthCoinType::Eth { - eth_value - calc_total_fee(U256::from(21000), &pay_for_gas_option)? + eth_value - calc_total_fee(U256::from(gas_limit::ETH_SEND_COINS), &pay_for_gas_option)? } else { eth_value }; diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 25b6e2210d..11d8bb8460 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -473,7 +473,7 @@ fn test_withdraw_impl_manual_fee() { coin: "ETH".to_string(), max: false, fee: Some(WithdrawFee::EthGas { - gas: ETH_MAX_TRADE_GAS, + gas: gas_limit::ETH_MAX_TRADE_GAS, gas_price: 1.into(), }), memo: None, @@ -485,7 +485,7 @@ fn test_withdraw_impl_manual_fee() { EthTxFeeDetails { coin: "ETH".into(), gas_price: "0.000000001".parse().unwrap(), - gas: ETH_MAX_TRADE_GAS, + gas: gas_limit::ETH_MAX_TRADE_GAS, total_fee: "0.00015".parse().unwrap(), max_fee_per_gas: None, max_priority_fee_per_gas: None, @@ -520,7 +520,7 @@ fn test_withdraw_impl_fee_details() { coin: "JST".to_string(), max: false, fee: Some(WithdrawFee::EthGas { - gas: ETH_MAX_TRADE_GAS, + gas: gas_limit::ETH_MAX_TRADE_GAS, gas_price: 1.into(), }), memo: None, @@ -532,7 +532,7 @@ fn test_withdraw_impl_fee_details() { EthTxFeeDetails { coin: "ETH".into(), gas_price: "0.000000001".parse().unwrap(), - gas: ETH_MAX_TRADE_GAS, + gas: gas_limit::ETH_MAX_TRADE_GAS, total_fee: "0.00015".parse().unwrap(), max_fee_per_gas: None, max_priority_fee_per_gas: None, @@ -558,7 +558,7 @@ fn test_nonce_lock() { 1000000000000u64.into(), Action::Call(coin.my_address), vec![], - 21000.into(), + gas_limit::ETH_SEND_COINS.into(), ) .compat(), ); @@ -613,7 +613,7 @@ fn get_sender_trade_preimage() { true, )) .expect("!get_sender_trade_fee"); - let expected = expected_fee(GAS_PRICE, swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND); + let expected = expected_fee(GAS_PRICE, gas_limit::ETH_PAYMENT + gas_limit::ETH_SENDER_REFUND); assert_eq!(actual, expected); let value = u256_to_big_decimal(100.into(), 18).expect("!u256_to_big_decimal"); @@ -622,7 +622,7 @@ fn get_sender_trade_preimage() { .expect("!get_sender_trade_fee"); let expected = expected_fee( GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE, - swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND, + gas_limit::ETH_PAYMENT + gas_limit::ETH_SENDER_REFUND, ); assert_eq!(actual, expected); @@ -631,7 +631,7 @@ fn get_sender_trade_preimage() { .expect("!get_sender_trade_fee"); let expected = expected_fee( GAS_PRICE_APPROXIMATION_ON_START_SWAP, - swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND, + gas_limit::ETH_PAYMENT + gas_limit::ETH_SENDER_REFUND, ); assert_eq!(actual, expected); @@ -641,7 +641,7 @@ fn get_sender_trade_preimage() { .expect("!get_sender_trade_fee"); let expected = expected_fee( GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE, - swap_gas::ETH_PAYMENT + swap_gas::ETH_SENDER_REFUND, + gas_limit::ETH_PAYMENT + gas_limit::ETH_SENDER_REFUND, ); assert_eq!(actual, expected); } @@ -692,7 +692,7 @@ fn get_erc20_sender_trade_preimage() { unsafe { assert!(!ESTIMATE_GAS_CALLED) } assert_eq!( actual, - expected_trade_fee(swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND, GAS_PRICE) + expected_trade_fee(gas_limit::ERC20_PAYMENT + gas_limit::ERC20_SENDER_REFUND, GAS_PRICE) ); // value is greater than allowance @@ -708,7 +708,7 @@ fn get_erc20_sender_trade_preimage() { assert_eq!( actual, expected_trade_fee( - swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND + APPROVE_GAS_LIMIT, + gas_limit::ERC20_PAYMENT + gas_limit::ERC20_SENDER_REFUND + APPROVE_GAS_LIMIT, GAS_PRICE_APPROXIMATION_ON_START_SWAP ) ); @@ -723,7 +723,7 @@ fn get_erc20_sender_trade_preimage() { assert_eq!( actual, expected_trade_fee( - swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND, + gas_limit::ERC20_PAYMENT + gas_limit::ERC20_SENDER_REFUND, GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE ) ); @@ -741,7 +741,7 @@ fn get_erc20_sender_trade_preimage() { assert_eq!( actual, expected_trade_fee( - swap_gas::ERC20_PAYMENT + swap_gas::ERC20_SENDER_REFUND + APPROVE_GAS_LIMIT, + gas_limit::ERC20_PAYMENT + gas_limit::ERC20_SENDER_REFUND + APPROVE_GAS_LIMIT, GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE ) ); @@ -753,7 +753,7 @@ fn get_receiver_trade_preimage() { let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &["http://dummy.dummy"], None); let amount = - u256_to_big_decimal((swap_gas::ETH_RECEIVER_SPEND * GAS_PRICE).into(), 18).expect("!u256_to_big_decimal"); + u256_to_big_decimal((gas_limit::ETH_RECEIVER_SPEND * GAS_PRICE).into(), 18).expect("!u256_to_big_decimal"); let expected_fee = TradeFee { coin: "ETH".to_owned(), amount: amount.into(), diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index 61cda005f1..7134957265 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -14,8 +14,8 @@ mod structs; use structs::{ExpectedHtlcParams, PaymentType, ValidationParams}; use super::ContractType; -use crate::eth::{addr_from_raw_pubkey, decode_contract_call, EthCoin, EthCoinType, MakerPaymentStateV2, SignedEthTx, - TryToAddress, ERC1155_CONTRACT, ERC721_CONTRACT, ETH_MAX_TRADE_GAS, NFT_SWAP_CONTRACT}; +use crate::eth::{addr_from_raw_pubkey, decode_contract_call, gas_limit::ETH_MAX_TRADE_GAS, EthCoin, EthCoinType, + MakerPaymentStateV2, SignedEthTx, TryToAddress, ERC1155_CONTRACT, ERC721_CONTRACT, NFT_SWAP_CONTRACT}; use crate::{ParseCoinAssocTypes, RefundPaymentArgs, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, TransactionErr, ValidateNftMakerPaymentArgs}; @@ -39,7 +39,7 @@ impl EthCoin { 0.into(), Action::Call(*args.nft_swap_info.token_address), data, - U256::from(ETH_MAX_TRADE_GAS), + U256::from(ETH_MAX_TRADE_GAS), // TODO: fix to a more accurate const or estimated value ) .compat() .await @@ -158,7 +158,7 @@ impl EthCoin { 0.into(), Action::Call(*etomic_swap_contract), data, - U256::from(ETH_MAX_TRADE_GAS), + U256::from(ETH_MAX_TRADE_GAS), // TODO: fix to a more accurate const or estimated value ) .compat() .await From fffe24a5adeb3426ea157f45a3d34baa886b958d Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 13 Apr 2024 20:04:31 +0500 Subject: [PATCH 62/71] refactor swap code to add named constants indicating when refund fee is included --- mm2src/coins/lp_coins.rs | 6 ++++++ mm2src/mm2_main/src/lp_swap.rs | 5 +++++ mm2src/mm2_main/src/lp_swap/maker_swap.rs | 12 +++++++----- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 4 ++-- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 12 +++++++----- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 4 ++-- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 0b810dc127..63b241018e 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4544,6 +4544,12 @@ pub async fn my_tx_history(ctx: MmArc, req: Json) -> Result>, S Ok(try_s!(Response::builder().body(body))) } +/// `get_trade_fee` rpc implementation. +/// There is some consideration about this rpc: +/// for eth coin this rpc returns max possible trade fee (estimated for maximum possible gas limit for any kind of swap). +/// However for eth coin, as part of fixing this issue https://github.com/KomodoPlatform/komodo-defi-framework/issues/1848, +/// `max_taker_vol' and `trade_preimage` rpc now return more accurate required gas calculations. +/// So maybe it would be better to deprecate this `get_trade_fee` rpc pub async fn get_trade_fee(ctx: MmArc, req: Json) -> Result>, String> { let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); let coin = match lp_coinfind(&ctx, &ticker).await { diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 91f13d9638..b28131743c 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1831,6 +1831,11 @@ pub fn generate_secret() -> Result<[u8; 32], rand::Error> { Ok(sec) } +/// Add refund fee to calculate maximul available balance for a swap (including possible refund) +pub(crate) const INCLUDE_REFUND_FEE: bool = true; +/// Do not add refund fee to calculate fee needed only to make a successful swap +pub(crate) const NO_REFUND_FEE: bool = false; + #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index b211046a96..e9d712b2b4 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -9,7 +9,7 @@ use super::{broadcast_my_swap_status, broadcast_p2p_tx_msg, broadcast_swap_msg_e wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SecretHashAlgo, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, - SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL_SEC}; + SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, WAIT_CONFIRM_INTERVAL_SEC}; use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::MakerOrderBuilder; @@ -474,7 +474,9 @@ impl MakerSwap { // do not use self.r().data here as it is not initialized at this step yet let preimage_value = TradePreimageValue::Exact(self.maker_amount.clone()); let stage = FeeApproxStage::StartSwap; - let get_sender_trade_fee_fut = self.maker_coin.get_sender_trade_fee(preimage_value, stage, false); + let get_sender_trade_fee_fut = self + .maker_coin + .get_sender_trade_fee(preimage_value, stage, NO_REFUND_FEE); let maker_payment_trade_fee = match get_sender_trade_fee_fut.await { Ok(fee) => fee, Err(e) => { @@ -2177,7 +2179,7 @@ pub async fn check_balance_for_maker_swap( None => { let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); let maker_payment_trade_fee = my_coin - .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to check balance + .get_sender_trade_fee(preimage_value, stage, INCLUDE_REFUND_FEE) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let taker_payment_spend_trade_fee = other_coin @@ -2231,7 +2233,7 @@ pub async fn maker_swap_trade_preimage( let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); let base_coin_fee = base_coin - .get_sender_trade_fee(preimage_value, FeeApproxStage::TradePreimage, false) + .get_sender_trade_fee(preimage_value, FeeApproxStage::TradePreimage, NO_REFUND_FEE) .await .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, base_coin_ticker))?; let rel_coin_fee = rel_coin @@ -2313,7 +2315,7 @@ pub async fn calc_max_maker_vol( let preimage_value = TradePreimageValue::UpperBound(volume.to_decimal()); let trade_fee = coin - .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to get max trade amount + .get_sender_trade_fee(preimage_value, stage, INCLUDE_REFUND_FEE) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, ticker))?; diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 6143b708f1..95af4435e9 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -3,9 +3,9 @@ use super::{swap_v2_topic, LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsC NEGOTIATION_TIMEOUT_SEC}; use crate::mm2::lp_swap::maker_swap::MakerSwapPreparedParams; use crate::mm2::lp_swap::swap_lock::SwapLock; -use crate::mm2::lp_swap::swap_v2_pb::*; use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; +use crate::mm2::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, GenTakerFundingSpendArgs, @@ -812,7 +812,7 @@ impl fee, diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 793816a01e..6705c7ae48 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -9,7 +9,7 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_msg recv_swap_msg, swap_topic, wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, - SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL_SEC}; + SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, WAIT_CONFIRM_INTERVAL_SEC}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::TakerOrderBuilder; use crate::mm2::lp_swap::swap_v2_common::mark_swap_as_finished; @@ -1010,7 +1010,9 @@ impl TakerSwap { )])) }, }; - let get_sender_trade_fee_fut = self.taker_coin.get_sender_trade_fee(preimage_value, stage, false); + let get_sender_trade_fee_fut = self + .taker_coin + .get_sender_trade_fee(preimage_value, stage, NO_REFUND_FEE); let taker_payment_trade_fee = match get_sender_trade_fee_fut.await { Ok(fee) => fee, Err(e) => { @@ -2368,7 +2370,7 @@ pub async fn check_balance_for_taker_swap( .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); let taker_payment_trade_fee = my_coin - .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to check balance for swap + .get_sender_trade_fee(preimage_value, stage, INCLUDE_REFUND_FEE) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let maker_payment_spend_trade_fee = other_coin @@ -2463,7 +2465,7 @@ pub async fn taker_swap_trade_preimage( let preimage_value = TradePreimageValue::Exact(my_coin_volume.to_decimal()); let my_coin_trade_fee = my_coin - .get_sender_trade_fee(preimage_value, stage, false) + .get_sender_trade_fee(preimage_value, stage, NO_REFUND_FEE) .await .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, my_coin_ticker))?; let other_coin_trade_fee = other_coin @@ -2588,7 +2590,7 @@ pub async fn calc_max_taker_vol( let max_possible = &balance - &locked; let preimage_value = TradePreimageValue::UpperBound(max_possible.to_decimal()); let max_trade_fee = coin - .get_sender_trade_fee(preimage_value, stage, true) // use send+refund fee to get max trade amount + .get_sender_trade_fee(preimage_value, stage, INCLUDE_REFUND_FEE) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin))?; diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 5f1389adbd..256503350b 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -2,10 +2,10 @@ use super::swap_v2_common::*; use super::{LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsContext, TakerSwapPreparedParams, NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; use crate::mm2::lp_swap::swap_lock::SwapLock; -use crate::mm2::lp_swap::swap_v2_pb::*; use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, swap_v2_topic, SecretHashAlgo, SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, TAKER_SWAP_V2_TYPE}; +use crate::mm2::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, @@ -930,7 +930,7 @@ impl fee, From ebe4cad0e59ea13db49512779d2dc395723c9eff Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 13 Apr 2024 23:28:11 +0500 Subject: [PATCH 63/71] fix getting 'max_eth_tx_type' to look for it also in token conf to allow gas priority fee tests to work --- mm2src/coins/eth.rs | 38 ++++++++++++++++-------- mm2src/coins/eth/eth_wasm_tests.rs | 11 +++++-- mm2src/mm2_test_helpers/src/for_tests.rs | 18 +++++++---- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6e9626a235..9091c1c6be 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -6162,22 +6162,36 @@ fn rpc_event_handlers_for_eth_transport(ctx: &MmArc, ticker: String) -> Vec Result, String> { - let max_eth_tx_type = match &coin_type { - EthCoinType::Eth => conf["max_eth_tx_type"].as_u64(), + fn check_max_eth_tx_type_conf(conf: &Json) -> Result, String> { + if !conf["max_eth_tx_type"].is_null() { + let max_eth_tx_type = conf["max_eth_tx_type"] + .as_u64() + .ok_or_else(|| "max_eth_tx_type in coins is invalid".to_string())?; + if max_eth_tx_type > ETH_MAX_TX_TYPE { + return Err("max_eth_tx_type in coins is too big".to_string()); + } + Ok(Some(max_eth_tx_type)) + } else { + Ok(None) + } + } + + match &coin_type { + EthCoinType::Eth => check_max_eth_tx_type_conf(conf), EthCoinType::Erc20 { platform, .. } | EthCoinType::Nft { platform } => { - let platform_coin = lp_coinfind_or_err(ctx, platform).await; - match platform_coin { - Ok(MmCoinEnum::EthCoin(eth_coin)) => eth_coin.max_eth_tx_type, - _ => None, + let coin_max_eth_tx_type = check_max_eth_tx_type_conf(conf)?; + // Normally we suppose max_eth_tx_type is in platform coin but also try to get it from tokens for tests to work: + if let Some(coin_max_eth_tx_type) = coin_max_eth_tx_type { + Ok(Some(coin_max_eth_tx_type)) + } else { + let platform_coin = lp_coinfind_or_err(ctx, platform).await; + match platform_coin { + Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.max_eth_tx_type), + _ => Ok(None), + } } }, - }; - if let Some(max_eth_tx_type) = max_eth_tx_type { - if max_eth_tx_type > ETH_MAX_TX_TYPE { - return Err("eth tx type too big in coins file".into()); - } } - Ok(max_eth_tx_type) } #[inline] diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index c7e28b8103..ca9c03b1e5 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -82,7 +82,8 @@ async fn init_eth_coin_helper() -> Result<(MmArc, MmCoinEnum), String> { }, "chain_id": 1, "rpcport": 80, - "mm2": 1 + "mm2": 1, + "max_eth_tx_type": 2 }] }); @@ -126,14 +127,18 @@ async fn wasm_test_sign_eth_tx() { async fn wasm_test_sign_eth_tx_with_priority_fee() { // we need to hold ref to _ctx until the end of the test (because of the weak ref to MmCtx in EthCoinImpl) let (_ctx, coin) = init_eth_coin_helper().await.unwrap(); - coin.set_swap_transaction_fee_policy(SwapTxFeePolicy::Medium); let sign_req = json::from_value(json!({ "coin": "ETH", "type": "ETH", "tx": { "to": "0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94".to_string(), "value": "1.234", - "gas_limit": "21000" + "gas_limit": "21000", + "pay_for_gas": { + "tx_type": "Eip1559", + "max_fee_per_gas": "1234.567", + "max_priority_fee_per_gas": "1.2", + } } })) .unwrap(); diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 9256739270..c7fafa0b7a 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -793,7 +793,8 @@ pub fn eth_dev_conf() -> Json { "derivation_path": "m/44'/60'", "protocol": { "type": "ETH" - } + }, + "max_eth_tx_type": 2 }) } @@ -811,7 +812,8 @@ pub fn erc20_dev_conf(contract_address: &str) -> Json { "platform": "ETH", "contract_address": contract_address, } - } + }, + "max_eth_tx_type": 2 }) } @@ -828,7 +830,8 @@ pub fn nft_dev_conf() -> Json { "protocol_data": { "platform": "ETH" } - } + }, + "max_eth_tx_type": 2 }) } @@ -839,7 +842,8 @@ pub fn eth_sepolia_conf() -> Json { "chain_id": 11155111, "protocol": { "type": "ETH" - } + }, + "max_eth_tx_type": 2 }) } @@ -854,7 +858,8 @@ pub fn eth_jst_testnet_conf() -> Json { "platform": "ETH", "contract_address": ETH_DEV_TOKEN_CONTRACT } - } + }, + "max_eth_tx_type": 2 }) } @@ -870,7 +875,8 @@ pub fn jst_sepolia_conf() -> Json { "chain_id": 11155111, "contract_address": ETH_SEPOLIA_TOKEN_CONTRACT } - } + }, + "max_eth_tx_type": 2 }) } From 54b60a65888bf93e6b284eec75f2d38f05e77e96 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 14 Apr 2024 11:40:40 +0500 Subject: [PATCH 64/71] add check result for wasm sign raw tx test --- mm2src/coins/eth/eth_wasm_tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index ca9c03b1e5..928e7fda26 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -145,4 +145,8 @@ async fn wasm_test_sign_eth_tx_with_priority_fee() { let res = coin.sign_raw_tx(&sign_req).await; console::log_1(&format!("res={:?}", res).into()); assert!(res.is_ok()); + let tx: UnverifiedTransactionWrapper = rlp::decode(&res.unwrap().tx_hex).expect("decoding signed tx okay"); + if !matches!(tx, UnverifiedTransactionWrapper::Eip1559(..)) { + panic!("expected eip1559 tx"); + } } From 7cbe03da9906bb39bc9215f7dea7b2f2b2c0c1d6 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 10 May 2024 11:32:00 +0500 Subject: [PATCH 65/71] fix build err --- mm2src/coins/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 71f2032fbb..0eccb1114d 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2506,7 +2506,7 @@ async fn sign_and_send_transaction_with_keypair( data, gas, &pay_for_gas_option, - my_address, + address, ) .await?; let bytes = Bytes(rlp::encode(&signed).to_vec()); From 9de585bb488c12a6e01be68ba319b1c3aa833fde Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 10 May 2024 11:42:43 +0500 Subject: [PATCH 66/71] fix eip1559 trezor signature parsing --- .../tests/mm2_tests/mm2_tests_inner.rs | 21 +++++++++++++-- mm2src/trezor/src/eth/eth_command.rs | 26 +++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index f025e87e25..99cfa418e3 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -6236,6 +6236,7 @@ fn test_sign_raw_transaction_p2wpkh() { #[cfg(all(feature = "run-device-tests", not(target_arch = "wasm32")))] mod trezor_tests { + use coins::EthGasLimitOption; use coins::eth::{eth_coin_from_conf_and_request, gas_limit, EthCoin}; use coins::for_tests::test_withdraw_init_loop; use coins::rpc_command::account_balance::{AccountBalanceParams, AccountBalanceRpcOps}; @@ -6626,7 +6627,23 @@ mod trezor_tests { gas_price: 0.1_f32.try_into().unwrap(), }), )) - .expect("withdraw must end successfully"); + .expect("withdraw eth must end successfully"); + log!("tx_hex={}", serde_json::to_string(&tx_details.tx_hex).unwrap()); + + // try to create eth withdrawal eip1559 tx + let tx_details = block_on(test_withdraw_init_loop( + ctx.clone(), + ticker_coin, + "0xc06eFafa6527fc4b3C8F69Afb173964A3780a104", + "0.00001", + None, // try withdraw from default account + Some(WithdrawFee::EthGasEip1559 { + gas_option: EthGasLimitOption::Set(gas_limit::ETH_SEND_COINS), + max_fee_per_gas: 12.3_f32.try_into().unwrap(), + max_priority_fee_per_gas: 1.2_f32.try_into().unwrap(), + }), + )) + .expect("withdraw eth with eip1559 tx must end successfully"); log!("tx_hex={}", serde_json::to_string(&tx_details.tx_hex).unwrap()); // create a non-default address expected as "m/44'/1'/0'/0/1" (must be topped up already) @@ -6639,7 +6656,7 @@ mod trezor_tests { // TODO: ideally should be in loop to handle pin let new_addr_resp = block_on(eth_coin.get_new_address_rpc_without_conf(new_addr_params)).expect("new account created"); - println!("create new_addr_resp={:?}", new_addr_resp); + log!("create new_addr_resp={:?}", new_addr_resp); // try to create JST ERC20 token withdrawal tx from a non-default account (should have some tokens on it) let tx_details = block_on(test_withdraw_init_loop( diff --git a/mm2src/trezor/src/eth/eth_command.rs b/mm2src/trezor/src/eth/eth_command.rs index 43ce790593..7f2d73576d 100644 --- a/mm2src/trezor/src/eth/eth_command.rs +++ b/mm2src/trezor/src/eth/eth_command.rs @@ -21,6 +21,7 @@ type StaticAddressBytes = &'static [u8]; // new supported eth networks: const SEPOLIA_ID: u64 = 11155111; +const EIP2930_NOT_SUPPORTED_ERROR: &str = "EIP-2930 tx not supported for Trezor"; lazy_static! { @@ -125,7 +126,7 @@ impl<'a> TrezorSession<'a> { .mm_err(|e| TrezorError::Internal(e.to_string()))? }, TransactionWrapper::Eip2930(_) => { - return MmError::err(TrezorError::Internal("EIP-2930 tx not supported for Trezor".to_owned())) + return MmError::err(TrezorError::Internal(EIP2930_NOT_SUPPORTED_ERROR.to_owned())) }, }; @@ -145,7 +146,13 @@ impl<'a> TrezorSession<'a> { } } - let sig = extract_eth_signature(&tx_request)?; + let sig = match unsigned_tx { + TransactionWrapper::Legacy(_) => extract_eth_signature(&tx_request)?, + TransactionWrapper::Eip1559(_) => extract_eth_eip1559_signature(&tx_request)?, + TransactionWrapper::Eip2930(_) => { + return MmError::err(TrezorError::Internal(EIP2930_NOT_SUPPORTED_ERROR.to_owned())) + }, + }; unsigned_tx .clone() .with_signature(sig, Some(chain_id)) @@ -302,3 +309,18 @@ fn extract_eth_signature(tx_request: &proto_ethereum::EthereumTxRequest) -> Trez (_, _, _) => Err(MmError::new(TrezorError::Failure(OperationFailure::InvalidSignature))), } } + +fn extract_eth_eip1559_signature(tx_request: &proto_ethereum::EthereumTxRequest) -> TrezorResult { + match ( + tx_request.signature_r.as_ref(), + tx_request.signature_s.as_ref(), + tx_request.signature_v, + ) { + (Some(r), Some(s), Some(v)) => Ok(Signature::from_rsv( + &H256::from_slice(r.as_slice()), + &H256::from_slice(s.as_slice()), + v as u8, + )), + (_, _, _) => Err(MmError::new(TrezorError::Failure(OperationFailure::InvalidSignature))), + } +} From c516fd1d200eff00eecb46f46d073776c6e57405 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 10 May 2024 11:42:55 +0500 Subject: [PATCH 67/71] fix fmt --- mm2src/coins/eth.rs | 13 ++----------- mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 0eccb1114d..042f0f3de6 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2498,17 +2498,8 @@ async fn sign_and_send_transaction_with_keypair( ); let address_lock = coin.get_address_lock(address.to_string()).await; let _nonce_lock = address_lock.lock().await; - let (signed, web3_instances_with_latest_nonce) = sign_transaction_with_keypair( - coin, - key_pair, - value, - action, - data, - gas, - &pay_for_gas_option, - address, - ) - .await?; + let (signed, web3_instances_with_latest_nonce) = + sign_transaction_with_keypair(coin, key_pair, value, action, data, gas, &pay_for_gas_option, address).await?; let bytes = Bytes(rlp::encode(&signed).to_vec()); info!(target: "sign-and-send", "send_raw_transaction…"); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 99cfa418e3..6d4fb037f8 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -6236,13 +6236,13 @@ fn test_sign_raw_transaction_p2wpkh() { #[cfg(all(feature = "run-device-tests", not(target_arch = "wasm32")))] mod trezor_tests { - use coins::EthGasLimitOption; use coins::eth::{eth_coin_from_conf_and_request, gas_limit, EthCoin}; use coins::for_tests::test_withdraw_init_loop; use coins::rpc_command::account_balance::{AccountBalanceParams, AccountBalanceRpcOps}; use coins::rpc_command::get_new_address::{GetNewAddressParams, GetNewAddressRpcOps}; use coins::rpc_command::init_create_account::for_tests::test_create_new_account_init_loop; use coins::utxo::{utxo_standard::UtxoStandardCoin, UtxoActivationParams}; + use coins::EthGasLimitOption; use coins::{lp_coinfind, CoinProtocol, MmCoinEnum, PrivKeyBuildPolicy}; use coins_activation::platform_for_tests::init_platform_coin_with_tokens_loop; use coins_activation::{for_tests::init_standalone_coin_loop, InitStandaloneCoinReq}; From 902c0e39cc90fcbb4a48a23fbdbbec07834fbf4e Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 13 May 2024 21:13:50 +0500 Subject: [PATCH 68/71] refactor init_fee_estimator --- .../coins/rpc_command/get_estimated_fees.rs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 0e74bc3d8e..80b1c7b7cc 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -169,34 +169,35 @@ impl FeeEstimatorState { } else { Default::default() }; - match fee_estimator_conf { - FeeEstimatorConf::Simple | FeeEstimatorConf::Provider => match coin_type { - EthCoinType::Eth => { - let fee_estimator_ctx = AsyncMutex::new(FeeEstimatorContext { - estimated_fees: Default::default(), - abort_handler: AsyncMutex::new(None), - }); - let fee_estimator_state = if matches!(fee_estimator_conf, FeeEstimatorConf::Simple) { - FeeEstimatorState::Simple(fee_estimator_ctx) - } else { - FeeEstimatorState::Provider(fee_estimator_ctx) - }; - Ok(Arc::new(fee_estimator_state)) - }, - EthCoinType::Erc20 { platform, .. } | EthCoinType::Nft { platform, .. } => { - let platform_coin = lp_coinfind_or_err(ctx, platform).await; - match platform_coin { - Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.platform_fee_estimator_state.clone()), - _ => Ok(Arc::new(FeeEstimatorState::PlatformCoinRequired)), - } - }, + match (fee_estimator_conf, coin_type) { + (FeeEstimatorConf::Simple, EthCoinType::Eth) => { + let fee_estimator_state = FeeEstimatorState::Simple(FeeEstimatorContext::new()); + Ok(Arc::new(fee_estimator_state)) }, - FeeEstimatorConf::NotConfigured => Ok(Arc::new(FeeEstimatorState::CoinNotSupported)), + (FeeEstimatorConf::Provider, EthCoinType::Eth) => { + let fee_estimator_state = FeeEstimatorState::Provider(FeeEstimatorContext::new()); + Ok(Arc::new(fee_estimator_state)) + }, + (_, EthCoinType::Erc20 { platform, .. }) | (_, EthCoinType::Nft { platform, .. }) => { + let platform_coin = lp_coinfind_or_err(ctx, platform).await; + match platform_coin { + Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.platform_fee_estimator_state.clone()), + _ => Ok(Arc::new(FeeEstimatorState::PlatformCoinRequired)), + } + }, + (FeeEstimatorConf::NotConfigured, _) => Ok(Arc::new(FeeEstimatorState::CoinNotSupported)), } } } impl FeeEstimatorContext { + fn new() -> AsyncMutex { + AsyncMutex::new(FeeEstimatorContext { + estimated_fees: Default::default(), + abort_handler: AsyncMutex::new(None), + }) + } + /// Fee estimation update period in secs, basically equals to eth blocktime const fn get_refresh_interval() -> f64 { 15.0 } From 20d16d99d0f92e3b138f6689721e515155563227 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 17 May 2024 22:08:29 +0500 Subject: [PATCH 69/71] fix review notes --- mm2src/coins/eth.rs | 3 ++- mm2src/coins/eth/eip1559_gas_fee.rs | 9 ++++----- mm2src/coins/eth/web3_transport/mod.rs | 1 - mm2src/coins/rpc_command/get_estimated_fees.rs | 9 ++------- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index c63f72369e..cd0e9f0d61 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -321,6 +321,8 @@ impl PayForGasOption { } impl TryFrom for PayForGasOption { + type Error = MmError; + fn try_from(param: PayForGasParams) -> Result { match param { PayForGasParams::Legacy(legacy) => Ok(Self::Legacy(LegacyGasPrice { @@ -332,7 +334,6 @@ impl TryFrom for PayForGasOption { })), } } - type Error = MmError; } type GasDetails = (U256, PayForGasOption); diff --git a/mm2src/coins/eth/eip1559_gas_fee.rs b/mm2src/coins/eth/eip1559_gas_fee.rs index 2f7a69ba8f..4d33781f39 100644 --- a/mm2src/coins/eth/eip1559_gas_fee.rs +++ b/mm2src/coins/eth/eip1559_gas_fee.rs @@ -13,7 +13,6 @@ use url::Url; use web3::types::BlockNumber; pub(crate) use gas_api::BlocknativeGasApiCaller; -#[allow(unused_imports)] pub(crate) use gas_api::InfuraGasApiCaller; use gas_api::{BlocknativeBlockPricesResponse, InfuraFeePerGas}; @@ -101,6 +100,8 @@ pub struct FeePerGasEstimated { } impl TryFrom for FeePerGasEstimated { + type Error = MmError; + fn try_from(infura_fees: InfuraFeePerGas) -> Result { Ok(Self { base_fee: wei_from_gwei_decimal!(&infura_fees.estimated_base_fee)?, @@ -129,11 +130,11 @@ impl TryFrom for FeePerGasEstimated { priority_fee_trend: infura_fees.priority_fee_trend, }) } - - type Error = MmError; } impl TryFrom for FeePerGasEstimated { + type Error = MmError; + fn try_from(block_prices: BlocknativeBlockPricesResponse) -> Result { if block_prices.block_prices.is_empty() { return Ok(FeePerGasEstimated::default()); @@ -178,8 +179,6 @@ impl TryFrom for FeePerGasEstimated { priority_fee_trend: String::default(), }) } - - type Error = MmError; } /// Simple priority fee per gas estimator based on fee history diff --git a/mm2src/coins/eth/web3_transport/mod.rs b/mm2src/coins/eth/web3_transport/mod.rs index 6614d77073..dcbdf6ef90 100644 --- a/mm2src/coins/eth/web3_transport/mod.rs +++ b/mm2src/coins/eth/web3_transport/mod.rs @@ -128,7 +128,6 @@ pub struct FeeHistoryResult { pub oldest_block: U256, #[serde(rename = "baseFeePerGas")] pub base_fee_per_gas: Vec, - #[serde(rename = "gasUsedRatio")] pub gas_used_ratio: Vec, #[serde(rename = "reward")] diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index 80b1c7b7cc..b62e572756 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -62,6 +62,8 @@ pub struct FeePerGasEstimatedExt { } impl TryFrom for FeePerGasEstimatedExt { + type Error = MmError; + fn try_from(fees: FeePerGasEstimated) -> Result { Ok(Self { base_fee: wei_to_gwei_decimal!(fees.base_fee)?, @@ -89,8 +91,6 @@ impl TryFrom for FeePerGasEstimatedExt { units: EstimationUnits::Gwei, }) } - - type Error = MmError; } #[derive(Debug, Display, Serialize, SerializeErrorType)] @@ -289,11 +289,6 @@ pub struct FeeEstimatorStartStopResponse { result: String, } -impl FeeEstimatorStartStopResponse { - #[allow(dead_code)] - pub fn get_result(&self) -> &str { &self.result } -} - pub type FeeEstimatorStartStopResult = Result>; /// Rpc request to get latest estimated fee per gas From a7852746400f2bdd5e675b0bbd373e9444b05def Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 17 May 2024 22:14:27 +0500 Subject: [PATCH 70/71] move eip1559 swap policy methods to a trait --- mm2src/coins/eth.rs | 20 ++++++----- mm2src/coins/lightning.rs | 8 ++--- mm2src/coins/lp_coins.rs | 38 +++++++++++++++------ mm2src/coins/qrc20.rs | 12 ++++--- mm2src/coins/sia.rs | 18 ++++------ mm2src/coins/solana.rs | 18 ++++------ mm2src/coins/solana/spl.rs | 19 ++++------- mm2src/coins/tendermint/tendermint_coin.rs | 8 ++--- mm2src/coins/tendermint/tendermint_token.rs | 19 ++++------- mm2src/coins/test_coin.rs | 10 ++---- mm2src/coins/utxo/bch.rs | 16 ++++----- mm2src/coins/utxo/qtum.rs | 6 +--- mm2src/coins/utxo/slp.rs | 20 +++++------ mm2src/coins/utxo/utxo_standard.rs | 22 +++++------- mm2src/coins/z_coin.rs | 20 +++++------ mm2src/mm2_main/src/lp_swap.rs | 2 +- 16 files changed, 114 insertions(+), 142 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index cd0e9f0d61..dfdac1304a 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -47,9 +47,9 @@ use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawTaskHandleShar use crate::rpc_command::{account_balance, get_new_address, init_account_balance, init_create_account, init_scan_for_new_addresses}; use crate::{coin_balance, scan_for_new_addresses_impl, BalanceResult, CoinWithDerivationMethod, DerivationMethod, - DexFee, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, PayForGasParams, PrivKeyPolicy, - RefundMakerPaymentArgs, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, ToBytes, - ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; + DexFee, Eip1559Ops, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, PayForGasParams, + PrivKeyPolicy, RefundMakerPaymentArgs, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, + ToBytes, ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; @@ -5712,12 +5712,6 @@ impl MmCoin for EthCoin { tokens.remove(ticker); }; } - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { self.swap_txfee_policy.lock().unwrap().clone() } - - fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy) { - *self.swap_txfee_policy.lock().unwrap() = swap_txfee_policy - } } pub trait TryToAddress { @@ -7018,3 +7012,11 @@ pub fn pubkey_from_extended(extended_pubkey: &Secp256k1ExtendedPublicKey) -> Pub pubkey_uncompressed.as_mut().copy_from_slice(&serialized[1..]); pubkey_uncompressed } + +impl Eip1559Ops for EthCoin { + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { self.swap_txfee_policy.lock().unwrap().clone() } + + fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy) { + *self.swap_txfee_policy.lock().unwrap() = swap_txfee_policy + } +} diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index d74b8f42dc..2feb7ef014 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -21,8 +21,8 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, - SwapTxFeePolicy, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - Transaction, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxMarshalingErr, + TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, + TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, @@ -1472,8 +1472,4 @@ impl MmCoin for LightningCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.platform.abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index baf14643b3..78490e74ed 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -2431,12 +2431,14 @@ pub struct SwapTxFeePolicyRequest { pub enum SwapTxFeePolicyError { #[from_stringify("CoinFindError")] NoSuchCoin(String), + #[display(fmt = "eip-1559 policy is not supported for coin {}", _0)] + NotSupported(String), } impl HttpStatusCode for SwapTxFeePolicyError { fn status_code(&self) -> StatusCode { match self { - SwapTxFeePolicyError::NoSuchCoin { .. } => StatusCode::BAD_REQUEST, + SwapTxFeePolicyError::NoSuchCoin(_) | SwapTxFeePolicyError::NotSupported(_) => StatusCode::BAD_REQUEST, } } } @@ -3324,12 +3326,6 @@ pub trait MmCoin: /// For Handling the removal/deactivation of token on platform coin deactivation. fn on_token_deactivated(&self, ticker: &str); - - /// Return swap transaction fee policy - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy; - - /// set swap transaction fee policy - fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy); } /// The coin futures spawner. It's used to spawn futures that can be aborted immediately or after a timeout @@ -5362,17 +5358,39 @@ fn coins_conf_check(ctx: &MmArc, coins_en: &Json, ticker: &str, req: Option<&Jso Ok(()) } +#[async_trait] +pub trait Eip1559Ops { + /// Return swap transaction fee policy + fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy; + + /// set swap transaction fee policy + fn set_swap_transaction_fee_policy(&self, swap_txfee_policy: SwapTxFeePolicy); +} + /// Get eip 1559 transaction fee per gas policy (low, medium, high) set for the coin pub async fn get_swap_transaction_fee_policy(ctx: MmArc, req: SwapTxFeePolicyRequest) -> SwapTxFeePolicyResult { let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; - Ok(coin.get_swap_transaction_fee_policy()) + match coin { + MmCoinEnum::EthCoin(eth_coin) => Ok(eth_coin.get_swap_transaction_fee_policy()), + MmCoinEnum::Qrc20Coin(qrc20_coin) => Ok(qrc20_coin.get_swap_transaction_fee_policy()), + _ => MmError::err(SwapTxFeePolicyError::NotSupported(req.coin)), + } } /// Set eip 1559 transaction fee per gas policy (low, medium, high) pub async fn set_swap_transaction_fee_policy(ctx: MmArc, req: SwapTxFeePolicyRequest) -> SwapTxFeePolicyResult { let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; - coin.set_swap_transaction_fee_policy(req.swap_tx_fee_policy); - Ok(coin.get_swap_transaction_fee_policy()) + match coin { + MmCoinEnum::EthCoin(eth_coin) => { + eth_coin.set_swap_transaction_fee_policy(req.swap_tx_fee_policy); + Ok(eth_coin.get_swap_transaction_fee_policy()) + }, + MmCoinEnum::Qrc20Coin(qrc20_coin) => { + qrc20_coin.set_swap_transaction_fee_policy(req.swap_tx_fee_policy); + Ok(qrc20_coin.get_swap_transaction_fee_policy()) + }, + _ => MmError::err(SwapTxFeePolicyError::NotSupported(req.coin)), + } } /// Checks addresses that either had empty transaction history last time we checked or has not been checked before. diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 5574918068..944d4130a5 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -17,7 +17,7 @@ use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, Broadca UtxoFromLegacyReqErr, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom, UTXO_LOCK}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, - DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, + DexFee, Eip1559Ops, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, @@ -1505,10 +1505,6 @@ impl MmCoin for Qrc20Coin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } pub fn qrc20_swap_id(time_lock: u32, secret_hash: &[u8]) -> Vec { @@ -1688,3 +1684,9 @@ fn transfer_event_from_log(log: &LogEntry) -> Result SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } + + fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} +} diff --git a/mm2src/coins/sia.rs b/mm2src/coins/sia.rs index ac39939343..446a507070 100644 --- a/mm2src/coins/sia.rs +++ b/mm2src/coins/sia.rs @@ -5,13 +5,13 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicy, RawTransactionResult, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, - SignatureResult, SpendPaymentArgs, SwapTxFeePolicy, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidatePaymentResult, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, + ValidatePaymentFut, ValidatePaymentInput, ValidatePaymentResult, ValidateWatcherSpendInput, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, + WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; use futures::{FutureExt, TryFutureExt}; @@ -283,10 +283,6 @@ impl MmCoin for SiaCoin { fn on_disabled(&self) -> Result<(), AbortedError> { Ok(()) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } // TODO Alright - Dummy values for these functions allow minimal functionality to produce signatures diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 1a6b06741d..2a454fd90c 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -9,13 +9,13 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, - SpendPaymentArgs, SwapTxFeePolicy, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionData, TransactionDetails, TransactionFut, TransactionResult, - TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, + TransactionData, TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -805,8 +805,4 @@ impl MmCoin for SolanaCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 6f1020c4b5..7bc720e5e7 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -7,14 +7,13 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPayment PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, - SignatureResult, SolanaCoin, SpendPaymentArgs, SwapTxFeePolicy, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionData, TransactionDetails, TransactionFut, - TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, - WithdrawResult}; + SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionData, TransactionDetails, TransactionFut, TransactionResult, + TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bincode::serialize; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; @@ -598,8 +597,4 @@ impl MmCoin for SplToken { fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 15e77b98fd..d62a14cd12 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -20,8 +20,8 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, - SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, TakerSwapMakerCoin, ToBytes, - TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionData, + SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, ToBytes, TradeFee, + TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionData, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, @@ -2287,10 +2287,6 @@ impl MmCoin for TendermintCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 13c231de03..8b9fba4489 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -12,14 +12,13 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, - TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, - TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest}; + SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionErr, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use crate::{DexFee, MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; use async_trait::async_trait; use bitcrypto::sha256; @@ -727,8 +726,4 @@ impl MmCoin for TendermintToken { fn on_disabled(&self) -> Result<(), AbortedError> { self.abortable_system.abort_all() } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 1fad252d54..c077bf60bd 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -10,9 +10,9 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay ParseCoinAssocTypes, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionResult, RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, - SignatureResult, SpendPaymentArgs, SwapTxFeePolicy, TakerCoinSwapOpsV2, TakerSwapMakerCoin, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, - TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + SignatureResult, SpendPaymentArgs, TakerCoinSwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, + TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, + TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, VerificationResult, @@ -408,10 +408,6 @@ impl MmCoin for TestCoin { fn on_disabled(&self) -> Result<(), AbortedError> { Ok(()) } fn on_token_deactivated(&self, _ticker: &str) { () } - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } pub struct TestPubkey {} diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 0ec228a49f..2b94135933 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -19,12 +19,12 @@ use crate::{coin_balance, BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSen PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, - TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, + TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, + WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; @@ -1358,10 +1358,6 @@ impl MmCoin for BchCoin { tokens.remove(ticker); }; } - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index f09649adce..c226abe1cb 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -28,7 +28,7 @@ use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithD NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, StakingInfosFut, SwapOps, SwapTxFeePolicy, + SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, @@ -993,10 +993,6 @@ impl MmCoin for QtumCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 302fd058c9..beb59f962f 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -19,14 +19,14 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, - SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, TakerSwapMakerCoin, TradeFee, - TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionData, - TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxFeeDetails, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentInput, ValidateWatcherSpendInput, - VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; + SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TakerSwapMakerCoin, TradeFee, TradePreimageError, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionData, TransactionDetails, + TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use base64::engine::general_purpose::STANDARD; use base64::Engine; @@ -1908,10 +1908,6 @@ impl MmCoin for SlpToken { fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 4910d2a676..03c889d790 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -29,15 +29,15 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva RefundFundingSecretArgs, RefundMakerPaymentArgs, RefundPaymentArgs, RefundResult, SearchForFundingSpendErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, - SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, - SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, - TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, - ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, - ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationResult, - WaitForHTLCTxSpendArgs, WaitForTakerPaymentSpendError, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; + SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, + TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, + TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, + ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WaitForTakerPaymentSpendError, + WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use futures::{FutureExt, TryFutureExt}; use mm2_metrics::MetricsArc; @@ -1006,10 +1006,6 @@ impl MmCoin for UtxoStandardCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index b92afd5ac2..d9a416d204 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -32,14 +32,14 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, - TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, - TransactionData, TransactionDetails, TransactionEnum, TransactionFut, TransactionResult, TxFeeDetails, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; + SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, + TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionData, + TransactionDetails, TransactionEnum, TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateWatcherSpendInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -1783,10 +1783,6 @@ impl MmCoin for ZCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} - - fn get_swap_transaction_fee_policy(&self) -> SwapTxFeePolicy { SwapTxFeePolicy::Unsupported } - - fn set_swap_transaction_fee_policy(&self, _swap_txfee_policy: SwapTxFeePolicy) {} } #[async_trait] diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 1988d5181e..81c0fd932a 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1831,7 +1831,7 @@ pub fn generate_secret() -> Result<[u8; 32], rand::Error> { Ok(sec) } -/// Add refund fee to calculate maximul available balance for a swap (including possible refund) +/// Add refund fee to calculate maximum available balance for a swap (including possible refund) pub(crate) const INCLUDE_REFUND_FEE: bool = true; /// Do not add refund fee to calculate fee needed only to make a successful swap pub(crate) const NO_REFUND_FEE: bool = false; From 1cbf2f2f5839e95bef157fe340e47da6b9208956 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 17 May 2024 22:16:19 +0500 Subject: [PATCH 71/71] return swap policy docker tests (lost in merge) --- .../tests/docker_tests/eth_docker_tests.rs | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index f2ae5ab735..1109e9f159 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -5,10 +5,11 @@ use super::docker_tests_common::{random_secp256k1_secret, ERC1155_TEST_ABI, ERC7 use bitcrypto::{dhash160, sha256}; use coins::eth::{checksum_address, eth_addr_to_hex, eth_coin_from_conf_and_request, EthCoin, ERC20_ABI}; use coins::nft::nft_structs::{Chain, ContractType, NftInfo}; -use coins::{CoinProtocol, CoinWithDerivationMethod, ConfirmPaymentInput, DerivationMethod, FoundSwapTxSpend, - MakerNftSwapOpsV2, MarketCoinOps, NftSwapInfo, ParseCoinAssocTypes, PrivKeyBuildPolicy, RefundPaymentArgs, - SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, - SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, ToBytes, Transaction, ValidateNftMakerPaymentArgs}; +use coins::{CoinProtocol, CoinWithDerivationMethod, ConfirmPaymentInput, DerivationMethod, Eip1559Ops, + FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, NftSwapInfo, ParseCoinAssocTypes, PrivKeyBuildPolicy, + RefundPaymentArgs, SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, + SpendNftMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, + Transaction, ValidateNftMakerPaymentArgs}; use common::{block_on, now_sec}; use crypto::Secp256k1Secret; use ethereum_types::U256; @@ -60,6 +61,7 @@ pub fn erc721_contract() -> Address { unsafe { GETH_ERC721_CONTRACT } } pub fn erc1155_contract() -> Address { unsafe { GETH_ERC1155_CONTRACT } } fn wait_for_confirmation(tx_hash: H256) { + thread::sleep(Duration::from_millis(2000)); loop { match block_on(GETH_WEB3.eth().transaction_receipt(tx_hash)) { Ok(Some(r)) => match r.block_hash { @@ -392,9 +394,9 @@ pub fn fill_eth_erc20_with_private_key(priv_key: Secp256k1Secret) { fill_erc20(my_address, U256::from(10000000000u64)); } -#[test] -fn send_and_refund_eth_maker_payment() { +fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let eth_coin = eth_coin_with_random_privkey(swap_contract()); + eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy); let time_lock = now_sec() - 100; let other_pubkey = &[ @@ -467,10 +469,20 @@ fn send_and_refund_eth_maker_payment() { } #[test] -fn send_and_spend_eth_maker_payment() { +fn send_and_refund_eth_maker_payment_internal_gas_policy() { + send_and_refund_eth_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_refund_eth_maker_payment_priority_fee() { send_and_refund_eth_maker_payment_impl(SwapTxFeePolicy::Medium); } + +fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let maker_eth_coin = eth_coin_with_random_privkey(swap_contract()); let taker_eth_coin = eth_coin_with_random_privkey(swap_contract()); + maker_eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy.clone()); + taker_eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy); + let time_lock = now_sec() + 1000; let maker_pubkey = maker_eth_coin.derive_htlc_pubkey(&[]); let taker_pubkey = taker_eth_coin.derive_htlc_pubkey(&[]); @@ -542,8 +554,16 @@ fn send_and_spend_eth_maker_payment() { } #[test] -fn send_and_refund_erc20_maker_payment() { +fn send_and_spend_eth_maker_payment_internal_gas_policy() { + send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_spend_eth_maker_payment_priority_fee() { send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Medium); } + +fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let erc20_coin = erc20_coin_with_random_privkey(swap_contract()); + erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy); let time_lock = now_sec() - 100; let other_pubkey = &[ @@ -617,10 +637,22 @@ fn send_and_refund_erc20_maker_payment() { } #[test] -fn send_and_spend_erc20_maker_payment() { +fn send_and_refund_erc20_maker_payment_internal_gas_policy() { + send_and_refund_erc20_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_refund_erc20_maker_payment_priority_fee() { + send_and_refund_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); +} + +fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let maker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); let taker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); + maker_erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy.clone()); + taker_erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy); + let time_lock = now_sec() + 1000; let maker_pubkey = maker_erc20_coin.derive_htlc_pubkey(&[]); let taker_pubkey = taker_erc20_coin.derive_htlc_pubkey(&[]); @@ -691,6 +723,16 @@ fn send_and_spend_erc20_maker_payment() { assert_eq!(expected, search_tx); } +#[test] +fn send_and_spend_erc20_maker_payment_internal_gas_policy() { + send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Internal); +} + +#[test] +fn send_and_spend_erc20_maker_payment_priority_fee() { + send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); +} + #[test] fn send_and_spend_erc721_maker_payment() { // TODO: Evaluate implementation strategy — either employing separate contracts for maker and taker