diff --git a/rust/chains/hyperlane-ethereum/src/tx.rs b/rust/chains/hyperlane-ethereum/src/tx.rs index 85e0edef03..f578a15f94 100644 --- a/rust/chains/hyperlane-ethereum/src/tx.rs +++ b/rust/chains/hyperlane-ethereum/src/tx.rs @@ -5,10 +5,17 @@ use std::time::Duration; use ethers::{ abi::Detokenize, prelude::{NameOrAddress, TransactionReceipt}, + providers::ProviderError, types::Eip1559TransactionRequest, }; use ethers_contract::builders::ContractCall; -use ethers_core::types::BlockNumber; +use ethers_core::{ + types::{BlockNumber, U256 as EthersU256}, + utils::{ + eip1559_default_estimator, EIP1559_FEE_ESTIMATION_PAST_BLOCKS, + EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE, + }, +}; use hyperlane_core::{utils::bytes_to_hex, ChainCommunicationError, ChainResult, H256, U256}; use tracing::{error, info}; @@ -84,10 +91,22 @@ where .saturating_add(U256::from(GAS_ESTIMATE_BUFFER).into()) .into() }; - let Ok((max_fee, max_priority_fee)) = provider.estimate_eip1559_fees(None).await else { + + let Ok((base_fee, max_fee, max_priority_fee)) = estimate_eip1559_fees(provider, None).await + else { // Is not EIP 1559 chain return Ok(tx.gas(gas_limit)); }; + + // If the base fee is zero, just treat the chain as a non-EIP-1559 chain. + // This is useful for BSC, where the base fee is zero, there's a minimum gas price + // generally enforced by nodes of 3 gwei, but EIP 1559 estimation suggests a priority + // fee lower than 3 gwei because of privileged transactions being included by block + // producers that have a lower priority fee. + if base_fee.is_zero() { + return Ok(tx.gas(gas_limit)); + } + // Is EIP 1559 chain let mut request = Eip1559TransactionRequest::new(); if let Some(from) = tx.tx.from() { @@ -109,6 +128,46 @@ where Ok(eip_1559_tx.gas(gas_limit)) } +type FeeEstimator = fn(EthersU256, Vec>) -> (EthersU256, EthersU256); + +/// Pretty much a copy of the logic in ethers-rs (https://github.com/hyperlane-xyz/ethers-rs/blob/c9ced035628da59376c369be035facda1648577a/ethers-providers/src/provider.rs#L478) +/// but returns the base fee as well as the max fee and max priority fee. +/// Gets a heuristic recommendation of max fee per gas and max priority fee per gas for +/// EIP-1559 compatible transactions. +async fn estimate_eip1559_fees( + provider: Arc, + estimator: Option, +) -> ChainResult<(EthersU256, EthersU256, EthersU256)> +where + M: Middleware + 'static, +{ + let base_fee_per_gas = provider + .get_block(BlockNumber::Latest) + .await + .map_err(ChainCommunicationError::from_other)? + .ok_or_else(|| ProviderError::CustomError("Latest block not found".into()))? + .base_fee_per_gas + .ok_or_else(|| ProviderError::CustomError("EIP-1559 not activated".into()))?; + + let fee_history = provider + .fee_history( + EIP1559_FEE_ESTIMATION_PAST_BLOCKS, + BlockNumber::Latest, + &[EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ) + .await + .map_err(ChainCommunicationError::from_other)?; + + // use the provided fee estimator function, or fallback to the default implementation. + let (max_fee_per_gas, max_priority_fee_per_gas) = if let Some(es) = estimator { + es(base_fee_per_gas, fee_history.reward) + } else { + eip1559_default_estimator(base_fee_per_gas, fee_history.reward) + }; + + Ok((base_fee_per_gas, max_fee_per_gas, max_priority_fee_per_gas)) +} + pub(crate) async fn call_with_lag( call: ethers::contract::builders::ContractCall, provider: &M, diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index c6477c5ea4..b13cb6ca1b 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -137,7 +137,7 @@ const hyperlane: RootAgentConfig = { docker: { repo, // Includes Cosmos block-by-block indexing. - tag: 'a72c3cf-20240314-173418', + tag: '39df4ca-20240321-100543', }, gasPaymentEnforcement: [ // Temporary measure to ensure all inEVM warp route messages are delivered -