mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-30 09:38:24 -05:00
fix: estimate gas (#7133)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@@ -35,7 +35,10 @@ use tracing::trace;
|
||||
|
||||
// Gas per transaction not creating a contract.
|
||||
const MIN_TRANSACTION_GAS: u64 = 21_000u64;
|
||||
const MIN_CREATE_GAS: u64 = 53_000u64;
|
||||
/// Allowed error ratio for gas estimation
|
||||
/// Taken from Geth's implementation in order to pass the hive tests
|
||||
/// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/internal/ethapi/api.go#L56>
|
||||
const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015;
|
||||
|
||||
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
|
||||
where
|
||||
@@ -262,7 +265,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let (res, env) = ethres?;
|
||||
let (mut res, mut env) = ethres?;
|
||||
match res.result {
|
||||
ExecutionResult::Success { .. } => {
|
||||
// succeeded
|
||||
@@ -285,14 +288,33 @@ where
|
||||
}
|
||||
|
||||
// at this point we know the call succeeded but want to find the _best_ (lowest) gas the
|
||||
// transaction succeeds with. we find this by doing a binary search over the
|
||||
// transaction succeeds with. We find this by doing a binary search over the
|
||||
// possible range NOTE: this is the gas the transaction used, which is less than the
|
||||
// transaction requires to succeed
|
||||
let gas_used = res.result.gas_used();
|
||||
// the lowest value is capped by the gas it takes for a transfer
|
||||
let mut lowest_gas_limit =
|
||||
if env.tx.transact_to.is_create() { MIN_CREATE_GAS } else { MIN_TRANSACTION_GAS };
|
||||
let mut highest_gas_limit: u64 = highest_gas_limit.try_into().unwrap_or(u64::MAX);
|
||||
// the lowest value is capped by the gas used by the unconstrained transaction
|
||||
let mut lowest_gas_limit = gas_used.saturating_sub(1);
|
||||
|
||||
let gas_refund = match res.result {
|
||||
ExecutionResult::Success { gas_refunded, .. } => gas_refunded,
|
||||
_ => 0,
|
||||
};
|
||||
// As stated in Geth, there is a good change that the transaction will pass if we set the
|
||||
// gas limit to the execution gas used plus the gas refund, so we check this first
|
||||
// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L135
|
||||
let optimistic_gas_limit = (gas_used + gas_refund) * 64 / 63;
|
||||
if optimistic_gas_limit < highest_gas_limit {
|
||||
env.tx.gas_limit = optimistic_gas_limit;
|
||||
(res, env) = transact(&mut db, env)?;
|
||||
update_estimated_gas_range(
|
||||
res.result,
|
||||
optimistic_gas_limit,
|
||||
&mut highest_gas_limit,
|
||||
&mut lowest_gas_limit,
|
||||
)?;
|
||||
};
|
||||
|
||||
// pick a point that's close to the estimated gas
|
||||
let mut mid_gas_limit = std::cmp::min(
|
||||
gas_used * 3,
|
||||
@@ -303,7 +325,15 @@ where
|
||||
|
||||
// binary search
|
||||
while (highest_gas_limit - lowest_gas_limit) > 1 {
|
||||
let mut env = env.clone();
|
||||
// An estimation error is allowed once the current gas limit range used in the binary
|
||||
// search is small enough (less than 1.5% of the highest gas limit)
|
||||
// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L152
|
||||
if (highest_gas_limit - lowest_gas_limit) as f64 / (highest_gas_limit as f64) <
|
||||
ESTIMATE_GAS_ERROR_RATIO
|
||||
{
|
||||
break
|
||||
};
|
||||
|
||||
env.tx.gas_limit = mid_gas_limit;
|
||||
let ethres = transact(&mut db, env);
|
||||
|
||||
@@ -317,37 +347,18 @@ where
|
||||
|
||||
// new midpoint
|
||||
mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64;
|
||||
(_, env) = ethres?;
|
||||
continue
|
||||
}
|
||||
|
||||
let (res, _) = ethres?;
|
||||
match res.result {
|
||||
ExecutionResult::Success { .. } => {
|
||||
// cap the highest gas limit with succeeding gas limit
|
||||
highest_gas_limit = mid_gas_limit;
|
||||
}
|
||||
ExecutionResult::Revert { .. } => {
|
||||
// increase the lowest gas limit
|
||||
lowest_gas_limit = mid_gas_limit;
|
||||
}
|
||||
ExecutionResult::Halt { reason, .. } => {
|
||||
match reason {
|
||||
HaltReason::OutOfGas(_) | HaltReason::InvalidFEOpcode => {
|
||||
// either out of gas or invalid opcode can be thrown dynamically if
|
||||
// gasLeft is too low, so we treat this as `out of gas`, we know this
|
||||
// call succeeds with a higher gaslimit. common usage of invalid opcode in openzeppelin <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/94697be8a3f0dfcd95dfb13ffbd39b5973f5c65d/contracts/metatx/ERC2771Forwarder.sol#L360-L367>
|
||||
(res, env) = ethres?;
|
||||
update_estimated_gas_range(
|
||||
res.result,
|
||||
mid_gas_limit,
|
||||
&mut highest_gas_limit,
|
||||
&mut lowest_gas_limit,
|
||||
)?;
|
||||
|
||||
// increase the lowest gas limit
|
||||
lowest_gas_limit = mid_gas_limit;
|
||||
}
|
||||
err => {
|
||||
// these should be unreachable because we know the transaction succeeds,
|
||||
// but we consider these cases an error
|
||||
return Err(RpcInvalidTransactionError::EvmHalt(err).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// new midpoint
|
||||
mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64;
|
||||
}
|
||||
@@ -464,3 +475,42 @@ where
|
||||
ExecutionResult::Halt { reason, .. } => RpcInvalidTransactionError::EvmHalt(reason).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the highest and lowest gas limits for binary search
|
||||
/// based on the result of the execution
|
||||
#[inline]
|
||||
fn update_estimated_gas_range(
|
||||
result: ExecutionResult,
|
||||
tx_gas_limit: u64,
|
||||
highest_gas_limit: &mut u64,
|
||||
lowest_gas_limit: &mut u64,
|
||||
) -> EthResult<()> {
|
||||
match result {
|
||||
ExecutionResult::Success { .. } => {
|
||||
// cap the highest gas limit with succeeding gas limit
|
||||
*highest_gas_limit = tx_gas_limit;
|
||||
}
|
||||
ExecutionResult::Revert { .. } => {
|
||||
// increase the lowest gas limit
|
||||
*lowest_gas_limit = tx_gas_limit;
|
||||
}
|
||||
ExecutionResult::Halt { reason, .. } => {
|
||||
match reason {
|
||||
HaltReason::OutOfGas(_) | HaltReason::InvalidFEOpcode => {
|
||||
// either out of gas or invalid opcode can be thrown dynamically if
|
||||
// gasLeft is too low, so we treat this as `out of gas`, we know this
|
||||
// call succeeds with a higher gaslimit. common usage of invalid opcode in openzeppelin <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/94697be8a3f0dfcd95dfb13ffbd39b5973f5c65d/contracts/metatx/ERC2771Forwarder.sol#L360-L367>
|
||||
|
||||
// increase the lowest gas limit
|
||||
*lowest_gas_limit = tx_gas_limit;
|
||||
}
|
||||
err => {
|
||||
// these should be unreachable because we know the transaction succeeds,
|
||||
// but we consider these cases an error
|
||||
return Err(RpcInvalidTransactionError::EvmHalt(err).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user