Skip to content

Commit 04f9ae4

Browse files
authored
feat: add eth_estimateGas support (#4763)
1 parent 456121e commit 04f9ae4

File tree

7 files changed

+444
-129
lines changed

7 files changed

+444
-129
lines changed

crates/edr_evm/src/transaction/pending.rs

+81-76
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl PendingTransaction {
5050
return Err(TransactionCreationError::ContractMissingData);
5151
}
5252

53-
let initial_cost = Self::initial_cost(spec_id, &transaction);
53+
let initial_cost = initial_cost(spec_id, &transaction);
5454
if transaction.gas_limit() < initial_cost {
5555
return Err(TransactionCreationError::InsufficientGas {
5656
initial_gas_cost: U256::from(initial_cost),
@@ -69,6 +69,11 @@ impl PendingTransaction {
6969
&self.caller
7070
}
7171

72+
/// The minimum gas required to include the transaction in a block.
73+
pub fn initial_cost(&self, spec_id: SpecId) -> u64 {
74+
initial_cost(spec_id, &self.transaction)
75+
}
76+
7277
/// Returns the inner [`SignedTransaction`]
7378
pub fn transaction(&self) -> &SignedTransaction {
7479
&self.transaction
@@ -78,81 +83,6 @@ impl PendingTransaction {
7883
pub fn into_inner(self) -> (SignedTransaction, Address) {
7984
(self.transaction, self.caller)
8085
}
81-
82-
fn initial_cost(spec_id: SpecId, transaction: &SignedTransaction) -> u64 {
83-
let access_list: Option<Vec<(Address, Vec<U256>)>> =
84-
transaction.access_list().cloned().map(Into::into);
85-
86-
match spec_id {
87-
SpecId::FRONTIER | SpecId::FRONTIER_THAWING => initial_tx_gas::<FrontierSpec>(
88-
transaction.data(),
89-
transaction.kind() == TransactionKind::Create,
90-
access_list.as_ref().map_or(&[], |access_list| access_list),
91-
),
92-
SpecId::HOMESTEAD | SpecId::DAO_FORK => initial_tx_gas::<HomesteadSpec>(
93-
transaction.data(),
94-
transaction.kind() == TransactionKind::Create,
95-
access_list.as_ref().map_or(&[], |access_list| access_list),
96-
),
97-
SpecId::TANGERINE => initial_tx_gas::<TangerineSpec>(
98-
transaction.data(),
99-
transaction.kind() == TransactionKind::Create,
100-
access_list.as_ref().map_or(&[], |access_list| access_list),
101-
),
102-
SpecId::SPURIOUS_DRAGON => initial_tx_gas::<SpuriousDragonSpec>(
103-
transaction.data(),
104-
transaction.kind() == TransactionKind::Create,
105-
access_list.as_ref().map_or(&[], |access_list| access_list),
106-
),
107-
SpecId::BYZANTIUM => initial_tx_gas::<ByzantiumSpec>(
108-
transaction.data(),
109-
transaction.kind() == TransactionKind::Create,
110-
access_list.as_ref().map_or(&[], |access_list| access_list),
111-
),
112-
SpecId::PETERSBURG | SpecId::CONSTANTINOPLE => initial_tx_gas::<PetersburgSpec>(
113-
transaction.data(),
114-
transaction.kind() == TransactionKind::Create,
115-
access_list.as_ref().map_or(&[], |access_list| access_list),
116-
),
117-
SpecId::ISTANBUL | SpecId::MUIR_GLACIER => initial_tx_gas::<IstanbulSpec>(
118-
transaction.data(),
119-
transaction.kind() == TransactionKind::Create,
120-
access_list.as_ref().map_or(&[], |access_list| access_list),
121-
),
122-
SpecId::BERLIN => initial_tx_gas::<BerlinSpec>(
123-
transaction.data(),
124-
transaction.kind() == TransactionKind::Create,
125-
access_list.as_ref().map_or(&[], |access_list| access_list),
126-
),
127-
SpecId::LONDON | SpecId::ARROW_GLACIER | SpecId::GRAY_GLACIER => {
128-
initial_tx_gas::<LondonSpec>(
129-
transaction.data(),
130-
transaction.kind() == TransactionKind::Create,
131-
access_list.as_ref().map_or(&[], |access_list| access_list),
132-
)
133-
}
134-
SpecId::MERGE => initial_tx_gas::<MergeSpec>(
135-
transaction.data(),
136-
transaction.kind() == TransactionKind::Create,
137-
access_list.as_ref().map_or(&[], |access_list| access_list),
138-
),
139-
SpecId::SHANGHAI => initial_tx_gas::<ShanghaiSpec>(
140-
transaction.data(),
141-
transaction.kind() == TransactionKind::Create,
142-
access_list.as_ref().map_or(&[], |access_list| access_list),
143-
),
144-
SpecId::CANCUN => initial_tx_gas::<LatestSpec>(
145-
transaction.data(),
146-
transaction.kind() == TransactionKind::Create,
147-
access_list.as_ref().map_or(&[], |access_list| access_list),
148-
),
149-
SpecId::LATEST => initial_tx_gas::<LatestSpec>(
150-
transaction.data(),
151-
transaction.kind() == TransactionKind::Create,
152-
access_list.as_ref().map_or(&[], |access_list| access_list),
153-
),
154-
}
155-
}
15686
}
15787

15888
impl Deref for PendingTransaction {
@@ -281,3 +211,78 @@ impl From<PendingTransaction> for TxEnv {
281211
}
282212
}
283213
}
214+
215+
fn initial_cost(spec_id: SpecId, transaction: &SignedTransaction) -> u64 {
216+
let access_list: Option<Vec<(Address, Vec<U256>)>> =
217+
transaction.access_list().cloned().map(Into::into);
218+
219+
match spec_id {
220+
SpecId::FRONTIER | SpecId::FRONTIER_THAWING => initial_tx_gas::<FrontierSpec>(
221+
transaction.data(),
222+
transaction.kind() == TransactionKind::Create,
223+
access_list.as_ref().map_or(&[], |access_list| access_list),
224+
),
225+
SpecId::HOMESTEAD | SpecId::DAO_FORK => initial_tx_gas::<HomesteadSpec>(
226+
transaction.data(),
227+
transaction.kind() == TransactionKind::Create,
228+
access_list.as_ref().map_or(&[], |access_list| access_list),
229+
),
230+
SpecId::TANGERINE => initial_tx_gas::<TangerineSpec>(
231+
transaction.data(),
232+
transaction.kind() == TransactionKind::Create,
233+
access_list.as_ref().map_or(&[], |access_list| access_list),
234+
),
235+
SpecId::SPURIOUS_DRAGON => initial_tx_gas::<SpuriousDragonSpec>(
236+
transaction.data(),
237+
transaction.kind() == TransactionKind::Create,
238+
access_list.as_ref().map_or(&[], |access_list| access_list),
239+
),
240+
SpecId::BYZANTIUM => initial_tx_gas::<ByzantiumSpec>(
241+
transaction.data(),
242+
transaction.kind() == TransactionKind::Create,
243+
access_list.as_ref().map_or(&[], |access_list| access_list),
244+
),
245+
SpecId::PETERSBURG | SpecId::CONSTANTINOPLE => initial_tx_gas::<PetersburgSpec>(
246+
transaction.data(),
247+
transaction.kind() == TransactionKind::Create,
248+
access_list.as_ref().map_or(&[], |access_list| access_list),
249+
),
250+
SpecId::ISTANBUL | SpecId::MUIR_GLACIER => initial_tx_gas::<IstanbulSpec>(
251+
transaction.data(),
252+
transaction.kind() == TransactionKind::Create,
253+
access_list.as_ref().map_or(&[], |access_list| access_list),
254+
),
255+
SpecId::BERLIN => initial_tx_gas::<BerlinSpec>(
256+
transaction.data(),
257+
transaction.kind() == TransactionKind::Create,
258+
access_list.as_ref().map_or(&[], |access_list| access_list),
259+
),
260+
SpecId::LONDON | SpecId::ARROW_GLACIER | SpecId::GRAY_GLACIER => {
261+
initial_tx_gas::<LondonSpec>(
262+
transaction.data(),
263+
transaction.kind() == TransactionKind::Create,
264+
access_list.as_ref().map_or(&[], |access_list| access_list),
265+
)
266+
}
267+
SpecId::MERGE => initial_tx_gas::<MergeSpec>(
268+
transaction.data(),
269+
transaction.kind() == TransactionKind::Create,
270+
access_list.as_ref().map_or(&[], |access_list| access_list),
271+
),
272+
SpecId::SHANGHAI => initial_tx_gas::<ShanghaiSpec>(
273+
transaction.data(),
274+
transaction.kind() == TransactionKind::Create,
275+
access_list.as_ref().map_or(&[], |access_list| access_list),
276+
),
277+
SpecId::CANCUN => initial_tx_gas::<LatestSpec>(
278+
transaction.data(),
279+
transaction.kind() == TransactionKind::Create,
280+
access_list.as_ref().map_or(&[], |access_list| access_list),
281+
),
282+
SpecId::LATEST => initial_tx_gas::<LatestSpec>(
283+
transaction.data(),
284+
transaction.kind() == TransactionKind::Create,
285+
access_list.as_ref().map_or(&[], |access_list| access_list),
286+
),
287+
}
288+
}

crates/edr_provider/src/data.rs

+98-46
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
mod account;
2+
mod call;
3+
mod gas;
24
mod inspector;
35

46
use std::{
@@ -10,7 +12,6 @@ use std::{
1012
};
1113

1214
use edr_eth::{
13-
block::BlobGas,
1415
log::FilterLog,
1516
receipt::BlockReceipt,
1617
remote::{
@@ -28,15 +29,14 @@ use edr_evm::{
2829
},
2930
calculate_next_base_fee,
3031
db::StateRef,
31-
guaranteed_dry_run, mempool, mine_block,
32+
mempool, mine_block,
3233
state::{
3334
AccountModifierFn, IrregularState, StateDiff, StateError, StateOverride, StateOverrides,
3435
SyncState,
3536
},
36-
Account, AccountInfo, BlobExcessGasAndPrice, Block, BlockEnv, Bytecode, CfgEnv,
37-
ExecutionResult, HashMap, HashSet, MemPool, MineBlockResult, MineBlockResultAndState,
38-
MineOrdering, OrderedTransaction, PendingTransaction, RandomHashGenerator, StorageSlot,
39-
SyncBlock, KECCAK_EMPTY,
37+
Account, AccountInfo, Block, Bytecode, CfgEnv, ExecutionResult, HashMap, HashSet, MemPool,
38+
MineBlockResult, MineBlockResultAndState, MineOrdering, OrderedTransaction, PendingTransaction,
39+
RandomHashGenerator, StorageSlot, SyncBlock, TxEnv, KECCAK_EMPTY,
4040
};
4141
use indexmap::IndexMap;
4242
use inspector::EvmInspector;
@@ -46,6 +46,10 @@ use tokio::runtime;
4646

4747
use self::account::{create_accounts, InitialAccounts};
4848
use crate::{
49+
data::{
50+
call::RunCallArgs,
51+
gas::{BinarySearchEstimationArgs, CheckGasLimitArgs},
52+
},
4953
error::TransactionFailure,
5054
filter::{bloom_contains_log_filter, filter_logs, Filter, FilterData, LogFilter},
5155
logger::Logger,
@@ -417,6 +421,79 @@ impl ProviderData {
417421
self.beneficiary
418422
}
419423

424+
/// Estimate the gas cost of a transaction. Matches Hardhat behavior.
425+
pub fn estimate_gas(
426+
&self,
427+
transaction: PendingTransaction,
428+
block_spec: &BlockSpec,
429+
) -> Result<u64, ProviderError> {
430+
let cfg_env = self.create_evm_config(Some(block_spec))?;
431+
// Minimum gas cost that is required for transaction to be included in
432+
// a block
433+
let minimum_cost = transaction.initial_cost(self.spec_id());
434+
let transaction_hash = *transaction.hash();
435+
let tx_env: TxEnv = transaction.into();
436+
437+
let state_overrides = StateOverrides::default();
438+
439+
self.execute_in_block_context(Some(block_spec), |blockchain, block, state| {
440+
let header = block.header();
441+
442+
// Measure the gas used by the transaction with optional limit from call request
443+
// defaulting to block limit. Report errors from initial call as if from
444+
// `eth_call`.
445+
let (mut initial_estimation, _) = call::run_call_and_handle_errors(
446+
RunCallArgs {
447+
blockchain,
448+
header,
449+
state: &state,
450+
state_overrides: &state_overrides,
451+
cfg_env: cfg_env.clone(),
452+
tx_env: tx_env.clone(),
453+
inspector: None,
454+
},
455+
&transaction_hash,
456+
)?;
457+
458+
// Ensure that the initial estimation is at least the minimum cost + 1.
459+
if initial_estimation <= minimum_cost {
460+
initial_estimation = minimum_cost + 1;
461+
}
462+
463+
// Test if the transaction would be successful with the initial estimation
464+
let result = gas::check_gas_limit(CheckGasLimitArgs {
465+
blockchain,
466+
header,
467+
state: &state,
468+
state_overrides: &state_overrides,
469+
cfg_env: cfg_env.clone(),
470+
tx_env: tx_env.clone(),
471+
transaction_hash: &transaction_hash,
472+
gas_limit: initial_estimation,
473+
})?;
474+
475+
// Return the initial estimation if it was successful
476+
if result {
477+
return Ok(initial_estimation);
478+
}
479+
480+
// Correct the initial estimation if the transaction failed with the actually
481+
// used gas limit. This can happen if the execution logic is based
482+
// on the available gas.
483+
gas::binary_search_estimation(BinarySearchEstimationArgs {
484+
blockchain,
485+
header,
486+
state: &state,
487+
state_overrides: &state_overrides,
488+
cfg_env: cfg_env.clone(),
489+
tx_env: tx_env.clone(),
490+
transaction_hash: &transaction_hash,
491+
lower_bound: initial_estimation,
492+
upper_bound: header.gas_limit,
493+
})
494+
})?
495+
}
496+
420497
pub fn gas_price(&self) -> Result<U256, ProviderError> {
421498
const PRE_EIP_1559_GAS_PRICE: u64 = 8_000_000_000;
422499
const SUGGESTED_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000;
@@ -876,52 +953,27 @@ impl ProviderData {
876953
block_spec: Option<&BlockSpec>,
877954
state_overrides: &StateOverrides,
878955
) -> Result<Bytes, ProviderError> {
879-
let cfg = self.create_evm_config(block_spec)?;
956+
let cfg_env = self.create_evm_config(block_spec)?;
880957
let transaction_hash = *transaction.hash();
881-
let transaction = transaction.into();
958+
let tx_env = transaction.into();
882959

883960
self.execute_in_block_context(block_spec, |blockchain, block, state| {
884-
let header = block.header();
885-
let block = BlockEnv {
886-
number: U256::from(header.number),
887-
coinbase: header.beneficiary,
888-
timestamp: U256::from(header.timestamp),
889-
gas_limit: U256::from(header.gas_limit),
890-
basefee: U256::from(0),
891-
difficulty: header.difficulty,
892-
prevrandao: if cfg.spec_id >= SpecId::MERGE {
893-
Some(header.mix_hash)
894-
} else {
895-
None
896-
},
897-
blob_excess_gas_and_price: header
898-
.blob_gas
899-
.as_ref()
900-
.map(|BlobGas { excess_gas, .. }| BlobExcessGasAndPrice::new(*excess_gas)),
901-
};
902-
903961
let mut inspector = self.evm_inspector();
904962

905-
let result = guaranteed_dry_run(
906-
blockchain,
907-
&state,
908-
state_overrides,
909-
cfg,
910-
transaction,
911-
block,
912-
Some(&mut inspector),
913-
)
914-
.map_err(ProviderError::RunTransaction)?;
963+
let (_, output) = call::run_call_and_handle_errors(
964+
RunCallArgs {
965+
blockchain,
966+
header: block.header(),
967+
state: &state,
968+
state_overrides,
969+
cfg_env,
970+
tx_env,
971+
inspector: Some(&mut inspector),
972+
},
973+
&transaction_hash,
974+
)?;
915975

916-
match result.result {
917-
ExecutionResult::Success { output, .. } => Ok(output.into_data()),
918-
ExecutionResult::Revert { output, .. } => {
919-
Err(TransactionFailure::revert(output, transaction_hash).into())
920-
}
921-
ExecutionResult::Halt { reason, .. } => {
922-
Err(TransactionFailure::halt(reason, transaction_hash).into())
923-
}
924-
}
976+
Ok(output)
925977
})?
926978
}
927979

0 commit comments

Comments
 (0)