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
6 changes: 6 additions & 0 deletions .changelog/cast-run-any-network.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
cast: patch
foundry-evm-core: patch
---

Made `cast run` decode blocks with `AnyNetwork`, so chains whose blocks carry non-standard transaction types, such as Arbitrum, Celo and unrouted OP-stack forks, can be replayed at all.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ alloy-eips.workspace = true
tempo-alloy.workspace = true
tempo-contracts.workspace = true
tempo-primitives.workspace = true
alloy-evm.workspace = true

op-alloy-consensus = { workspace = true, features = ["k256"], optional = true }
op-alloy-flz = { workspace = true, optional = true }
Expand Down
62 changes: 49 additions & 13 deletions crates/cast/src/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use crate::{
};
use alloy_consensus::{BlockHeader, Transaction, transaction::SignerRecoverable};
use alloy_eips::BlockNumHash;
use alloy_evm::FromRecoveredTx;
use alloy_network::{
BlockResponse, Network, ReceiptResponse, TransactionResponse, primitives::HeaderResponse,
AnyNetwork, AnyTxEnvelope, BlockResponse, Network, ReceiptResponse, TransactionResponse,
primitives::HeaderResponse,
};
use alloy_primitives::{
Address, B256, Bytes, U256,
Expand Down Expand Up @@ -47,6 +47,7 @@ use foundry_evm::core::evm::OpEvmNetwork;
use foundry_evm::{
core::{
FoundryBlock as _, FoundryChain,
env::FromAnyRpcTransaction as _,
evm::{
BlockContext, ChainFor, EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor,
},
Expand Down Expand Up @@ -200,7 +201,11 @@ impl RunArgs {
self.rpc.common.compute_units_per_second
};

let provider = ProviderBuilder::<FEN::Network>::from_config(&config)?
// `AnyNetwork` rather than `FEN::Network`: chains such as Arbitrum, Celo and the
// OP-stack forks Foundry does not route to a dedicated network put transaction types the
// strict Ethereum envelope cannot decode into every block, which would fail the full
// block fetch below for the whole chain. Execution still uses `FEN`.
let provider = ProviderBuilder::<AnyNetwork>::from_config(&config)?
.compute_units_per_second_opt(compute_units_per_second)
.build()?;

Expand Down Expand Up @@ -373,9 +378,17 @@ impl RunArgs {
return Ok(());
}

let target_tx_env = TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from());
let target_is_system = is_known_system_sender(tx.from())
|| tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE);
// Report an unsupported system transaction before decoding it: the envelopes a chain
// reserves for itself, such as Arbitrum's internal transaction, are exactly the ones this
// build may not be able to decode.
if target_is_system && !self.replay_system_txes && !evm_opts.networks.is_monad() {
return Err(eyre::eyre!(
"{tx_hash:?} is a system transaction.\nReplaying system transactions is currently not supported."
));
}
let target_tx_env = TxEnvFor::<FEN>::from_any_rpc_transaction(&tx)?;

let tx_block_number = tx
.block_number()
Expand Down Expand Up @@ -421,7 +434,7 @@ impl RunArgs {
// TODO: add glamsterdam header field checks in the future
evm_version = Some(EvmVersion::Cancun);
}
apply_chain_and_block_specific_env_changes_for_chain::<FEN::Network, _, _>(
apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
&mut evm_env,
block,
chain.id(),
Expand All @@ -439,12 +452,20 @@ impl RunArgs {
TracingExecutor::<FEN>::extend_precompile_labels(&mut config, networks, resolved_hardfork);

let block_context = if networks.is_monad() {
let block = block.as_ref().ok_or_else(|| {
eyre::eyre!(
"block {tx_block_number} is required to reconstruct transaction context"
)
})?;
Some(BlockContext::<FEN>::fetch(&provider, block).await?)
// `BlockContext` is typed to `FEN::Network`. Monad blocks only carry standard
// envelopes, so a typed provider can serve this path while the rest of the command
// stays on `AnyNetwork`.
let typed_provider = ProviderBuilder::<FEN::Network>::from_config(&config)?
.compute_units_per_second_opt(compute_units_per_second)
.build()?;
let block = typed_provider.get_block(tx_block_number.into()).full().await?.ok_or_else(
|| {
eyre::eyre!(
"block {tx_block_number} is required to reconstruct transaction context"
)
},
)?;
Some(BlockContext::<FEN>::fetch(&typed_provider, &block).await?)
} else {
None
};
Expand Down Expand Up @@ -519,9 +540,15 @@ impl RunArgs {
break;
}

let tx_env = TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from());
let is_system = is_known_system_sender(tx.from())
|| tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE);
// Classify before converting: a chain's own system envelopes are exactly the
// ones this build may not be able to decode, and they are skipped below.
if is_system && !self.replay_system_txes && !networks.is_monad() {
pb.set_position((index + 1) as u64);
continue;
}
let tx_env = TxEnvFor::<FEN>::from_any_rpc_transaction(tx)?;
let chain_context = block_context.as_ref().map_or_else(
|| ChainFor::<FEN>::for_transaction(&tx_env),
|context| context.transaction(index),
Expand Down Expand Up @@ -635,7 +662,16 @@ impl RunArgs {
|context| context.transaction(target_index),
);

if tx.as_ref().recover_signer().is_ok_and(|signer| signer != tx.from()) {
// A recovered signer that disagrees with the `from` the node reports marks a
// transaction the chain injected rather than one a key signed, such as a HyperCore
// credit. Envelopes this build cannot decode are in the same category.
let sender_is_forged = match &*tx.inner.inner {
AnyTxEnvelope::Ethereum(inner) => {
inner.recover_signer().is_ok_and(|signer| signer != tx.from())
}
AnyTxEnvelope::Unknown(_) => true,
};
if sender_is_forged {
evm_env.cfg_env.disable_balance_check = true;
}

Expand Down
12 changes: 11 additions & 1 deletion crates/evm/core/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ impl FromAnyRpcTransaction for TempoTxEnv {
#[cfg(feature = "optimism")]
mod optimism {
use super::*;
use alloy_eips::eip2718::Encodable2718;
use alloy_op_evm::OpTx;
use op_alloy_consensus::{DEPOSIT_TX_TYPE_ID, TxDeposit};
use op_revm::{OpTransaction, transaction::OpTxTr};
Expand Down Expand Up @@ -914,7 +915,9 @@ mod optimism {
if let Some(envelope) = tx.as_envelope() {
return Ok(Self(OpTransaction::<TxEnv> {
base: TxEnv::from_recovered_tx(envelope, tx.from()),
enveloped_tx: None,
// The L1 data fee is charged off these bytes, and op-revm rejects a
// non-deposit transaction that arrives without them.
enveloped_tx: Some(envelope.encoded_2718().into()),
deposit: Default::default(),
}));
}
Expand Down Expand Up @@ -1209,6 +1212,7 @@ mod tests {
mod optimism {
use super::*;
use alloy_consensus::Sealed;
use alloy_eips::eip2718::Encodable2718;
use alloy_op_evm::{OpEvmFactory, OpTx};
use op_alloy_consensus::{OpTxEnvelope, TxDeposit, transaction::OpTransactionInfo};
use op_alloy_rpc_types::Transaction as OpRpcTransaction;
Expand Down Expand Up @@ -1253,6 +1257,12 @@ mod tests {

let op_tx_env = OpTx::from_any_rpc_transaction(&any_tx).unwrap();
assert_eq!(op_tx_env.base, expected_base);
// op-revm charges the L1 data fee off these bytes and rejects a non-deposit
// transaction that arrives without them.
assert_eq!(
op_tx_env.enveloped_tx,
Some(any_tx.as_envelope().unwrap().encoded_2718().into())
);
}

#[test]
Expand Down
Loading