Skip to content
Open
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
8 changes: 8 additions & 0 deletions .changelog/cast-hyperevm-read-precompiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
cast: patch
foundry-evm-core: patch
foundry-evm: patch
foundry-evm-networks: patch
---

Added exact historical replay of HyperEVM read precompiles when the archive RPC exposes block precompile data.
7 changes: 7 additions & 0 deletions .changelog/cast-run-single-evm-replay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
cast: patch
foundry-evm: patch
foundry-evm-core: patch
---

Reused one EVM instance while replaying a block in `cast run`.
342 changes: 161 additions & 181 deletions crates/cast/src/cmd/run.rs

Large diffs are not rendered by default.

45 changes: 42 additions & 3 deletions crates/cast/tests/cli/run_networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,51 @@ network_replay_tests! {
flaky_run_berachain => ("berachain", "https://rpc.berachain.com", Exact),
flaky_run_mantle => ("mantle", "https://rpc.mantle.xyz", ReplaysOnly),

// HyperCore credits are injected by the chain and read precompiles have no local
// implementation, so only some transactions reproduce exactly. Needs an archive endpoint:
// the official one ignores the block tag and answers every state read at latest.
// HyperCore credits are injected by the chain and can still affect prefix replay, so recent
// transactions are only required to replay. Needs an archive endpoint: the official one
// ignores the block tag and answers every state read at latest.
flaky_run_hyperevm => ("hyperevm", "https://rpc.purroofgroup.com", ReplaysOnly),
}

/// Replays a pinned HyperEVM transaction that calls the BBO read precompile four times.
#[expect(clippy::disallowed_macros, reason = "skips have to be visible in the nightly test log")]
fn assert_replays_hyperevm_read_precompile(cmd: &mut TestCommand) {
let network = Network {
name: "hyperevm-read-precompile",
rpc_url: "https://rpc.purroofgroup.com",
gas: GasExpectation::Exact,
transaction_type: None,
};
let tx_hash = "0x0ef26f3e00d84d6a5d47271d0f7bbde713f4067e78d61b458727bfc7205017e4";
let Some(receipt) = json_output(cmd, &["receipt", tx_hash, "--rpc-url", network.rpc_url])
else {
eprintln!("skipping {}: endpoint unreachable", network.name);
return;
};

let output = cmd
.cast_fuse()
.args(["run", tx_hash, "--rpc-url", network.rpc_url])
.assert_success()
.get_output()
.stdout_lossy();
let replayed_gas = gas_used(&output).unwrap_or_else(|| {
panic!("{}: `cast run` reported no gas for {tx_hash}:\n{output}", network.name)
});
let receipt_gas = hex_field(&receipt, "gasUsed")
.unwrap_or_else(|| panic!("{}: receipt for {tx_hash} has no gasUsed", network.name));
assert_eq!(
replayed_gas, receipt_gas,
"{}: replayed gas does not match the receipt",
network.name
);
}

// The block-scoped archive data makes the precompile output and gas exactly reproducible.
casttest!(flaky_run_hyperevm_read_precompile, |_prj, cmd| {
assert_replays_hyperevm_read_precompile(&mut cmd);
});

casttest!(flaky_run_celo_cip64, |_prj, cmd| {
assert_replays_recent_transaction(
&mut cmd,
Expand Down
24 changes: 23 additions & 1 deletion crates/evm/core/src/evm/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{fmt::Debug, ops::Deref};
use std::{
fmt::Debug,
ops::{Deref, DerefMut},
};

use crate::{
FoundryBlock, FoundryChain, FoundryContextExt, FoundryInspectorExt, FoundryJournal,
Expand Down Expand Up @@ -156,7 +159,9 @@ pub trait FoundryEvmFactory:
BlockEnv = Self::BlockEnv,
Spec = Self::Spec,
HaltReason = Self::HaltReason,
Precompiles = Self::Precompiles,
> + Deref<Target = Self::FoundryContext<'db>>
+ DerefMut<Target = Self::FoundryContext<'db>>
where
Self: 'db;

Expand All @@ -169,6 +174,23 @@ pub trait FoundryEvmFactory:
inspector: I,
) -> Self::FoundryEvm<'db, I>;

/// Tries to execute a canonical system transaction on a Foundry-wrapped EVM during replay.
///
/// Returning `Ok(None)` means the transaction was not recognized. Implementations must not
/// mutate the EVM, its database, or inspector before returning `Ok(None)`, because callers may
/// fall back to ordinary execution using the same EVM instance.
#[cfg(feature = "monad")]
fn try_transact_foundry_system_replay<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
&self,
_evm: &mut Self::FoundryEvm<'db, I>,
_tx: &Self::Tx,
) -> eyre::Result<Option<ResultAndState<Self::HaltReason>>>
where
Self: 'db,
{
Ok(None)
}
Comment on lines +177 to +192

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not add this to FoundryEvmFactory imo


/// Tries to execute a canonical system transaction on a regular Alloy EVM during replay.
///
/// Returning `Ok(None)` means the transaction was not recognized. Implementations must not
Expand Down
11 changes: 11 additions & 0 deletions crates/evm/core/src/evm/monad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,17 @@ impl FoundryEvmFactory for MonadEvmFactory {
try_transact_monad_system_replay(evm, tx)
}

fn try_transact_foundry_system_replay<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
&self,
evm: &mut Self::FoundryEvm<'db, I>,
tx: &Self::Tx,
) -> eyre::Result<Option<ResultAndState<Self::HaltReason>>>
where
Self: 'db,
{
try_transact_monad_system_replay(evm, tx)
}

fn create_foundry_nested_evm<'db>(
&self,
db: &'db mut dyn DatabaseExt<Self>,
Expand Down
Loading
Loading