Skip to content

Commit 219f457

Browse files
authored
fix: use correct hardfork for calls in pre-fork context (#5107)
1 parent e5f048e commit 219f457

File tree

8 files changed

+105
-35
lines changed

8 files changed

+105
-35
lines changed

.changeset/slow-baboons-press.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
@nomicfoundation/edr: path
3+
---
4+
5+
fix: When in fork mode and executing a call in the block immediately preceding the fork, make sure that the hardfork for EVM execution is derived from the block number as opposed to the provider config.

crates/edr_evm/src/block/builder.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,13 @@ where
9090
}
9191
_ => Self::InvalidTransaction(e),
9292
},
93-
EVMError::Header(
94-
InvalidHeader::ExcessBlobGasNotSet | InvalidHeader::PrevrandaoNotSet,
95-
) => unreachable!("error: {error:?}"),
9693
EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e),
9794
EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::BlockHash(e),
95+
// This case is a bug in our codebase for local blockchains, but it can happen that the
96+
// remote returns incorrect block data in which case we should return a custom error.
97+
EVMError::Header(
98+
error @ (InvalidHeader::ExcessBlobGasNotSet | InvalidHeader::PrevrandaoNotSet),
99+
) => Self::Custom(error.to_string()),
98100
EVMError::Custom(error) => Self::Custom(error),
99101
}
100102
}

crates/edr_provider/src/data.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -584,16 +584,16 @@ impl<LoggerErrorT: Debug> ProviderData<LoggerErrorT> {
584584
pub fn debug_trace_call(
585585
&mut self,
586586
transaction: ExecutableTransaction,
587-
block_spec: Option<&BlockSpec>,
587+
block_spec: &BlockSpec,
588588
trace_config: DebugTraceConfig,
589589
) -> Result<DebugTraceResult, ProviderError<LoggerErrorT>> {
590-
let cfg_env = self.create_evm_config(block_spec)?;
590+
let cfg_env = self.create_evm_config(Some(block_spec))?;
591591

592592
let tx_env: TxEnv = transaction.into();
593593

594594
let mut tracer = TracerEip3155::new(trace_config);
595595

596-
self.execute_in_block_context(block_spec, |blockchain, block, state| {
596+
self.execute_in_block_context(Some(block_spec), |blockchain, block, state| {
597597
let result = run_call(RunCallArgs {
598598
blockchain,
599599
header: block.header(),
@@ -1346,15 +1346,15 @@ impl<LoggerErrorT: Debug> ProviderData<LoggerErrorT> {
13461346
pub fn run_call(
13471347
&mut self,
13481348
transaction: ExecutableTransaction,
1349-
block_spec: Option<&BlockSpec>,
1349+
block_spec: &BlockSpec,
13501350
state_overrides: &StateOverrides,
13511351
) -> Result<CallResult, ProviderError<LoggerErrorT>> {
1352-
let cfg_env = self.create_evm_config(block_spec)?;
1352+
let cfg_env = self.create_evm_config(Some(block_spec))?;
13531353
let tx_env = transaction.into();
13541354

13551355
let mut debugger = Debugger::with_mocker(Mocker::new(self.call_override.clone()));
13561356

1357-
self.execute_in_block_context(block_spec, |blockchain, block, state| {
1357+
self.execute_in_block_context(Some(block_spec), |blockchain, block, state| {
13581358
let execution_result = call::run_call(RunCallArgs {
13591359
blockchain,
13601360
header: block.header(),
@@ -1819,6 +1819,9 @@ impl<LoggerErrorT: Debug> ProviderData<LoggerErrorT> {
18191819
Ok(transaction_hash)
18201820
}
18211821

1822+
/// Creates a configuration, taking into the hardfork at the provided
1823+
/// `BlockSpec`. If none is provided, assumes the hardfork for newly
1824+
/// mined blocks.
18221825
fn create_evm_config(
18231826
&self,
18241827
block_spec: Option<&BlockSpec>,
@@ -2817,7 +2820,7 @@ mod tests {
28172820

28182821
let result = fixture.provider_data.run_call(
28192822
pending_transaction,
2820-
None,
2823+
&BlockSpec::latest(),
28212824
&StateOverrides::default(),
28222825
)?;
28232826

@@ -3549,10 +3552,9 @@ mod tests {
35493552
) -> Result<CallResult, ProviderError<Infallible>> {
35503553
let state_overrides = StateOverrides::default();
35513554

3552-
let transaction =
3553-
resolve_call_request(data, request, Some(&block_spec), &state_overrides)?;
3555+
let transaction = resolve_call_request(data, request, &block_spec, &state_overrides)?;
35543556

3555-
data.run_call(transaction, Some(&block_spec), &state_overrides)
3557+
data.run_call(transaction, &block_spec, &state_overrides)
35563558
}
35573559

35583560
const EIP_1559_ACTIVATION_BLOCK: u64 = 12_965_000;

crates/edr_provider/src/requests/debug.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use serde::{Deserialize, Deserializer};
99

1010
use crate::{
1111
data::ProviderData,
12-
requests::{eth::resolve_call_request, validation::validate_call_request},
12+
requests::{
13+
eth::{resolve_block_spec_for_call_request, resolve_call_request},
14+
validation::validate_call_request,
15+
},
1316
ProviderError,
1417
};
1518

@@ -36,17 +39,14 @@ pub fn handle_debug_trace_call<LoggerErrorT: Debug>(
3639
block_spec: Option<BlockSpec>,
3740
config: Option<DebugTraceConfig>,
3841
) -> Result<DebugTraceResult, ProviderError<LoggerErrorT>> {
42+
let block_spec = resolve_block_spec_for_call_request(block_spec);
3943
validate_call_request(data.spec_id(), &call_request, &block_spec)?;
4044

41-
let transaction = resolve_call_request(
42-
data,
43-
call_request,
44-
block_spec.as_ref(),
45-
&StateOverrides::default(),
46-
)?;
45+
let transaction =
46+
resolve_call_request(data, call_request, &block_spec, &StateOverrides::default())?;
4747
data.debug_trace_call(
4848
transaction,
49-
block_spec.as_ref(),
49+
&block_spec,
5050
config.map(Into::into).unwrap_or_default(),
5151
)
5252
}

crates/edr_provider/src/requests/eth/call.rs

+12-7
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ pub fn handle_call_request<LoggerErrorT: Debug>(
2121
block_spec: Option<BlockSpec>,
2222
state_overrides: Option<StateOverrideOptions>,
2323
) -> Result<(Bytes, Trace), ProviderError<LoggerErrorT>> {
24+
let block_spec = resolve_block_spec_for_call_request(block_spec);
2425
validate_call_request(data.spec_id(), &request, &block_spec)?;
2526

2627
let state_overrides =
2728
state_overrides.map_or(Ok(StateOverrides::default()), StateOverrides::try_from)?;
2829

29-
let transaction = resolve_call_request(data, request, block_spec.as_ref(), &state_overrides)?;
30-
let result = data.run_call(transaction.clone(), block_spec.as_ref(), &state_overrides)?;
30+
let transaction = resolve_call_request(data, request, &block_spec, &state_overrides)?;
31+
let result = data.run_call(transaction.clone(), &block_spec, &state_overrides)?;
3132

3233
let spec_id = data.spec_id();
3334
data.logger_mut()
@@ -51,10 +52,14 @@ pub fn handle_call_request<LoggerErrorT: Debug>(
5152
Ok((output, result.trace))
5253
}
5354

55+
pub(crate) fn resolve_block_spec_for_call_request(block_spec: Option<BlockSpec>) -> BlockSpec {
56+
block_spec.unwrap_or_else(BlockSpec::latest)
57+
}
58+
5459
pub(crate) fn resolve_call_request<LoggerErrorT: Debug>(
5560
data: &mut ProviderData<LoggerErrorT>,
5661
request: CallRequest,
57-
block_spec: Option<&BlockSpec>,
62+
block_spec: &BlockSpec,
5863
state_overrides: &StateOverrides,
5964
) -> Result<ExecutableTransaction, ProviderError<LoggerErrorT>> {
6065
resolve_call_request_inner(
@@ -78,7 +83,7 @@ pub(crate) fn resolve_call_request<LoggerErrorT: Debug>(
7883
pub(crate) fn resolve_call_request_inner<LoggerErrorT: Debug>(
7984
data: &mut ProviderData<LoggerErrorT>,
8085
request: CallRequest,
81-
block_spec: Option<&BlockSpec>,
86+
block_spec: &BlockSpec,
8287
state_overrides: &StateOverrides,
8388
default_gas_price_fn: impl FnOnce(
8489
&ProviderData<LoggerErrorT>,
@@ -108,7 +113,7 @@ pub(crate) fn resolve_call_request_inner<LoggerErrorT: Debug>(
108113
let from = from.unwrap_or_else(|| data.default_caller());
109114
let gas_limit = gas.unwrap_or_else(|| data.block_gas_limit());
110115
let input = input.map_or(Bytes::new(), Bytes::from);
111-
let nonce = data.nonce(&from, block_spec, state_overrides)?;
116+
let nonce = data.nonce(&from, Some(block_spec), state_overrides)?;
112117
let value = value.unwrap_or(U256::ZERO);
113118

114119
let transaction = if data.spec_id() < SpecId::LONDON || gas_price.is_some() {
@@ -179,7 +184,7 @@ mod tests {
179184
let resolved = resolve_call_request_inner(
180185
&mut fixture.provider_data,
181186
request,
182-
Some(&BlockSpec::pending()),
187+
&BlockSpec::pending(),
183188
&StateOverrides::default(),
184189
|_data| unreachable!("gas_price is set"),
185190
|_, _, _| unreachable!("gas_price is set"),
@@ -208,7 +213,7 @@ mod tests {
208213
let resolved = resolve_call_request_inner(
209214
&mut fixture.provider_data,
210215
request,
211-
Some(&BlockSpec::pending()),
216+
&BlockSpec::pending(),
212217
&StateOverrides::default(),
213218
|_data| unreachable!("max fees are set"),
214219
|_, max_fee_per_gas, max_priority_fee_per_gas| {

crates/edr_provider/src/requests/eth/gas.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ pub fn handle_estimate_gas<LoggerErrorT: Debug>(
2222
call_request: CallRequest,
2323
block_spec: Option<BlockSpec>,
2424
) -> Result<(U64, Vec<Trace>), ProviderError<LoggerErrorT>> {
25-
validate_call_request(data.spec_id(), &call_request, &block_spec)?;
26-
2725
// Matching Hardhat behavior in defaulting to "pending" instead of "latest" for
2826
// estimate gas.
2927
let block_spec = block_spec.unwrap_or_else(BlockSpec::pending);
3028

29+
validate_call_request(data.spec_id(), &call_request, &block_spec)?;
30+
3131
let transaction =
3232
resolve_estimate_gas_request(data, call_request, &block_spec, &StateOverrides::default())?;
3333

@@ -109,7 +109,7 @@ fn resolve_estimate_gas_request<LoggerErrorT: Debug>(
109109
resolve_call_request_inner(
110110
data,
111111
request,
112-
Some(block_spec),
112+
block_spec,
113113
state_overrides,
114114
ProviderData::gas_price,
115115
|data, max_fee_per_gas, max_priority_fee_per_gas| {

crates/edr_provider/src/requests/validation.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,9 @@ pub fn validate_transaction_spec<LoggerErrorT: Debug>(
155155
pub fn validate_call_request<LoggerErrorT: Debug>(
156156
spec_id: SpecId,
157157
call_request: &CallRequest,
158-
block_spec: &Option<BlockSpec>,
158+
block_spec: &BlockSpec,
159159
) -> Result<(), ProviderError<LoggerErrorT>> {
160-
if let Some(ref block_spec) = block_spec {
161-
validate_post_merge_block_tags(spec_id, block_spec)?;
162-
}
160+
validate_post_merge_block_tags(spec_id, block_spec)?;
163161

164162
validate_transaction_and_call_request(
165163
spec_id,
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::str::FromStr;
2+
3+
use anyhow::Context;
4+
use edr_eth::{remote::eth::CallRequest, Address, Bytes, SpecId};
5+
use edr_provider::{
6+
hardhat_rpc_types::ForkConfig, test_utils::create_test_config_with_fork, MethodInvocation,
7+
NoopLogger, Provider, ProviderRequest,
8+
};
9+
use edr_test_utils::env::get_alchemy_url;
10+
use sha3::{Digest, Keccak256};
11+
use tokio::runtime;
12+
13+
// Check that there is no panic when calling a forked blockchain where the
14+
// hardfork is specified as Cancun, but the block number is before the Cancun
15+
// hardfork. https://github.com/NomicFoundation/edr/issues/356
16+
#[tokio::test(flavor = "multi_thread")]
17+
async fn issue_324() -> anyhow::Result<()> {
18+
// ERC-20 contract
19+
const TEST_CONTRACT_ADDRESS: &str = "0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0";
20+
21+
let contract_address = Address::from_str(TEST_CONTRACT_ADDRESS).context("Invalid address")?;
22+
23+
let logger = Box::new(NoopLogger);
24+
let subscriber = Box::new(|_event| {});
25+
26+
let mut config = create_test_config_with_fork(Some(ForkConfig {
27+
json_rpc_url: get_alchemy_url().replace("mainnet", "sepolia"),
28+
// Pre-cancun Sepolia block
29+
block_number: Some(4243456),
30+
http_headers: None,
31+
}));
32+
config.hardfork = SpecId::CANCUN;
33+
34+
let provider = Provider::new(runtime::Handle::current(), logger, subscriber, config)?;
35+
36+
let selector = Bytes::copy_from_slice(
37+
&Keccak256::new_with_prefix("decimals()")
38+
.finalize()
39+
.as_slice()[..4],
40+
);
41+
42+
let response = provider.handle_request(ProviderRequest::Single(MethodInvocation::Call(
43+
CallRequest {
44+
to: Some(contract_address),
45+
data: Some(selector),
46+
..CallRequest::default()
47+
},
48+
None,
49+
None,
50+
)))?;
51+
52+
assert_eq!(
53+
response.result,
54+
"0x0000000000000000000000000000000000000000000000000000000000000006"
55+
);
56+
57+
Ok(())
58+
}

0 commit comments

Comments
 (0)