The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/rust/chains/hyperlane-ethereum/src/tx.rs

133 lines
3.9 KiB

use std::num::NonZeroU64;
use std::sync::Arc;
use std::time::Duration;
use ethers::{
abi::Detokenize,
prelude::{NameOrAddress, TransactionReceipt},
types::Eip1559TransactionRequest,
};
use ethers_contract::builders::ContractCall;
use ethers_core::types::BlockNumber;
Use Polygon gas oracle (#2965) ### Description After investigating #2959, I found the following this is a known problem with Polygon, explanation here https://github.com/ethers-io/ethers.js/issues/2828#issuecomment-1283014250 Fun fact Asa looked into this once https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/771 Here's a discussion it in Foundry, which I found hoping that ethers-rs folks had ran into this before https://github.com/foundry-rs/foundry/issues/1703 Foundry fixed this by using the Polygon gas station oracle which seems to be recommended path https://github.com/foundry-rs/foundry/pull/3368/files#diff-c89a4bbf7a90da118dcf00c5fe70eba78f8e5d95662bb5f039a353113e95042bR205 There's actually a polygon ethers middleware for this https://docs.rs/ethers/latest/ethers/middleware/gas_oracle/polygon/struct.Polygon.html So I (originally) borrowed this code from Foundry https://github.com/foundry-rs/foundry/blob/master/crates/common/src/provider.rs#L254-L290 Changed to use Middlewares This also means we can remove our existing janky Polygon logic ### Drive-by changes <!-- Are there any minor or drive-by changes also included? --> ### Related issues <!-- - Fixes #[issue number here] --> ### Backward compatibility <!-- Are these changes backward compatible? Are there any infrastructure implications, e.g. changes that would prohibit deploying older commits using this infra tooling? Yes/No --> ### Testing <!-- What kind of testing have these changes undergone? None/Manual/Unit Tests -->
1 year ago
use hyperlane_core::{utils::fmt_bytes, ChainCommunicationError, ChainResult, H256, U256};
use tracing::{error, info};
use crate::Middleware;
/// An amount of gas to add to the estimated gas
const GAS_ESTIMATE_BUFFER: u32 = 50000;
/// Dispatches a transaction, logs the tx id, and returns the result
pub(crate) async fn report_tx<M, D>(tx: ContractCall<M, D>) -> ChainResult<TransactionReceipt>
where
M: Middleware + 'static,
D: Detokenize,
{
let data = tx
.tx
.data()
.map(|b| fmt_bytes(b))
.unwrap_or_else(|| "None".into());
let to = tx
.tx
.to()
.cloned()
.unwrap_or_else(|| NameOrAddress::Address(Default::default()));
info!(?to, %data, "Dispatching transaction");
// We can set the gas higher here!
let dispatch_fut = tx.send();
let dispatched = dispatch_fut.await?;
let tx_hash: H256 = (*dispatched).into();
info!(?to, %data, ?tx_hash, "Dispatched tx");
match tokio::time::timeout(Duration::from_secs(300), dispatched).await {
// all good
Ok(Ok(Some(receipt))) => {
info!(?tx_hash, "confirmed transaction");
Ok(receipt)
}
// ethers-rs will return None if it can no longer poll for the tx in the mempool
Ok(Ok(None)) => Err(ChainCommunicationError::TransactionDropped(tx_hash)),
// Received error, pass it through
Ok(Err(x)) => {
error!(?tx_hash, error = ?x, "encountered error when waiting for receipt");
Err(x.into())
}
// Timed out
Err(x) => {
error!(?tx_hash, error = ?x, "waiting for receipt timed out");
Err(ChainCommunicationError::TransactionTimeout())
}
}
}
/// Populates the gas limit and price for a transaction
pub(crate) async fn fill_tx_gas_params<M, D>(
tx: ContractCall<M, D>,
tx_gas_limit: Option<U256>,
provider: Arc<M>,
) -> ChainResult<ContractCall<M, D>>
where
M: Middleware + 'static,
D: Detokenize,
{
let gas_limit = if let Some(gas_limit) = tx_gas_limit {
gas_limit
} else {
tx.estimate_gas()
.await?
.saturating_add(U256::from(GAS_ESTIMATE_BUFFER).into())
.into()
};
let Ok((max_fee, max_priority_fee)) = provider.estimate_eip1559_fees(None).await else {
// Is not EIP 1559 chain
return Ok(tx.gas(gas_limit));
};
// Is EIP 1559 chain
let mut request = Eip1559TransactionRequest::new();
if let Some(from) = tx.tx.from() {
request = request.from(*from);
}
if let Some(to) = tx.tx.to() {
request = request.to(to.clone());
}
if let Some(data) = tx.tx.data() {
request = request.data(data.clone());
}
if let Some(value) = tx.tx.value() {
request = request.value(*value);
}
request = request.max_fee_per_gas(max_fee);
request = request.max_priority_fee_per_gas(max_priority_fee);
let mut eip_1559_tx = tx;
eip_1559_tx.tx = ethers::types::transaction::eip2718::TypedTransaction::Eip1559(request);
Ok(eip_1559_tx.gas(gas_limit))
}
pub(crate) async fn call_with_lag<M, T>(
call: ethers::contract::builders::ContractCall<M, T>,
provider: &M,
maybe_lag: Option<NonZeroU64>,
) -> ChainResult<ethers::contract::builders::ContractCall<M, T>>
where
M: Middleware + 'static,
T: Detokenize,
{
if let Some(lag) = maybe_lag {
let fixed_block_number: BlockNumber = provider
.get_block_number()
.await
.map_err(ChainCommunicationError::from_other)?
.saturating_sub(lag.get().into())
.into();
Ok(call.block(fixed_block_number))
} else {
Ok(call)
}
}