Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
7 changes: 7 additions & 0 deletions .changelog/exact-fork-ancestry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
forge: patch
chisel: patch
anvil: patch
---

Pinned fork state reads and `BLOCKHASH` ancestry to the exact resolved block across reorgs.
3 changes: 2 additions & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ monad-revm = { git = "https://github.com/category-labs/monad-revm", rev = "8ba4a
foundry-wallets = { git = "https://github.com/foundry-rs/foundry-core", rev = "8b3ea9453789ba0d9d8ebf0fc4ee0fed9e4add8f" }
foundry-compilers = { git = "https://github.com/foundry-rs/foundry-core", rev = "8b3ea9453789ba0d9d8ebf0fc4ee0fed9e4add8f" }
foundry-block-explorers = { git = "https://github.com/foundry-rs/foundry-core", rev = "8b3ea9453789ba0d9d8ebf0fc4ee0fed9e4add8f" }
foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-core", rev = "8b3ea9453789ba0d9d8ebf0fc4ee0fed9e4add8f" }
foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-core", rev = "d654e3e51c2e27d61d94d287d1cab993be7139ce" }

## alloy-core
# alloy-dyn-abi = { path = "../../alloy-rs/core/crates/dyn-abi" }
Expand Down
49 changes: 36 additions & 13 deletions crates/anvil/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use foundry_config::Config;
#[cfg(feature = "monad")]
use foundry_evm::hardfork::MonadHardfork;
use foundry_evm::{
backend::{BlockchainDb, BlockchainDbMeta, SharedBackend},
backend::{BlockchainDb, BlockchainDbMeta, ForkBlock, SharedBackend},
constants::DEFAULT_CREATE2_DEPLOYER,
hardfork::FoundryHardfork,
hardforks::latest_active_tempo_hardfork,
Expand Down Expand Up @@ -152,6 +152,18 @@ const BANNER: &str = r"
\__,_| |_| |_| \_/ |_| |_|
";

fn fork_source_id(urls: &[String], headers: &[String]) -> B256 {
let mut encoded = Vec::new();
for parts in [urls, headers] {
encoded.extend_from_slice(&(parts.len() as u64).to_be_bytes());
for part in parts {
encoded.extend_from_slice(&(part.len() as u64).to_be_bytes());
encoded.extend_from_slice(part.as_bytes());
}
}
keccak256(encoded)
}

/// Configurations of the EVM node
#[derive(Clone, Debug)]
pub struct NodeConfig {
Expand Down Expand Up @@ -2022,14 +2034,12 @@ latest block number: {latest_block}"
eyre::bail!("primary fork endpoint changed while its context was being validated");
}

let meta = BlockchainDbMeta::new(cache_block_env, eth_rpc_url.clone());
let source_id = fork_source_id(&self.fork_urls, &self.fork_headers);
let meta = BlockchainDbMeta::new(cache_block_env, eth_rpc_url.clone())
.with_fork_identity(block_hash, source_id);
let cache_path =
self.block_cache_path_for_rpc(source_chain_id, fork_block_number, &eth_rpc_url);
let block_chain_db = if self.fork_chain_id.is_some() {
BlockchainDb::new_skip_check(meta, cache_path)
} else {
BlockchainDb::new(meta, cache_path)
};
let block_chain_db = BlockchainDb::new(meta, cache_path);

// After bootstrap, rebuild the provider with round-robin if multiple URLs are
// configured. This ensures bootstrap used only the primary endpoint for consistency,
Expand All @@ -2052,12 +2062,14 @@ latest block number: {latest_block}"

// This will spawn the background thread that will use the provider to fetch
// blockchain data from the other client
let backend = SharedBackend::spawn_backend(
Arc::clone(&provider),
block_chain_db.clone(),
Some(fork_block_number.into()),
)
.await;
let anchor = ForkBlock::with_rpc_number(
evm_env.block_env.number.saturating_to(),
fork_block_number,
block_hash,
);
let (backend, handler) =
SharedBackend::new_with_anchor(Arc::clone(&provider), block_chain_db.clone(), anchor);
tokio::spawn(handler);

let config = ClientForkConfig {
fork_urls: self.fork_urls.clone(),
Expand Down Expand Up @@ -2427,6 +2439,17 @@ async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
mod tests {
use super::*;

#[test]
fn fork_source_identity_includes_all_urls_and_headers() {
let urls = ["http://primary".to_string(), "http://fallback".to_string()];
let headers = ["Authorization: secret".to_string()];
let identity = fork_source_id(&urls, &headers);

assert_ne!(identity, fork_source_id(&urls[..1], &headers));
assert_ne!(identity, fork_source_id(&urls, &[]));
assert_ne!(identity, fork_source_id(&[urls[1].clone(), urls[0].clone()], &headers));
}

#[test]
fn test_prune_history() {
let config = PruneStateHistoryConfig::default();
Expand Down
5 changes: 2 additions & 3 deletions crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,9 +542,8 @@ const fn noop_on_execution_error<E>(_evm: &mut E) {}
/// Caches the fork blocks needed to construct the next Monad block's ancestor context.
#[cfg(feature = "monad")]
async fn cache_monad_fork_context(fork: &ClientFork) -> Result<(), BlockchainError> {
let block_number = fork.block_number();
let block =
fork.block_by_number_full(block_number).await?.ok_or(BlockchainError::BlockNotFound)?;
fork.block_by_hash_full(fork.block_hash()).await?.ok_or(BlockchainError::BlockNotFound)?;
let parent_hash = block.header().parent_hash();
if !parent_hash.is_zero() {
fork.block_by_hash_full(parent_hash).await?.ok_or(BlockchainError::BlockNotFound)?;
Expand Down Expand Up @@ -4751,7 +4750,7 @@ impl<N: Network> Backend<N> {
);
}
let fork_block = staged_fork
.block_by_number(staged_fork.block_number())
.block_by_hash(staged_fork.block_hash())
.await?
.ok_or(BlockchainError::BlockNotFound)?;
if fork_block.header.hash != staged_client_config.block_hash {
Expand Down
2 changes: 1 addition & 1 deletion crates/cheatcodes/src/evm/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ fn create_fork_request<FEN: FoundryEvmNetwork>(
&& ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url),
url,
evm_opts,
expected_context: None,
resolved: None,
};
Ok(fork)
}
Expand Down
1 change: 1 addition & 0 deletions crates/chisel/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ async fn run_command_with_network<FEN: FoundryEvmNetwork>(
resolved_hardfork: None,
source_chain_id: None,
backend: None,
resolved_fork: None,
calldata: None,
ir_minimum: args.ir_minimum,
})?;
Expand Down
1 change: 1 addition & 0 deletions crates/chisel/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ impl<FEN: FoundryEvmNetwork> ChiselDispatcher<FEN> {
// Clear the backend so that it is re-instantiated with the new fork
// upon the next execution of the session source.
source.config.backend = None;
source.config.resolved_fork = None;

sh_println!("Set fork URL to {}", fork_url.yellow())?;

Expand Down
47 changes: 30 additions & 17 deletions crates/chisel/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,35 @@ impl<FEN: FoundryEvmNetwork> SessionSource<FEN> {
}

async fn build_runner(&mut self, final_pc: usize) -> Result<ChiselRunner<FEN>> {
let (mut evm_env, tx_env, fork_context) = self
.config
.evm_opts
.env_with_fork_context::<SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>()
.await?;
let (mut evm_env, tx_env, backend, resolved_fork) = match self.config.backend.clone() {
Some(backend) => {
let resolved_fork = self.config.resolved_fork.clone();
let (evm_env, tx_env) = self
.config
.evm_opts
.env_with_resolved_fork::<SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>(
self.config.resolved_fork.as_ref(),
)
.await?;
(evm_env, tx_env, backend, resolved_fork)
}
None => {
let (evm_env, tx_env, resolved_fork) = self
.config
.evm_opts
.env_resolved::<SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>()
.await?;
let fork = self.config.evm_opts.get_fork_resolved(
&self.config.foundry_config,
evm_env.cfg_env.chain_id,
resolved_fork.as_ref(),
);
let backend = Backend::spawn(fork)?;
self.config.backend = Some(backend.clone());
(evm_env, tx_env, backend, resolved_fork)
}
};
let fork_context = resolved_fork.as_ref().map(|fork| fork.context());
let fork_chain_id = fork_context.map(|context| context.source_chain_id);
let fork_hardfork = fork_context.and_then(|context| context.hardfork);
self.config.source_chain_id = fork_chain_id;
Expand All @@ -210,18 +234,7 @@ impl<FEN: FoundryEvmNetwork> SessionSource<FEN> {
None,
None,
);

let backend = match self.config.backend.clone() {
Some(backend) => backend,
None => {
let fork = fork_context.and_then(|context| {
self.config.evm_opts.get_fork_with_context(&self.config.foundry_config, context)
});
let backend = Backend::spawn(fork)?;
self.config.backend = Some(backend.clone());
backend
}
};
self.config.resolved_fork = resolved_fork;

let executor = ExecutorBuilder::default()
.inspectors(|stack| {
Expand Down
4 changes: 4 additions & 0 deletions crates/chisel/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use foundry_config::{Config, FoundryHardfork, SolcReq};
use foundry_evm::{
backend::Backend,
core::{bytecode::InstIter, evm::FoundryEvmNetwork},
fork::ResolvedFork,
opts::EvmOpts,
};
use foundry_evm_networks::NetworkConfigs;
Expand Down Expand Up @@ -303,6 +304,9 @@ pub struct SessionSourceConfig<FEN: FoundryEvmNetwork> {
/// In-memory REVM db for the session's runner.
#[serde(skip)]
pub backend: Option<Backend<FEN>>,
/// Exact fork identity used to construct the cached backend.
#[serde(skip)]
pub resolved_fork: Option<ResolvedFork>,
/// Optionally enable traces for the REPL contract execution
pub traces: bool,
/// Optionally set calldata for the REPL contract execution
Expand Down
Loading
Loading