From bcc73c9f09e92c15b492e4924c6c2c3d6b446646 Mon Sep 17 00:00:00 2001 From: Karel Moravec Date: Mon, 25 Nov 2024 17:57:12 +0100 Subject: [PATCH] fix: improve Gas Fee Estimation by Integrating Filecoin's EIP-1559-Compatible APIs (#1182) --- .../manager/evm/gas_estimator_middleware.rs | 160 ++++++++++++++ ipc/provider/src/manager/evm/manager.rs | 209 +++++------------- ipc/provider/src/manager/evm/mod.rs | 1 + 3 files changed, 211 insertions(+), 159 deletions(-) create mode 100644 ipc/provider/src/manager/evm/gas_estimator_middleware.rs diff --git a/ipc/provider/src/manager/evm/gas_estimator_middleware.rs b/ipc/provider/src/manager/evm/gas_estimator_middleware.rs new file mode 100644 index 000000000..e1499b863 --- /dev/null +++ b/ipc/provider/src/manager/evm/gas_estimator_middleware.rs @@ -0,0 +1,160 @@ +// Copyright 2022-2024 Protocol Labs +// SPDX-License-Identifier: MIT + +use async_trait::async_trait; +use ethers::{ + core::types::{transaction::eip2718::TypedTransaction, BlockId, U256}, + providers::{Middleware, MiddlewareError, PendingTransaction, ProviderError}, +}; +use serde_json::json; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Eip1559GasEstimatorError { + #[error("{0}")] + MiddlewareError(M::Error), + #[error("{0}")] + FailedToEstimateGas(String), +} + +impl Eip1559GasEstimatorError { + pub fn failed_to_estimate_gas_not_supported() -> Self { + Eip1559GasEstimatorError::FailedToEstimateGas( + "Only EIP-1559 transactions are supported".to_string(), + ) + } +} + +impl MiddlewareError for Eip1559GasEstimatorError { + type Inner = M::Error; + + fn from_err(src: M::Error) -> Self { + Eip1559GasEstimatorError::MiddlewareError(src) + } + + fn as_inner(&self) -> Option<&Self::Inner> { + match self { + Eip1559GasEstimatorError::MiddlewareError(e) => Some(e), + _ => None, + } + } +} + +pub struct GasFeeEstimate { + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, +} + +#[derive(Debug)] +pub struct Eip1559GasEstimatorMiddleware { + inner: M, +} + +impl Eip1559GasEstimatorMiddleware { + pub fn new(inner: M) -> Self { + Self { inner } + } + + pub async fn max_priority_fee_per_gas(&self) -> Result { + self.inner + .provider() + .request("eth_maxPriorityFeePerGas", json!([])) + .await + } + + pub async fn base_fee_per_gas(&self) -> Result { + let latest_block = self + .inner + .provider() + .get_block(ethers::types::BlockNumber::Latest) + .await? + .ok_or_else(|| ProviderError::CustomError("Latest block not found".to_string()))?; + + latest_block + .base_fee_per_gas + .ok_or_else(|| ProviderError::CustomError("EIP-1559 not activated".to_string())) + } + + pub async fn estimate_gas_fees(&self) -> Result> { + let max_priority_fee_per_gas = self + .max_priority_fee_per_gas() + .await + .map_err(|e| Eip1559GasEstimatorError::FailedToEstimateGas(e.to_string()))?; + let base_fee_per_gas = self + .base_fee_per_gas() + .await + .map_err(|e| Eip1559GasEstimatorError::FailedToEstimateGas(e.to_string()))?; + + // Buffer the base fee by multiplying by 2 to account for potential cumulative increases. + let base_fee_per_gas_surged = base_fee_per_gas * 2; + let max_fee_per_gas = max_priority_fee_per_gas + base_fee_per_gas_surged; + + Ok(GasFeeEstimate { + max_priority_fee_per_gas, + max_fee_per_gas, + }) + } +} + +#[async_trait] +impl Middleware for Eip1559GasEstimatorMiddleware { + type Error = Eip1559GasEstimatorError; + type Provider = M::Provider; + type Inner = M; + + fn inner(&self) -> &M { + &self.inner + } + + /// Fills the transaction with EIP-1559 gas fees. + async fn fill_transaction( + &self, + tx: &mut TypedTransaction, + block: Option, + ) -> Result<(), Self::Error> { + if let TypedTransaction::Eip1559(inner) = tx { + let gas_fees = self.estimate_gas_fees().await?; + + // Set the gas fees directly on the transaction. + let tx_req = inner + .clone() + .max_fee_per_gas(gas_fees.max_fee_per_gas) + .max_priority_fee_per_gas(gas_fees.max_priority_fee_per_gas); + + *tx = TypedTransaction::Eip1559(tx_req); + } else { + return Err(Eip1559GasEstimatorError::failed_to_estimate_gas_not_supported()); + } + + // Delegate to the inner middleware for filling remaining transaction fields. + self.inner() + .fill_transaction(tx, block) + .await + .map_err(Eip1559GasEstimatorError::MiddlewareError) + } + + /// Sends a transaction with EIP-1559 gas fees. + async fn send_transaction + Send + Sync>( + &self, + tx: T, + block: Option, + ) -> Result, Self::Error> { + let mut tx = tx.into(); + + // Automatically fill EIP-1559 gas fees if they are not already set. + if let TypedTransaction::Eip1559(ref mut inner) = tx { + if inner.max_fee_per_gas.is_none() || inner.max_priority_fee_per_gas.is_none() { + // Populate missing gas fees with `fill_transaction`. + self.fill_transaction(&mut tx, block).await?; + } + } else { + return Err(Eip1559GasEstimatorError::failed_to_estimate_gas_not_supported()); + } + + // Proceed to send the transaction with the inner middleware. + self.inner() + .send_transaction(tx, block) + .await + .map_err(Eip1559GasEstimatorError::MiddlewareError) + } +} diff --git a/ipc/provider/src/manager/evm/manager.rs b/ipc/provider/src/manager/evm/manager.rs index ac9b0766f..fe393242f 100644 --- a/ipc/provider/src/manager/evm/manager.rs +++ b/ipc/provider/src/manager/evm/manager.rs @@ -29,6 +29,7 @@ use crate::manager::subnet::{ BottomUpCheckpointRelayer, GetBlockHashResult, SubnetGenesisInfo, TopDownFinalityQuery, TopDownQueryPayload, }; + use crate::manager::{EthManager, SubnetManager}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; @@ -38,8 +39,9 @@ use ethers::prelude::k256::ecdsa::SigningKey; use ethers::prelude::{Signer, SignerMiddleware}; use ethers::providers::{Authorization, Http, Middleware, Provider}; use ethers::signers::{LocalWallet, Wallet}; -use ethers::types::{BlockId, Eip1559TransactionRequest, ValueOrArray, I256, U256}; +use ethers::types::{Eip1559TransactionRequest, ValueOrArray, U256}; +use super::gas_estimator_middleware::Eip1559GasEstimatorMiddleware; use fvm_shared::clock::ChainEpoch; use fvm_shared::{address::Address, econ::TokenAmount}; use ipc_api::checkpoint::{ @@ -53,7 +55,8 @@ use ipc_wallet::{EthKeyAddress, EvmKeyStore, PersistentKeyStore}; use num_traits::ToPrimitive; use std::result; -pub type DefaultSignerMiddleware = SignerMiddleware, Wallet>; +pub type SignerWithFeeEstimatorMiddleware = + Eip1559GasEstimatorMiddleware, Wallet>>; /// Default polling time used by the Ethers provider to check for pending /// transactions and events. Default is 7, and for our child subnets we @@ -282,7 +285,7 @@ impl SubnetManager for EthSubnetManager { tracing::info!("creating subnet on evm with params: {params:?}"); - let signer = self.get_signer(&from)?; + let signer = self.get_signer_with_fee_estimator(&from)?; let signer = Arc::new(signer); let registry_contract = register_subnet_facet::RegisterSubnetFacet::new( self.ipc_contract_info.registry_addr, @@ -290,8 +293,7 @@ impl SubnetManager for EthSubnetManager { ); let call = - call_with_premium_and_pending_block(signer, registry_contract.new_subnet_actor(params)) - .await?; + extend_call_with_pending_block(registry_contract.new_subnet_actor(params)).await?; // TODO: Edit call to get estimate premium let pending_tx = call.send().await?; // We need the retry to parse the deployment event. At the time of this writing, it's a bug @@ -342,17 +344,14 @@ impl SubnetManager for EthSubnetManager { "interacting with evm subnet contract: {address:} with collateral: {collateral:}" ); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); let mut txn = contract.join(ethers::types::Bytes::from(pub_key), U256::from(collateral)); txn = self.handle_txn_token(&subnet, txn, collateral, 0).await?; - let txn = call_with_premium_and_pending_block(signer, txn).await?; - - // Use the pending state to get the nonce because there could have been a pre-fund. Best would be to use this for everything. - let txn = txn.block(BlockId::Number(ethers::types::BlockNumber::Pending)); + let txn = extend_call_with_pending_block(txn).await?; let pending_tx = txn.send().await?; let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; @@ -368,14 +367,14 @@ impl SubnetManager for EthSubnetManager { let address = contract_address_from_subnet(&subnet)?; tracing::info!("interacting with evm subnet contract: {address:} with balance: {balance:}"); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); let mut txn = contract.pre_fund(U256::from(balance)); txn = self.handle_txn_token(&subnet, txn, 0, balance).await?; - let txn = call_with_premium_and_pending_block(signer, txn).await?; + let txn = extend_call_with_pending_block(txn).await?; txn.send().await?; Ok(()) @@ -395,11 +394,11 @@ impl SubnetManager for EthSubnetManager { .to_u128() .ok_or_else(|| anyhow!("invalid pre-release amount"))?; - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); - call_with_premium_and_pending_block(signer, contract.pre_release(amount.into())) + extend_call_with_pending_block(contract.pre_release(amount.into())) .await? .send() .await? @@ -419,14 +418,14 @@ impl SubnetManager for EthSubnetManager { "interacting with evm subnet contract: {address:} with collateral: {collateral:}" ); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); let mut txn = contract.stake(U256::from(collateral)); txn = self.handle_txn_token(&subnet, txn, collateral, 0).await?; - let txn = call_with_premium_and_pending_block(signer, txn).await?; + let txn = extend_call_with_pending_block(txn).await?; txn.send().await?.await?; @@ -449,12 +448,11 @@ impl SubnetManager for EthSubnetManager { "interacting with evm subnet contract: {address:} with collateral: {collateral:}" ); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); - let txn = call_with_premium_and_pending_block(signer, contract.unstake(collateral.into())) - .await?; + let txn = extend_call_with_pending_block(contract.unstake(collateral.into())).await?; txn.send().await?.await?; Ok(()) @@ -464,11 +462,11 @@ impl SubnetManager for EthSubnetManager { let address = contract_address_from_subnet(&subnet)?; tracing::info!("leaving evm subnet: {subnet:} at contract: {address:}"); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); - call_with_premium_and_pending_block(signer, contract.leave()) + extend_call_with_pending_block(contract.leave()) .await? .send() .await? @@ -481,11 +479,11 @@ impl SubnetManager for EthSubnetManager { let address = contract_address_from_subnet(&subnet)?; tracing::info!("kill evm subnet: {subnet:} at contract: {address:}"); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); - call_with_premium_and_pending_block(signer, contract.kill()) + extend_call_with_pending_block(contract.kill()) .await? .send() .await? @@ -522,11 +520,11 @@ impl SubnetManager for EthSubnetManager { let address = contract_address_from_subnet(&subnet)?; tracing::info!("claim collateral evm subnet: {subnet:} at contract: {address:}"); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let contract = subnet_actor_reward_facet::SubnetActorRewardFacet::new(address, signer.clone()); - call_with_premium_and_pending_block(signer, contract.claim()) + extend_call_with_pending_block(contract.claim()) .await? .send() .await? @@ -555,7 +553,7 @@ impl SubnetManager for EthSubnetManager { let evm_subnet_id = gateway_manager_facet::SubnetID::try_from(&subnet)?; tracing::debug!("evm subnet id to fund: {evm_subnet_id:?}"); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let gateway_contract = gateway_manager_facet::GatewayManagerFacet::new( self.ipc_contract_info.gateway_addr, signer.clone(), @@ -566,7 +564,7 @@ impl SubnetManager for EthSubnetManager { gateway_manager_facet::FvmAddress::try_from(to)?, ); txn.tx.set_value(value); - let txn = call_with_premium_and_pending_block(signer, txn).await?; + let txn = extend_call_with_pending_block(txn).await?; let pending_tx = txn.send().await?; let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; @@ -584,7 +582,7 @@ impl SubnetManager for EthSubnetManager { let value = fil_amount_to_eth_amount(&amount)?; - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let subnet_supply_source = self.get_subnet_supply_source(&subnet).await?; if subnet_supply_source.kind != AssetKind::ERC20 { @@ -600,7 +598,7 @@ impl SubnetManager for EthSubnetManager { let token_contract = IERC20::new(token_address, signer.clone()); let txn = token_contract.approve(self.ipc_contract_info.gateway_addr, value); - let txn = call_with_premium_and_pending_block(signer, txn).await?; + let txn = extend_call_with_pending_block(txn).await?; let pending_tx = txn.send().await?; let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; @@ -621,7 +619,7 @@ impl SubnetManager for EthSubnetManager { let value = fil_amount_to_eth_amount(&amount)?; let evm_subnet_id = gateway_manager_facet::SubnetID::try_from(&subnet)?; - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let gateway_contract = gateway_manager_facet::GatewayManagerFacet::new( self.ipc_contract_info.gateway_addr, signer.clone(), @@ -632,7 +630,7 @@ impl SubnetManager for EthSubnetManager { gateway_manager_facet::FvmAddress::try_from(to)?, value, ); - let txn = call_with_premium_and_pending_block(signer, txn).await?; + let txn = extend_call_with_pending_block(txn).await?; let pending_tx = txn.send().await?; let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; @@ -655,14 +653,14 @@ impl SubnetManager for EthSubnetManager { tracing::info!("release with evm gateway contract: {gateway_addr:} with value: {value:}"); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let gateway_contract = gateway_manager_facet::GatewayManagerFacet::new( self.ipc_contract_info.gateway_addr, signer.clone(), ); let mut txn = gateway_contract.release(gateway_manager_facet::FvmAddress::try_from(to)?); txn.tx.set_value(value); - let txn = call_with_premium_and_pending_block(signer, txn).await?; + let txn = extend_call_with_pending_block(txn).await?; let pending_tx = txn.send().await?; let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; @@ -688,7 +686,7 @@ impl SubnetManager for EthSubnetManager { tracing::info!("propagate postbox evm gateway contract: {gateway_addr:} with message key: {postbox_msg_key:?}"); - let signer = Arc::new(self.get_signer(&from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let gateway_contract = gateway_messenger_facet::GatewayMessengerFacet::new( self.ipc_contract_info.gateway_addr, signer.clone(), @@ -697,7 +695,7 @@ impl SubnetManager for EthSubnetManager { let mut key = [0u8; 32]; key.copy_from_slice(&postbox_msg_key); - call_with_premium_and_pending_block(signer, gateway_contract.propagate(key)) + extend_call_with_pending_block(gateway_contract.propagate(key)) .await? .send() .await?; @@ -707,13 +705,10 @@ impl SubnetManager for EthSubnetManager { /// Send value between two addresses in a subnet async fn send_value(&self, from: Address, to: Address, amount: TokenAmount) -> Result<()> { - let signer = Arc::new(self.get_signer(&from)?); - let (fee, fee_cap) = premium_estimation(signer.clone()).await?; + let signer = Arc::new(self.get_signer_with_fee_estimator(&from)?); let tx = Eip1559TransactionRequest::new() .to(payload_to_evm_address(to.payload())?) - .value(fil_to_eth_amount(&amount)?) - .max_priority_fee_per_gas(fee) - .max_fee_per_gas(fee_cap); + .value(fil_to_eth_amount(&amount)?); let tx_pending = signer.send_transaction(tx, None).await?; @@ -832,11 +827,11 @@ impl SubnetManager for EthSubnetManager { return Err(anyhow!("wrong format for bootstrap endpoint")); } - let signer = Arc::new(self.get_signer(from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); - call_with_premium_and_pending_block(signer, contract.add_bootstrap_node(endpoint)) + extend_call_with_pending_block(contract.add_bootstrap_node(endpoint)) .await? .send() .await? @@ -888,7 +883,7 @@ impl SubnetManager for EthSubnetManager { let address = contract_address_from_subnet(subnet)?; tracing::info!("interacting with evm subnet contract: {address:}"); - let signer = Arc::new(self.get_signer(from)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(from)?); let contract = subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); @@ -913,7 +908,7 @@ impl SubnetManager for EthSubnetManager { tracing::debug!("from address: {:?}", from); let call = contract.set_federated_power(addresses, pubkeys, power_u256); - let txn = call_with_premium_and_pending_block(signer, call).await?; + let txn = extend_call_with_pending_block(call).await?; let pending_tx = txn.send().await?; let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; block_number_from_receipt(receipt) @@ -1083,7 +1078,10 @@ impl EthSubnetManager { /// Get the ethers singer instance. /// We use filecoin addresses throughout our whole code-base /// and translate them to evm addresses when relevant. - fn get_signer(&self, addr: &Address) -> Result { + fn get_signer_with_fee_estimator( + &self, + addr: &Address, + ) -> Result { // convert to its underlying eth address let addr = payload_to_evm_address(addr.payload())?; let keystore = self.keystore()?; @@ -1094,8 +1092,10 @@ impl EthSubnetManager { let wallet = LocalWallet::from_bytes(private_key.private_key())? .with_chain_id(self.ipc_contract_info.chain_id); + use super::gas_estimator_middleware::Eip1559GasEstimatorMiddleware; + let signer = SignerMiddleware::new(self.ipc_contract_info.provider.clone(), wallet); - Ok(signer) + Ok(Eip1559GasEstimatorMiddleware::new(signer)) } pub fn from_subnet_with_wallet_store( @@ -1172,13 +1172,13 @@ impl BottomUpCheckpointRelayer for EthSubnetManager { let checkpoint = subnet_actor_checkpointing_facet::BottomUpCheckpoint::try_from(checkpoint)?; - let signer = Arc::new(self.get_signer(submitter)?); + let signer = Arc::new(self.get_signer_with_fee_estimator(submitter)?); let contract = subnet_actor_checkpointing_facet::SubnetActorCheckpointingFacet::new( address, signer.clone(), ); let call = contract.submit_checkpoint(checkpoint, signatories, signatures); - let call = call_with_premium_and_pending_block(signer, call).await?; + let call = extend_call_with_pending_block(call).await?; let pending_tx = call.send().await?; let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?; @@ -1277,126 +1277,17 @@ impl BottomUpCheckpointRelayer for EthSubnetManager { Ok(epoch as ChainEpoch) } } - /// Takes a `FunctionCall` input and returns a new instance with an estimated optimal `gas_premium`. /// The function also uses the pending block number to help retrieve the latest nonce /// via `get_transaction_count` with the `pending` parameter. -pub(crate) async fn call_with_premium_and_pending_block( - signer: Arc, +pub(crate) async fn extend_call_with_pending_block( call: ethers_contract::FunctionCall, ) -> Result> where B: std::borrow::Borrow, M: ethers::abi::Detokenize, { - let (max_priority_fee_per_gas, _) = premium_estimation(signer).await?; - Ok(call - .gas_price(max_priority_fee_per_gas) - .block(ethers::types::BlockNumber::Pending)) -} - -/// Returns an estimation of an optimal `gas_premium` and `gas_fee_cap` -/// for a transaction considering the average premium, base_fee and reward percentile from -/// past blocks -/// This is adaptation of ethers' `eip1559_default_estimator`: -/// https://github.com/gakonst/ethers-rs/blob/5dcd3b7e754174448f9a8cbfc0523896609629f9/ethers-core/src/utils/mod.rs#L476 -async fn premium_estimation( - signer: Arc, -) -> Result<(ethers::types::U256, ethers::types::U256)> { - let base_fee_per_gas = signer - .get_block(ethers::types::BlockNumber::Latest) - .await? - .ok_or_else(|| anyhow!("Latest block not found"))? - .base_fee_per_gas - .ok_or_else(|| anyhow!("EIP-1559 not activated"))?; - - let fee_history = signer - .fee_history( - ethers::utils::EIP1559_FEE_ESTIMATION_PAST_BLOCKS, - ethers::types::BlockNumber::Latest, - &[ethers::utils::EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], - ) - .await?; - - let max_priority_fee_per_gas = estimate_priority_fee(fee_history.reward); //overestimate? - let potential_max_fee = base_fee_surged(base_fee_per_gas); - let max_fee_per_gas = if max_priority_fee_per_gas > potential_max_fee { - max_priority_fee_per_gas + potential_max_fee - } else { - potential_max_fee - }; - - Ok((max_priority_fee_per_gas, max_fee_per_gas)) -} - -/// Implementation borrowed from -/// https://github.com/gakonst/ethers-rs/blob/ethers-v2.0.8/ethers-core/src/utils/mod.rs#L582 -/// Refer to the implementation for unit tests -fn base_fee_surged(base_fee_per_gas: U256) -> U256 { - if base_fee_per_gas <= U256::from(40_000_000_000u64) { - base_fee_per_gas * 2 - } else if base_fee_per_gas <= U256::from(100_000_000_000u64) { - base_fee_per_gas * 16 / 10 - } else if base_fee_per_gas <= U256::from(200_000_000_000u64) { - base_fee_per_gas * 14 / 10 - } else { - base_fee_per_gas * 12 / 10 - } -} - -/// Implementation borrowed from -/// https://github.com/gakonst/ethers-rs/blob/ethers-v2.0.8/ethers-core/src/utils/mod.rs#L536 -/// Refer to the implementation for unit tests -fn estimate_priority_fee(rewards: Vec>) -> U256 { - let mut rewards: Vec = rewards - .iter() - .map(|r| r[0]) - .filter(|r| *r > U256::zero()) - .collect(); - if rewards.is_empty() { - return U256::zero(); - } - if rewards.len() == 1 { - return rewards[0]; - } - // Sort the rewards as we will eventually take the median. - rewards.sort(); - - // A copy of the same vector is created for convenience to calculate percentage change - // between subsequent fee values. - let mut rewards_copy = rewards.clone(); - rewards_copy.rotate_left(1); - - let mut percentage_change: Vec = rewards - .iter() - .zip(rewards_copy.iter()) - .map(|(a, b)| { - let a = I256::try_from(*a).expect("priority fee overflow"); - let b = I256::try_from(*b).expect("priority fee overflow"); - ((b - a) * 100) / a - }) - .collect(); - percentage_change.pop(); - - // Fetch the max of the percentage change, and that element's index. - let max_change = percentage_change.iter().max().unwrap(); - let max_change_index = percentage_change - .iter() - .position(|&c| c == *max_change) - .unwrap(); - - // If we encountered a big change in fees at a certain position, then consider only - // the values >= it. - let values = if *max_change >= ethers::utils::EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE.into() - && (max_change_index >= (rewards.len() / 2)) - { - rewards[max_change_index..].to_vec() - } else { - rewards - }; - - // Return the median. - values[values.len() / 2] + Ok(call.block(ethers::types::BlockNumber::Pending)) } /// Get the block number from the transaction receipt diff --git a/ipc/provider/src/manager/evm/mod.rs b/ipc/provider/src/manager/evm/mod.rs index f51b5359a..973c34cf9 100644 --- a/ipc/provider/src/manager/evm/mod.rs +++ b/ipc/provider/src/manager/evm/mod.rs @@ -1,6 +1,7 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: MIT +mod gas_estimator_middleware; mod manager; use async_trait::async_trait;