Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changelog/anvil-decoder-executed-hardfork.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
anvil: patch
---

Fixed anvil trace decoding to follow the executed network hardfork when a cross-namespace hardfork override is configured.
2 changes: 1 addition & 1 deletion crates/anvil/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1422,7 +1422,7 @@ impl NodeConfig {
.unwrap_or_else(|| self.get_hardfork());
let mut decoder_builder = CallTraceDecoderBuilder::new()
.with_networks(self.networks)
.with_hardfork(Some(active_hardfork));
.with_hardfork(Some(self.networks.executed_hardfork(active_hardfork)));
if self.print_traces {
// if traces should get printed we configure the decoder with the signatures cache
if let Ok(identifier) = SignaturesIdentifier::new(false) {
Expand Down
16 changes: 15 additions & 1 deletion crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1490,7 +1490,7 @@ impl<N: Network> Backend<N> {

/// Returns a trace decoder configured for the currently resolved hardfork.
fn call_trace_decoder(&self) -> Arc<CallTraceDecoder> {
let hardfork = Some(self.hardfork());
let hardfork = Some(self.networks.executed_hardfork(self.hardfork()));
let decoder = self.call_trace_decoder.read();
if decoder.hardfork() == hardfork {
return Arc::clone(&decoder);
Expand Down Expand Up @@ -9883,6 +9883,20 @@ mod tests {
assert_eq!(outcome.block_number, original_best_number + 1);
}

#[tokio::test]
async fn trace_decoder_follows_executed_hardfork_for_cross_namespace_override() {
let (api, _) = spawn(
NodeConfig::test_tempo()
.with_hardfork(Some(FoundryHardfork::Ethereum(EthereumHardfork::Prague))),
)
.await;

let decoder = api.backend.call_trace_decoder();
assert_eq!(decoder.hardfork(), Some(FoundryHardfork::Tempo(api.backend.tempo_hardfork())));
// The refresh compares the same coerced value, so repeated calls stay stable.
assert!(Arc::ptr_eq(&decoder, &api.backend.call_trace_decoder()));
}

#[cfg(feature = "monad")]
#[tokio::test]
async fn monad_trace_decoder_follows_resolved_hardfork() {
Expand Down
29 changes: 29 additions & 0 deletions crates/evm/networks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,23 @@ impl NetworkConfigs {
false
}

/// Coerces `hardfork` into this network's family.
///
/// Execution and fee rules apply the same lossy `From` conversions, so a cross-namespace
/// override such as `--hardfork prague` on a Tempo node runs as a Tempo hardfork. Callers that
/// need to describe what actually executed, like trace decoding, go through here rather than
/// carrying the configured value.
pub fn executed_hardfork(&self, hardfork: FoundryHardfork) -> FoundryHardfork {
if self.is_tempo() {
return TempoHardfork::from(hardfork).into();
}
#[cfg(feature = "monad")]
if self.is_monad() {
return MonadHardfork::from(hardfork).into();
}
hardfork
}

/// Returns additional cheatcode contract addresses for the active network.
pub const fn extra_cheatcode_addresses(&self) -> &'static [Address] {
#[cfg(feature = "monad")]
Expand Down Expand Up @@ -1789,4 +1806,16 @@ mod tests {
assert!(cfg_optimism.is_optimism());
}
}

#[test]
fn executed_hardfork_follows_the_network_family() {
// A cross-namespace override runs as the configured network's hardfork, so the value used
// to describe execution has to be coerced the same way.
let prague = FoundryHardfork::Ethereum(EthereumHardfork::Prague);
assert_eq!(NetworkConfigs::default().executed_hardfork(prague), prague);
assert_eq!(
NetworkConfigs::with_tempo().executed_hardfork(prague),
FoundryHardfork::Tempo(TempoHardfork::from(prague))
);
}
}
Loading