1use crate::{
2 FeeManager, PrecompileFactory,
3 eth::{
4 backend::{
5 db::{Db, SerializableState},
6 fork::{
7 ClientFork, ClientForkConfig, ForkEndpointIdentity, ensure_fork_network_supported,
8 },
9 genesis::GenesisConfig,
10 mem::fork_db::ForkedDatabase,
11 time::duration_since_unix_epoch,
12 },
13 fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
14 pool::transactions::TransactionOrder,
15 },
16 mem::{self, in_memory_db::StateRootDb},
17};
18use alloy_chains::{Chain, NamedChain};
19use alloy_consensus::BlockHeader;
20use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
21use alloy_evm::EvmEnv;
22use alloy_genesis::Genesis;
23use alloy_network::{AnyNetwork, AnyRpcBlock, BlockResponse, TransactionResponse};
24use alloy_primitives::{
25 Address, B256, BlockNumber, TxHash, U256, hex, keccak256, map::HashMap, utils::Unit,
26};
27use alloy_provider::Provider;
28use alloy_rpc_types::{
29 BlockNumberOrTag,
30 anvil::{Metadata, NodeInfo},
31};
32use alloy_signer::Signer;
33use alloy_signer_local::{
34 MnemonicBuilder, PrivateKeySigner,
35 coins_bip39::{English, Mnemonic},
36};
37use alloy_transport::TransportError;
38use anvil_server::ServerConfig;
39use eyre::{Context, Result};
40use foundry_common::{
41 ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT,
42 provider::{ProviderBuilder, RetryProvider, is_rpc_method_not_found, redact_url},
43};
44use foundry_config::Config;
45use foundry_evm::{
46 backend::{BlockchainDb, BlockchainDbMeta, ForkBlock, SharedBackend},
47 constants::DEFAULT_CREATE2_DEPLOYER,
48 hardfork::FoundryHardfork,
49 utils::{apply_chain_and_block_specific_env_changes_for_chain, block_env_from_header},
50};
51use parking_lot::RwLock;
52use rand_08::thread_rng;
53use revm::{
54 context::{BlockEnv, CfgEnv},
55 context_interface::block::BlobExcessGasAndPrice,
56 primitives::hardfork::SpecId,
57};
58use serde_json::{Value, json};
59use std::{
60 fmt::Write as FmtWrite,
61 net::{IpAddr, Ipv4Addr},
62 path::PathBuf,
63 sync::Arc,
64 time::Duration,
65};
66use tempo_hardfork::{
67 TempoHardfork,
68 constants::gas::{TEMPO_T0_BASE_FEE, TEMPO_T1_BASE_FEE},
69};
70use tokio::sync::RwLock as TokioRwLock;
71use yansi::Paint;
72
73pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
74use foundry_evm::{
75 traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
76 utils::{get_blob_params, get_blob_params_by_hardfork},
77};
78use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
79use tempo_precompiles::TIP_FEE_MANAGER_ADDRESS;
80
81pub const NODE_PORT: u16 = 8545;
83pub const CHAIN_ID: u64 = 31337;
85pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
87pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
89pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
91
92#[derive(Clone, Copy, Debug)]
93struct ForkOverrides {
94 gas_limit: Option<u64>,
95 gas_price: Option<u128>,
96 base_fee: Option<u64>,
97}
98
99struct StableForkSnapshot {
100 endpoint_identity: ForkEndpointIdentity,
101 block_number: u64,
102 transaction_replay: Option<ForkTransactionReplay>,
103 block: Option<AnyRpcBlock>,
104 gas_price: u128,
105}
106
107#[derive(Clone, Copy, Debug, Default)]
114struct AnvilNodeInfoProbe {
115 identified: bool,
116}
117
118impl AnvilNodeInfoProbe {
119 const fn new(identified: bool) -> Self {
120 Self { identified }
121 }
122
123 async fn request(&mut self, provider: &RetryProvider) -> Result<Option<NodeInfo>> {
124 match provider.raw_request::<_, NodeInfo>("anvil_nodeInfo".into(), ()).await {
125 Ok(node_info) => {
126 self.identified = true;
127 Ok(Some(node_info))
128 }
129 Err(_) if !self.identified => Ok(None),
130 Err(error) => {
131 Err(error).wrap_err("failed to determine network family from fork endpoint")
132 }
133 }
134 }
135}
136
137#[derive(Clone, Debug)]
139pub(crate) struct ForkTransactionReplay {
140 pub(crate) source_block: AnyRpcBlock,
141 pub(crate) target_index: usize,
142}
143
144pub const DEFAULT_IPC_ENDPOINT: &str =
146 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
147
148const BANNER: &str = r"
149 _ _
150 (_) | |
151 __ _ _ __ __ __ _ | |
152 / _` | | '_ \ \ \ / / | | | |
153 | (_| | | | | | \ V / | | | |
154 \__,_| |_| |_| \_/ |_| |_|
155";
156
157fn fork_source_id(urls: &[String], headers: &[String]) -> B256 {
158 let mut encoded = Vec::new();
159 for parts in [urls, headers] {
160 encoded.extend_from_slice(&(parts.len() as u64).to_be_bytes());
161 for part in parts {
162 encoded.extend_from_slice(&(part.len() as u64).to_be_bytes());
163 encoded.extend_from_slice(part.as_bytes());
164 }
165 }
166 keccak256(encoded)
167}
168
169#[derive(Clone, Debug)]
171pub struct NodeConfig {
172 pub chain_id: Option<u64>,
174 pub gas_limit: Option<u64>,
176 pub disable_block_gas_limit: bool,
178 pub enable_tx_gas_limit: bool,
180 pub gas_price: Option<u128>,
182 pub base_fee: Option<u64>,
184 pub disable_min_priority_fee: bool,
186 pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
188 pub hardfork: Option<FoundryHardfork>,
190 pub genesis_accounts: Vec<PrivateKeySigner>,
192 pub genesis_balance: U256,
194 pub genesis_timestamp: Option<u64>,
196 pub genesis_block_number: Option<u64>,
198 pub signer_accounts: Vec<PrivateKeySigner>,
200 pub block_time: Option<Duration>,
202 pub no_mining: bool,
204 pub mixed_mining: bool,
206 pub port: u16,
208 pub max_transactions: usize,
210 pub fork_urls: Vec<String>,
214 pub fork_choice: Option<ForkChoice>,
216 pub fork_headers: Vec<String>,
218 pub fork_chain_id: Option<U256>,
220 pub fork_source_chain_id: Option<u64>,
222 pub fork_execution_chain_id: Option<u64>,
224 pub(crate) fork_endpoint_is_anvil: bool,
226 inferred_fork_network: Option<NetworkVariant>,
228 chain_id_network_base: Option<NetworkConfigs>,
230 fork_overrides: Option<ForkOverrides>,
232 pub account_generator: Option<AccountGenerator>,
234 pub enable_tracing: bool,
236 pub no_storage_caching: bool,
238 pub server_config: ServerConfig,
240 pub host: Vec<IpAddr>,
242 pub transaction_order: TransactionOrder,
244 pub config_out: Option<PathBuf>,
246 pub genesis: Option<Genesis>,
248 pub fork_request_timeout: Duration,
250 pub fork_request_retries: u32,
252 pub fork_retry_backoff: Duration,
254 pub compute_units_per_second: u64,
256 pub ipc_path: Option<Option<String>>,
258 pub enable_steps_tracing: bool,
260 pub print_logs: bool,
262 pub print_traces: bool,
264 pub enable_auto_impersonate: bool,
266 pub code_size_limit: Option<usize>,
268 pub prune_history: PruneStateHistoryConfig,
272 pub max_persisted_states: Option<usize>,
274 pub init_state: Option<SerializableState>,
276 pub transaction_block_keeper: Option<usize>,
278 pub disable_default_create2_deployer: bool,
280 pub disable_pool_balance_checks: bool,
282 pub slots_in_an_epoch: u64,
284 pub memory_limit: Option<u64>,
286 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
288 pub networks: NetworkConfigs,
290 pub tempo_fee_payer: Option<Address>,
294 pub silent: bool,
296 pub cache_path: Option<PathBuf>,
299 pub funded_accounts: HashMap<Address, U256>,
301}
302
303impl NodeConfig {
304 fn as_string(&self, fork: Option<&ClientFork>) -> String {
305 let mut s: String = String::new();
306 let _ = write!(s, "\n{}", BANNER.green());
307 let _ = write!(s, "\n {VERSION_MESSAGE}");
308 let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green());
309
310 let _ = write!(
311 s,
312 r#"
313
314Available Accounts
315==================
316"#
317 );
318 let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
319 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
320 write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
321 }
322
323 let _ = write!(
324 s,
325 r#"
326
327Private Keys
328==================
329"#
330 );
331
332 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
333 let hex = hex::encode(wallet.credential().to_bytes());
334 let _ = write!(s, "\n({idx}) 0x{hex}");
335 }
336
337 if let Some(generator) = &self.account_generator {
338 let _ = write!(
339 s,
340 r#"
341
342Wallet
343==================
344Mnemonic: {}
345Derivation path: {}
346"#,
347 generator.phrase,
348 generator.get_derivation_path()
349 );
350 }
351
352 if let Some(fee_payer) = self.tempo_fee_payer_address() {
353 let _ = write!(
354 s,
355 r#"
356
357Tempo Fee Payer
358==================
359{fee_payer}
360"#
361 );
362 }
363
364 if let Some(fork) = fork {
365 let _ = write!(
366 s,
367 r#"
368
369Fork
370==================
371Endpoint: {}
372Block number: {}
373Block hash: {:?}
374Chain ID: {}
375"#,
376 fork.eth_rpc_url().as_deref().map(redact_url).unwrap_or_else(|| "none".to_string()),
377 fork.block_number(),
378 fork.block_hash(),
379 fork.execution_chain_id()
380 );
381 if fork.chain_id() != fork.execution_chain_id() {
382 let _ = writeln!(s, "Source chain ID: {}", fork.chain_id());
383 }
384
385 if self.fork_urls.len() > 1 {
386 let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len());
387 for (i, url) in self.fork_urls.iter().enumerate() {
388 let _ = writeln!(s, " ({i}) {}", redact_url(url));
389 }
390 }
391
392 if let Some(tx_hash) = fork.transaction_hash() {
393 let _ = writeln!(s, "Transaction hash: {tx_hash}");
394 }
395 } else {
396 let _ = write!(
397 s,
398 r#"
399
400Chain ID
401==================
402
403{}
404"#,
405 self.get_chain_id().green()
406 );
407 }
408
409 if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
410 let _ = write!(
411 s,
412 r#"
413Gas Price
414==================
415
416{}
417"#,
418 self.get_gas_price().green()
419 );
420 } else {
421 let _ = write!(
422 s,
423 r#"
424Base Fee
425==================
426
427{}
428"#,
429 self.get_base_fee().green()
430 );
431 }
432
433 let _ = write!(
434 s,
435 r#"
436Gas Limit
437==================
438
439{}
440"#,
441 {
442 if self.disable_block_gas_limit {
443 "Disabled".to_string()
444 } else {
445 self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
446 if self.fork_choice.is_some() {
447 "Forked".to_string()
448 } else {
449 DEFAULT_GAS_LIMIT.to_string()
450 }
451 })
452 }
453 }
454 .green()
455 );
456
457 let _ = write!(
458 s,
459 r#"
460Genesis Timestamp
461==================
462
463{}
464"#,
465 self.get_genesis_timestamp().green()
466 );
467
468 let _ = write!(
469 s,
470 r#"
471Genesis Number
472==================
473
474{}
475"#,
476 self.get_genesis_number().green()
477 );
478
479 s
480 }
481
482 fn as_json(&self, fork: Option<&ClientFork>) -> Value {
483 let mut wallet_description = HashMap::new();
484 let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
485 let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
486
487 for wallet in &self.genesis_accounts {
488 available_accounts.push(format!("{:?}", wallet.address()));
489 private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes())));
490 }
491
492 if let Some(generator) = &self.account_generator {
493 let phrase = generator.get_phrase().to_string();
494 let derivation_path = generator.get_derivation_path().to_string();
495
496 wallet_description.insert("derivation_path".to_string(), derivation_path);
497 wallet_description.insert("mnemonic".to_string(), phrase);
498 };
499
500 let gas_limit = match self.gas_limit {
501 Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
503 Some(limit) => Some(limit.to_string()),
504 _ => None,
505 };
506
507 if let Some(fork) = fork {
508 json!({
509 "available_accounts": available_accounts,
510 "private_keys": private_keys,
511 "endpoint": fork.eth_rpc_url().as_deref().map(redact_url).unwrap_or_default(),
512 "block_number": fork.block_number(),
513 "block_hash": fork.block_hash(),
514 "chain_id": fork.execution_chain_id(),
515 "source_chain_id": fork.chain_id(),
516 "wallet": wallet_description,
517 "base_fee": format!("{}", self.get_base_fee()),
518 "gas_price": format!("{}", self.get_gas_price()),
519 "gas_limit": gas_limit,
520 })
521 } else {
522 json!({
523 "available_accounts": available_accounts,
524 "private_keys": private_keys,
525 "wallet": wallet_description,
526 "base_fee": format!("{}", self.get_base_fee()),
527 "gas_price": format!("{}", self.get_gas_price()),
528 "gas_limit": gas_limit,
529 "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
530 })
531 }
532 }
533}
534
535impl NodeConfig {
536 #[doc(hidden)]
539 pub fn test() -> Self {
540 Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
541 }
542
543 #[doc(hidden)]
545 pub fn test_tempo() -> Self {
546 Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
547 }
548
549 #[cfg(feature = "monad")]
551 #[doc(hidden)]
552 pub fn test_monad() -> Self {
553 Self { networks: NetworkConfigs::with_monad(), ..Self::test() }
554 }
555
556 pub fn empty_state() -> Self {
558 Self {
559 genesis_accounts: vec![],
560 signer_accounts: vec![],
561 disable_default_create2_deployer: true,
562 ..Default::default()
563 }
564 }
565}
566
567impl Default for NodeConfig {
568 fn default() -> Self {
569 let genesis_accounts = AccountGenerator::new(10)
571 .phrase(DEFAULT_MNEMONIC)
572 .generate()
573 .expect("Invalid mnemonic.");
574 Self {
575 chain_id: None,
576 gas_limit: None,
577 disable_block_gas_limit: false,
578 enable_tx_gas_limit: false,
579 gas_price: None,
580 hardfork: None,
581 signer_accounts: genesis_accounts.clone(),
582 genesis_timestamp: None,
583 genesis_block_number: None,
584 genesis_accounts,
585 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
587 block_time: None,
588 no_mining: false,
589 mixed_mining: false,
590 port: NODE_PORT,
591 max_transactions: 1_000,
592 fork_urls: vec![],
593 fork_choice: None,
594 account_generator: None,
595 base_fee: None,
596 disable_min_priority_fee: false,
597 blob_excess_gas_and_price: None,
598 enable_tracing: true,
599 enable_steps_tracing: false,
600 print_logs: true,
601 print_traces: false,
602 enable_auto_impersonate: false,
603 no_storage_caching: false,
604 server_config: Default::default(),
605 host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
606 transaction_order: Default::default(),
607 config_out: None,
608 genesis: None,
609 fork_request_timeout: REQUEST_TIMEOUT,
610 fork_headers: vec![],
611 fork_request_retries: 5,
612 fork_retry_backoff: Duration::from_millis(1_000),
613 fork_chain_id: None,
614 fork_source_chain_id: None,
615 fork_execution_chain_id: None,
616 fork_endpoint_is_anvil: false,
617 inferred_fork_network: None,
618 chain_id_network_base: None,
619 fork_overrides: None,
620 compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
622 ipc_path: None,
623 code_size_limit: None,
624 prune_history: Default::default(),
625 max_persisted_states: None,
626 init_state: None,
627 transaction_block_keeper: None,
628 disable_default_create2_deployer: false,
629 disable_pool_balance_checks: false,
630 slots_in_an_epoch: DEFAULT_SLOTS_IN_AN_EPOCH,
631 memory_limit: None,
632 precompile_factory: None,
633 networks: Default::default(),
634 tempo_fee_payer: None,
635 silent: false,
636 cache_path: None,
637 funded_accounts: HashMap::default(),
638 }
639 }
640}
641
642impl NodeConfig {
643 pub(crate) fn apply_tempo_fork_beneficiary_default<N>(&self, evm_env: &mut EvmEnv<N>) {
646 if self.networks.is_tempo()
647 && !self.fork_urls.is_empty()
648 && evm_env.block_env.beneficiary.is_zero()
649 {
650 evm_env.block_env.beneficiary = TIP_FEE_MANAGER_ADDRESS;
657 }
658 }
659
660 #[must_use]
662 pub const fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
663 self.memory_limit = mems_value;
664 self
665 }
666
667 pub fn get_base_fee(&self) -> u64 {
671 let default = if self.networks.is_tempo() {
672 tempo_default_base_fee(TempoHardfork::from(self.get_hardfork()))
673 } else {
674 INITIAL_BASE_FEE
675 };
676 self.base_fee
677 .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
678 .unwrap_or(default)
679 }
680
681 pub fn get_gas_price(&self) -> u128 {
685 let default = if self.networks.is_tempo() {
686 tempo_default_base_fee(TempoHardfork::from(self.get_hardfork())) as u128
687 } else {
688 INITIAL_GAS_PRICE
689 };
690 self.gas_price.unwrap_or(default)
691 }
692
693 pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
694 if let Some(value) = self.blob_excess_gas_and_price {
695 value
696 } else {
697 let excess_blob_gas =
698 self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
699 BlobExcessGasAndPrice::new(
700 excess_blob_gas,
701 self.get_blob_params().update_fraction as u64,
702 )
703 }
704 }
705
706 pub fn get_blob_params(&self) -> BlobParams {
708 get_blob_params_by_hardfork(self.get_hardfork())
709 }
710
711 pub fn get_hardfork(&self) -> FoundryHardfork {
713 if let Some(hardfork) = self.hardfork {
714 return hardfork;
715 }
716 self.networks
717 .execution_network()
718 .hardfork_at(self.protocol_chain_id(), self.get_genesis_timestamp())
719 }
720
721 #[must_use]
723 pub const fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
724 self.code_size_limit = code_size_limit;
725 self
726 }
727 #[must_use]
729 pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
730 if disable_code_size_limit {
731 self.code_size_limit = Some(usize::MAX);
732 }
733 self
734 }
735
736 #[must_use]
738 pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
739 self.init_state = init_state;
740 self
741 }
742
743 #[must_use]
745 #[cfg(feature = "cmd")]
746 pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
747 self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
748 self
749 }
750
751 #[must_use]
753 pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
754 self.set_chain_id(chain_id);
755 self
756 }
757
758 pub fn get_chain_id(&self) -> u64 {
760 self.chain_id
761 .or(self.fork_execution_chain_id)
762 .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
763 .unwrap_or(CHAIN_ID)
764 }
765
766 fn protocol_chain_id(&self) -> u64 {
768 self.fork_source_chain_id.unwrap_or_else(|| self.get_chain_id())
769 }
770
771 pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
773 if let Some(base) = self.chain_id_network_base.take() {
774 self.networks = base;
775 }
776 self.chain_id = chain_id.map(Into::into);
777 let chain_id = self.get_chain_id();
778 let base = self.networks;
779 let inferred = base.with_chain_id(chain_id);
780 if !base.has_network_selection() && inferred.has_network_selection() {
781 self.chain_id_network_base = Some(base);
782 }
783 self.networks = inferred;
784 self.update_wallet_chain_id(chain_id);
785 }
786
787 pub(crate) fn update_wallet_chain_id(&mut self, chain_id: u64) {
788 self.genesis_accounts.iter_mut().for_each(|wallet| {
789 *wallet = wallet.clone().with_chain_id(Some(chain_id));
790 });
791 self.signer_accounts.iter_mut().for_each(|wallet| {
792 *wallet = wallet.clone().with_chain_id(Some(chain_id));
793 })
794 }
795
796 #[must_use]
798 pub const fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
799 self.gas_limit = gas_limit;
800 self
801 }
802
803 #[must_use]
807 pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
808 self.disable_block_gas_limit = disable_block_gas_limit;
809 self
810 }
811
812 #[must_use]
816 pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
817 self.enable_tx_gas_limit = enable_tx_gas_limit;
818 self
819 }
820
821 #[must_use]
823 pub const fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
824 self.gas_price = gas_price;
825 self
826 }
827
828 #[must_use]
830 pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
831 self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
832 self
833 }
834
835 #[must_use]
837 pub fn with_max_persisted_states<U: Into<usize>>(
838 mut self,
839 max_persisted_states: Option<U>,
840 ) -> Self {
841 self.max_persisted_states = max_persisted_states.map(Into::into);
842 self
843 }
844
845 #[must_use]
847 pub const fn with_max_transactions(mut self, max_transactions: Option<usize>) -> Self {
848 if let Some(max_transactions) = max_transactions {
849 self.max_transactions = max_transactions;
850 }
851 self
852 }
853
854 #[must_use]
856 pub fn with_transaction_block_keeper<U: Into<usize>>(
857 mut self,
858 transaction_block_keeper: Option<U>,
859 ) -> Self {
860 self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
861 self
862 }
863
864 #[must_use]
866 pub const fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
867 self.base_fee = base_fee;
868 self
869 }
870
871 #[must_use]
873 pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
874 self.disable_min_priority_fee = disable_min_priority_fee;
875 self
876 }
877
878 #[must_use]
880 pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
881 self.genesis = genesis;
882 self
883 }
884
885 pub fn get_genesis_timestamp(&self) -> u64 {
887 self.genesis_timestamp
888 .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
889 .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
890 }
891
892 #[must_use]
894 pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
895 if let Some(timestamp) = timestamp {
896 self.genesis_timestamp = Some(timestamp.into());
897 }
898 self
899 }
900
901 #[must_use]
903 pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
904 if let Some(number) = number {
905 self.genesis_block_number = Some(number.into());
906 }
907 self
908 }
909
910 pub fn get_genesis_number(&self) -> u64 {
912 self.genesis_block_number
913 .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
914 .unwrap_or(0)
915 }
916
917 #[must_use]
919 pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
920 self.hardfork = hardfork;
921 self
922 }
923
924 #[must_use]
926 pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
927 self.genesis_accounts = accounts;
928 self
929 }
930
931 #[must_use]
933 pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
934 self.signer_accounts = accounts;
935 self
936 }
937
938 pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
941 let accounts = generator.generate()?;
942 self.account_generator = Some(generator);
943 Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
944 }
945
946 #[must_use]
948 pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
949 self.genesis_balance = balance.into();
950 self
951 }
952
953 #[must_use]
955 pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
956 self.block_time = block_time.map(Into::into);
957 self
958 }
959
960 #[must_use]
961 pub fn with_mixed_mining<D: Into<Duration>>(
962 mut self,
963 mixed_mining: bool,
964 block_time: Option<D>,
965 ) -> Self {
966 self.block_time = block_time.map(Into::into);
967 self.mixed_mining = mixed_mining;
968 self
969 }
970
971 #[must_use]
973 pub const fn with_no_mining(mut self, no_mining: bool) -> Self {
974 self.no_mining = no_mining;
975 self
976 }
977
978 #[must_use]
980 pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
981 self.slots_in_an_epoch = slots_in_an_epoch;
982 self
983 }
984
985 #[must_use]
987 pub const fn with_port(mut self, port: u16) -> Self {
988 self.port = port;
989 self
990 }
991
992 #[must_use]
999 pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
1000 self.ipc_path = ipc_path;
1001 self
1002 }
1003
1004 #[must_use]
1006 pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
1007 self.config_out = config_out;
1008 self
1009 }
1010
1011 #[must_use]
1012 pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self {
1013 self.no_storage_caching = no_storage_caching;
1014 self
1015 }
1016
1017 #[must_use]
1019 pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
1020 if let Some(url) = eth_rpc_url {
1021 let fork_urls = vec![url.into()];
1022 if self.fork_urls != fork_urls {
1023 self.fork_endpoint_is_anvil = false;
1024 }
1025 self.fork_urls = fork_urls;
1026 }
1027 self
1028 }
1029
1030 #[must_use]
1032 pub fn with_fork_urls(mut self, fork_urls: Vec<String>) -> Self {
1033 if self.fork_urls != fork_urls {
1034 self.fork_endpoint_is_anvil = false;
1035 }
1036 self.fork_urls = fork_urls;
1037 self
1038 }
1039
1040 #[must_use]
1042 pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
1043 self.with_fork_choice(fork_block_number.map(Into::into))
1044 }
1045
1046 #[must_use]
1048 pub fn with_fork_transaction_hash<U: Into<TxHash>>(
1049 self,
1050 fork_transaction_hash: Option<U>,
1051 ) -> Self {
1052 self.with_fork_choice(fork_transaction_hash.map(Into::into))
1053 }
1054
1055 #[must_use]
1057 pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
1058 self.fork_choice = fork_choice.map(Into::into);
1059 self
1060 }
1061
1062 #[must_use]
1064 pub const fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
1065 self.fork_chain_id = fork_chain_id;
1066 self
1067 }
1068
1069 #[must_use]
1071 pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
1072 self.fork_headers = headers;
1073 self
1074 }
1075
1076 #[must_use]
1078 pub const fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
1079 if let Some(fork_request_timeout) = fork_request_timeout {
1080 self.fork_request_timeout = fork_request_timeout;
1081 }
1082 self
1083 }
1084
1085 #[must_use]
1087 pub const fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
1088 if let Some(fork_request_retries) = fork_request_retries {
1089 self.fork_request_retries = fork_request_retries;
1090 }
1091 self
1092 }
1093
1094 #[must_use]
1096 pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
1097 if let Some(fork_retry_backoff) = fork_retry_backoff {
1098 self.fork_retry_backoff = fork_retry_backoff;
1099 }
1100 self
1101 }
1102
1103 #[must_use]
1107 pub const fn fork_compute_units_per_second(
1108 mut self,
1109 compute_units_per_second: Option<u64>,
1110 ) -> Self {
1111 if let Some(compute_units_per_second) = compute_units_per_second {
1112 self.compute_units_per_second = compute_units_per_second;
1113 }
1114 self
1115 }
1116
1117 #[must_use]
1119 pub const fn with_tracing(mut self, enable_tracing: bool) -> Self {
1120 self.enable_tracing = enable_tracing;
1121 self
1122 }
1123
1124 #[must_use]
1126 pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
1127 self.enable_steps_tracing = enable_steps_tracing;
1128 self
1129 }
1130
1131 #[must_use]
1133 pub const fn with_print_logs(mut self, print_logs: bool) -> Self {
1134 self.print_logs = print_logs;
1135 self
1136 }
1137
1138 #[must_use]
1140 pub const fn with_print_traces(mut self, print_traces: bool) -> Self {
1141 self.print_traces = print_traces;
1142 self
1143 }
1144
1145 #[must_use]
1147 pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
1148 self.enable_auto_impersonate = enable_auto_impersonate;
1149 self
1150 }
1151
1152 #[must_use]
1153 pub fn with_server_config(mut self, config: ServerConfig) -> Self {
1154 self.server_config = config;
1155 self
1156 }
1157
1158 #[must_use]
1160 pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
1161 self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
1162 self
1163 }
1164
1165 #[must_use]
1166 pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
1167 self.transaction_order = transaction_order;
1168 self
1169 }
1170
1171 pub fn get_ipc_path(&self) -> Option<String> {
1173 match &self.ipc_path {
1174 Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
1175 None => None,
1176 }
1177 }
1178
1179 pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
1181 if let Some(path) = &self.config_out {
1182 let value = self.as_json(fork);
1183 foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?;
1184 }
1185 if !self.silent {
1186 sh_println!("{}", self.as_string(fork))?;
1187 }
1188 Ok(())
1189 }
1190
1191 pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
1195 self.block_cache_path_for_rpc(self.protocol_chain_id(), block, self.fork_urls.first()?)
1196 }
1197
1198 fn block_cache_path_for_rpc(
1199 &self,
1200 source_chain_id: u64,
1201 block: u64,
1202 rpc_url: &str,
1203 ) -> Option<PathBuf> {
1204 if self.no_storage_caching || self.fork_urls.is_empty() {
1205 return None;
1206 }
1207
1208 let rpc_url_hash = hex::encode(keccak256(rpc_url));
1209 Some(
1210 Config::foundry_block_cache_file(source_chain_id, block)?
1211 .with_file_name(format!("storage-{rpc_url_hash}.json")),
1212 )
1213 }
1214
1215 #[must_use]
1217 pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1218 self.disable_default_create2_deployer = yes;
1219 self
1220 }
1221
1222 #[must_use]
1224 pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1225 self.disable_pool_balance_checks = yes;
1226 self
1227 }
1228
1229 #[must_use]
1231 pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1232 self.precompile_factory = Some(Arc::new(factory));
1233 self
1234 }
1235
1236 #[must_use]
1238 pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1239 self.networks = networks;
1240 self.inferred_fork_network = None;
1241 self.chain_id_network_base = None;
1242 self
1243 }
1244
1245 #[must_use]
1247 pub fn with_tempo(mut self) -> Self {
1248 self.networks = NetworkConfigs::with_tempo();
1249 self.inferred_fork_network = None;
1250 self.chain_id_network_base = None;
1251 self
1252 }
1253
1254 #[must_use]
1256 pub const fn with_tempo_fee_payer(mut self, fee_payer: Option<Address>) -> Self {
1257 self.tempo_fee_payer = fee_payer;
1258 self
1259 }
1260
1261 pub fn tempo_fee_payer_address(&self) -> Option<Address> {
1267 if !self.networks.is_tempo() {
1268 return None;
1269 }
1270 self.tempo_fee_payer.or_else(|| self.genesis_accounts.last().map(|wallet| wallet.address()))
1271 }
1272
1273 #[cfg(feature = "monad")]
1275 #[must_use]
1276 pub fn with_monad(mut self) -> Self {
1277 self.networks = NetworkConfigs::with_monad();
1278 self.inferred_fork_network = None;
1279 self.chain_id_network_base = None;
1280 self
1281 }
1282
1283 #[cfg(feature = "optimism")]
1285 #[must_use]
1286 pub fn with_optimism(mut self) -> Self {
1287 self.networks = NetworkConfigs::with_optimism();
1288 self.inferred_fork_network = None;
1289 self.chain_id_network_base = None;
1290 self
1291 }
1292
1293 #[must_use]
1295 pub const fn silent(self) -> Self {
1296 self.set_silent(true)
1297 }
1298
1299 #[must_use]
1300 pub const fn set_silent(mut self, silent: bool) -> Self {
1301 self.silent = silent;
1302 self
1303 }
1304
1305 #[must_use]
1310 pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1311 self.cache_path = cache_path;
1312 self
1313 }
1314
1315 #[must_use]
1317 pub fn with_funded_accounts(mut self, accounts: HashMap<Address, U256>) -> Self {
1318 self.funded_accounts = accounts;
1319 self
1320 }
1321
1322 pub(crate) async fn setup<N>(
1327 &mut self,
1328 ) -> Result<(mem::Backend<N>, Option<ForkTransactionReplay>)>
1329 where
1330 N: alloy_network::Network<
1331 TxEnvelope = foundry_primitives::FoundryTxEnvelope,
1332 ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope,
1333 >,
1334 {
1335 let mut cfg = CfgEnv::default();
1338 cfg.spec = self.get_hardfork().into();
1339
1340 cfg.chain_id = self.get_chain_id();
1341 cfg.limit_contract_code_size = self.code_size_limit;
1342 cfg.disable_eip3607 = true;
1346 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1347
1348 if !self.enable_tx_gas_limit {
1349 cfg.tx_gas_limit_cap = Some(u64::MAX);
1350 }
1351
1352 if let Some(value) = self.memory_limit {
1353 cfg.memory_limit = value;
1354 }
1355
1356 let spec_id = cfg.spec;
1357 let mut evm_env = EvmEnv::new(
1358 cfg,
1359 BlockEnv {
1360 gas_limit: self.gas_limit(),
1361 basefee: self.get_base_fee(),
1362 ..Default::default()
1363 },
1364 );
1365
1366 self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1367
1368 let genesis_timestamp = self.get_genesis_timestamp();
1369 let base_fee_params: BaseFeeParams = self.networks.base_fee_params(genesis_timestamp);
1370
1371 let tempo_hardfork =
1373 self.networks.is_tempo().then(|| TempoHardfork::from(self.get_hardfork()));
1374
1375 let fees = FeeManager::new(
1376 spec_id,
1377 self.get_base_fee(),
1378 !self.disable_min_priority_fee,
1379 self.get_gas_price(),
1380 self.get_blob_excess_gas_and_price(),
1381 self.get_blob_params(),
1382 base_fee_params,
1383 tempo_hardfork,
1384 );
1385 #[cfg(feature = "optimism")]
1386 if self.networks.is_optimism() {
1387 fees.set_optimism_hardfork(self.get_hardfork());
1388 }
1389
1390 let (db, fork, fork_transaction_replay) =
1391 if let Some(eth_rpc_url) = self.fork_urls.first().cloned() {
1392 self.setup_fork_db_with_replay(eth_rpc_url, &mut evm_env, &fees).await?
1393 } else {
1394 let track_history = self.prune_history.is_state_history_supported();
1395 let db: Arc<TokioRwLock<Box<dyn Db>>> =
1396 Arc::new(TokioRwLock::new(Box::new(StateRootDb::new(track_history))));
1397 (db, None, None)
1398 };
1399
1400 if let Some(ref genesis) = self.genesis {
1402 if self.chain_id.is_none() && fork.is_none() {
1405 evm_env.cfg_env.chain_id = genesis.config.chain_id;
1406 }
1407 evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1408 if let Some(base_fee) = genesis.base_fee_per_gas {
1409 evm_env.block_env.basefee = base_fee.try_into()?;
1410 }
1411 if let Some(number) = genesis.number {
1412 evm_env.block_env.number = U256::from(number);
1413 }
1414 evm_env.block_env.beneficiary = genesis.coinbase;
1415 }
1416
1417 let is_bsc = matches!(
1421 NamedChain::try_from(evm_env.cfg_env.chain_id),
1422 Ok(NamedChain::BinanceSmartChain | NamedChain::BinanceSmartChainTestnet)
1423 );
1424 if fork.is_none() && (self.genesis_timestamp.is_some() || is_bsc) {
1425 evm_env.block_env.timestamp = U256::from(genesis_timestamp);
1426 }
1427
1428 self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1429
1430 let genesis = GenesisConfig {
1431 number: self.get_genesis_number(),
1432 timestamp: genesis_timestamp,
1433 balance: self.genesis_balance,
1434 accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1435 genesis_init: self.genesis.clone(),
1436 };
1437
1438 let active_hardfork = fork
1439 .as_ref()
1440 .and_then(|fork| fork.config.read().hardfork)
1441 .unwrap_or_else(|| self.get_hardfork());
1442 let mut decoder_builder = CallTraceDecoderBuilder::new()
1443 .with_networks(self.networks)
1444 .with_hardfork(Some(self.networks.executed_hardfork(active_hardfork)));
1445 if self.print_traces {
1446 if let Ok(identifier) = SignaturesIdentifier::new(false) {
1448 debug!(target: "node", "using signature identifier");
1449 decoder_builder = decoder_builder.with_signature_identifier(identifier);
1450 }
1451 }
1452
1453 let backend = mem::Backend::with_genesis(
1455 db,
1456 Arc::new(RwLock::new(evm_env)),
1457 self.networks,
1458 genesis,
1459 fees,
1460 Arc::new(RwLock::new(fork)),
1461 self.enable_steps_tracing,
1462 self.print_logs,
1463 self.print_traces,
1464 Arc::new(decoder_builder.build()),
1465 self.prune_history,
1466 self.max_persisted_states,
1467 self.transaction_block_keeper,
1468 self.block_time,
1469 self.cache_path.clone(),
1470 Arc::new(TokioRwLock::new(self.clone())),
1471 )
1472 .await?;
1473
1474 if !self.disable_default_create2_deployer && self.fork_urls.is_empty() {
1477 backend
1478 .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1479 .await
1480 .wrap_err("failed to create default create2 deployer")?;
1481 }
1482
1483 if let Some(fork) = backend.get_fork() {
1484 let config = fork.config.read().clone();
1485 if !self
1486 .fork_urls_match_context(
1487 &config.fork_urls,
1488 config.endpoint_identity,
1489 config.block_number,
1490 config.block_hash,
1491 )
1492 .await?
1493 {
1494 eyre::bail!("fork endpoint changed while Anvil was being initialized");
1495 }
1496 }
1497 Ok((backend, fork_transaction_replay))
1498 }
1499
1500 pub async fn setup_fork_db(
1507 &mut self,
1508 eth_rpc_url: String,
1509 evm_env: &mut EvmEnv,
1510 fees: &FeeManager,
1511 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1512 let (db, fork, replay) = self.setup_fork_db_with_replay(eth_rpc_url, evm_env, fees).await?;
1513 eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1514 Ok((db, fork))
1515 }
1516
1517 async fn setup_fork_db_with_replay(
1518 &mut self,
1519 eth_rpc_url: String,
1520 evm_env: &mut EvmEnv,
1521 fees: &FeeManager,
1522 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>, Option<ForkTransactionReplay>)>
1523 {
1524 let (db, config, replay) =
1525 self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1526 let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1527 let fork = ClientFork::new(config, Arc::clone(&db));
1528 Ok((db, Some(fork), replay))
1529 }
1530
1531 fn fork_provider(&self, eth_rpc_url: &str) -> Result<RetryProvider> {
1532 ProviderBuilder::new(eth_rpc_url)
1533 .timeout(self.fork_request_timeout)
1534 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1535 .compute_units_per_second(self.compute_units_per_second)
1536 .max_retry(self.fork_request_retries)
1537 .headers(self.fork_headers.clone())
1538 .build()
1539 .wrap_err("failed to establish provider to fork url")
1540 }
1541
1542 async fn fork_endpoint_identity(
1543 &self,
1544 provider: &RetryProvider,
1545 fallback_execution_chain_id: u64,
1546 source_chain_id_override: Option<u64>,
1547 node_info_probe: &mut AnvilNodeInfoProbe,
1548 ) -> Result<ForkEndpointIdentity> {
1549 let Some(node_info) = node_info_probe.request(provider).await? else {
1550 let source_chain_id = source_chain_id_override.unwrap_or(fallback_execution_chain_id);
1551 let explicit_fallback = self.has_explicit_network_selection().then_some(self.networks);
1552 let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1553 source_chain_id,
1554 None,
1555 explicit_fallback,
1556 )
1557 .map_err(eyre::Report::msg)?;
1558 return Ok(ForkEndpointIdentity {
1559 execution_chain_id: fallback_execution_chain_id,
1560 source_chain_id,
1561 network: network_profile.map(|profile| profile.execution_network()),
1562 network_profile,
1563 hardfork: None,
1564 instance_id: None,
1565 source_fork_block_number: None,
1566 source_fork_block_hash: None,
1567 });
1568 };
1569
1570 let (
1571 execution_chain_id,
1572 source_chain_id,
1573 instance_id,
1574 source_fork_block_number,
1575 source_fork_block_hash,
1576 ) = match provider.raw_request::<_, Metadata>("anvil_metadata".into(), ()).await {
1577 Ok(metadata) => (
1578 metadata.chain_id,
1579 source_chain_id_override.unwrap_or_else(|| {
1580 metadata.forked_network.map(|fork| fork.chain_id).unwrap_or(metadata.chain_id)
1581 }),
1582 Some(metadata.instance_id),
1583 metadata.forked_network.map(|fork| fork.fork_block_number),
1584 metadata.forked_network.map(|fork| fork.fork_block_hash),
1585 ),
1586 Err(error) if is_rpc_method_not_found(&error) => (
1587 fallback_execution_chain_id,
1588 source_chain_id_override.unwrap_or(fallback_execution_chain_id),
1589 None,
1590 None,
1591 None,
1592 ),
1593 Err(error) => {
1594 return Err(error).wrap_err("failed to retrieve Anvil fork source identity");
1595 }
1596 };
1597 let identity_chain_id =
1598 if node_info.network.is_some() { execution_chain_id } else { source_chain_id };
1599 let explicit_fallback = self.has_explicit_network_selection().then_some(self.networks);
1600 let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1601 identity_chain_id,
1602 Some(node_info.network.as_deref()),
1603 explicit_fallback,
1604 )
1605 .map_err(eyre::Report::msg)?
1606 .ok_or_else(|| eyre::eyre!("Anvil metadata did not identify an execution profile"))?;
1607 let network = network_profile.execution_network();
1608 let hardfork = network
1609 .parse_hardfork(&node_info.hard_fork)
1610 .map_err(eyre::Report::msg)
1611 .wrap_err_with(|| {
1612 format!("unsupported hardfork `{}` reported for `{network}`", node_info.hard_fork)
1613 })?;
1614
1615 Ok(ForkEndpointIdentity {
1616 execution_chain_id,
1617 source_chain_id,
1618 network: Some(network),
1619 network_profile: Some(network_profile),
1620 hardfork: Some(hardfork),
1621 instance_id,
1622 source_fork_block_number,
1623 source_fork_block_hash,
1624 })
1625 }
1626
1627 async fn resolved_fork_endpoint_identity(
1628 &self,
1629 provider: &RetryProvider,
1630 node_info_probe: &mut AnvilNodeInfoProbe,
1631 ) -> Result<ForkEndpointIdentity> {
1632 let identity = if let Some(chain_id) = self.fork_chain_id {
1633 let chain_id = chain_id.to();
1634 self.fork_endpoint_identity(provider, chain_id, Some(chain_id), node_info_probe).await
1638 } else {
1639 let execution_chain_id =
1640 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?;
1641 self.fork_endpoint_identity(provider, execution_chain_id, None, node_info_probe).await
1642 }?;
1643 ensure_fork_network_supported(identity.source_chain_id)?;
1644 Ok(identity)
1645 }
1646
1647 pub(crate) async fn replacement_fork_provider(
1648 &self,
1649 eth_rpc_url: &str,
1650 expected: ForkEndpointIdentity,
1651 block_number: u64,
1652 block_hash: B256,
1653 serving_instance_id: B256,
1654 ) -> Result<(Arc<RetryProvider>, ForkEndpointIdentity)> {
1655 let provider = Arc::new(self.fork_provider(eth_rpc_url)?);
1656 let mut node_info_probe = AnvilNodeInfoProbe::default();
1657 for _ in 0..3 {
1658 let before =
1659 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1660 eyre::ensure!(
1661 before.instance_id != Some(serving_instance_id),
1662 "cannot set Anvil's fork provider to its own RPC endpoint"
1663 );
1664 let block = provider
1665 .get_block(BlockNumberOrTag::Number(block_number).into())
1666 .await
1667 .wrap_err("failed to confirm active fork block on replacement endpoint")?;
1668 let after =
1669 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1670 if before != after {
1671 continue;
1672 }
1673 if !before.context_eq(expected) {
1674 eyre::bail!("replacement fork endpoint has an incompatible execution context");
1675 }
1676 let actual_hash = block.map(|block| block.header.hash);
1677 if actual_hash != Some(block_hash) {
1678 eyre::bail!(
1679 "replacement fork endpoint does not contain active fork block {block_number} with hash {block_hash}"
1680 );
1681 }
1682 return Ok((provider, before));
1683 }
1684 eyre::bail!(
1685 "fork endpoint changed while its identity and active fork block were being resolved"
1686 );
1687 }
1688
1689 async fn stable_fork_snapshot(
1690 &self,
1691 provider: &Arc<RetryProvider>,
1692 fork_overrides: ForkOverrides,
1693 ) -> Result<StableForkSnapshot> {
1694 let mut node_info_probe = AnvilNodeInfoProbe::new(self.fork_endpoint_is_anvil);
1695 for _ in 0..3 {
1696 let before =
1697 self.resolved_fork_endpoint_identity(provider, &mut node_info_probe).await?;
1698 let (block_number, transaction_replay) = if let Some(fork_choice) = &self.fork_choice {
1699 derive_block_and_replay(fork_choice, provider).await.wrap_err(
1700 "failed to derive fork block and transaction replay from fork choice",
1701 )?
1702 } else {
1703 (
1704 find_latest_fork_block(provider)
1705 .await
1706 .wrap_err("failed to get fork block number")?,
1707 None,
1708 )
1709 };
1710 let block = provider
1711 .get_block(BlockNumberOrTag::Number(block_number).into())
1712 .await
1713 .wrap_err("failed to get fork block")?;
1714 let gas_price = if let Some(gas_price) = fork_overrides.gas_price {
1715 gas_price
1716 } else {
1717 provider.get_gas_price().await.unwrap_or(INITIAL_GAS_PRICE)
1718 };
1719 let after =
1720 self.resolved_fork_endpoint_identity(provider, &mut node_info_probe).await?;
1721 if before == after {
1722 return Ok(StableForkSnapshot {
1723 endpoint_identity: before,
1724 block_number,
1725 transaction_replay,
1726 block,
1727 gas_price,
1728 });
1729 }
1730 }
1731 eyre::bail!(
1732 "fork endpoint changed while its identity and block context were being resolved"
1733 );
1734 }
1735
1736 pub(crate) async fn fork_context_matches(
1737 &self,
1738 eth_rpc_url: &str,
1739 expected: ForkEndpointIdentity,
1740 block_number: u64,
1741 block_hash: B256,
1742 ) -> Result<bool> {
1743 let provider = self.fork_provider(eth_rpc_url)?;
1744 let mut node_info_probe = AnvilNodeInfoProbe::new(expected.is_authoritative());
1745 for _ in 0..3 {
1746 let before =
1747 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1748 let block = provider
1749 .get_block(BlockNumberOrTag::Number(block_number).into())
1750 .await
1751 .wrap_err("failed to confirm fork block context")?;
1752 let after =
1753 self.resolved_fork_endpoint_identity(&provider, &mut node_info_probe).await?;
1754 if before != after {
1755 continue;
1756 }
1757 if before.source_chain_id != expected.source_chain_id {
1758 eyre::bail!(
1759 "fork endpoints must use the same chain ID: expected {}, got {} from {}",
1760 expected.source_chain_id,
1761 before.source_chain_id,
1762 redact_url(eth_rpc_url)
1763 );
1764 }
1765 return Ok(
1766 before == expected && block.is_some_and(|block| block.header.hash == block_hash)
1767 );
1768 }
1769 Ok(false)
1770 }
1771
1772 pub(crate) async fn fork_urls_match_context(
1773 &self,
1774 fork_urls: &[String],
1775 expected: ForkEndpointIdentity,
1776 block_number: u64,
1777 block_hash: B256,
1778 ) -> Result<bool> {
1779 for eth_rpc_url in Self::fork_urls_requiring_revalidation(fork_urls, expected) {
1780 if !self.fork_context_matches(eth_rpc_url, expected, block_number, block_hash).await? {
1781 return Ok(false);
1782 }
1783 }
1784 Ok(true)
1785 }
1786
1787 const fn fork_urls_requiring_revalidation(
1788 fork_urls: &[String],
1789 endpoint_identity: ForkEndpointIdentity,
1790 ) -> &[String] {
1791 if endpoint_identity.is_authoritative() || fork_urls.len() > 1 { fork_urls } else { &[] }
1792 }
1793
1794 pub(crate) fn has_explicit_network_selection(&self) -> bool {
1795 let effective_network =
1796 self.networks.resolved_network().unwrap_or(NetworkVariant::Ethereum);
1797 self.networks.has_network_selection()
1798 && self.chain_id_network_base.is_none()
1799 && self.inferred_fork_network != Some(effective_network)
1800 }
1801
1802 const fn requires_primary_fork_revalidation(
1803 &self,
1804 endpoint_identity: ForkEndpointIdentity,
1805 ) -> bool {
1806 endpoint_identity.is_authoritative() || self.fork_urls.len() > 1
1807 }
1808
1809 pub async fn setup_fork_db_config(
1815 &mut self,
1816 eth_rpc_url: String,
1817 evm_env: &mut EvmEnv,
1818 fees: &FeeManager,
1819 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1820 let (db, config, replay) =
1821 self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1822 eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1823 Ok((db, config))
1824 }
1825
1826 pub(crate) async fn setup_fork_db_config_with_replay(
1827 &mut self,
1828 eth_rpc_url: String,
1829 evm_env: &mut EvmEnv,
1830 fees: &FeeManager,
1831 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig, Option<ForkTransactionReplay>)> {
1832 debug!(target: "node", eth_rpc_url=%redact_url(ð_rpc_url), "setting up fork db");
1833 if self.fork_chain_id.is_some() {
1834 eyre::ensure!(
1835 self.fork_urls.len() == 1,
1836 "multiple fork URLs cannot be validated with --fork-chain-id; remove \
1837 --fork-chain-id to validate every endpoint"
1838 );
1839 }
1840 let fork_overrides = *self.fork_overrides.get_or_insert(ForkOverrides {
1841 gas_limit: self.gas_limit,
1842 gas_price: self.gas_price,
1843 base_fee: self.base_fee,
1844 });
1845
1846 let provider = Arc::new(self.fork_provider(ð_rpc_url)?);
1850
1851 let StableForkSnapshot {
1854 endpoint_identity: fork_identity,
1855 block_number: fork_block_number,
1856 transaction_replay: fork_transaction_replay,
1857 block,
1858 gas_price,
1859 } = self.stable_fork_snapshot(&provider, fork_overrides).await?;
1860 self.fork_endpoint_is_anvil = fork_identity.is_authoritative();
1861
1862 let target_network = fork_identity.network.unwrap_or(NetworkVariant::Ethereum);
1863 let target_profile = fork_identity.network_profile.unwrap_or_default();
1864 if self.inferred_fork_network.is_some()
1865 && !self.networks.supports_fork_source(&target_profile)
1866 {
1867 eyre::bail!(
1868 "cannot reset Anvil across network families ({} -> {}); start a new instance \
1869 with matching network configuration",
1870 self.networks.execution_profile_name(),
1871 target_profile.execution_profile_name()
1872 );
1873 }
1874 let source_chain_id = fork_identity.source_chain_id;
1875 self.fork_source_chain_id = Some(source_chain_id);
1876 self.fork_execution_chain_id = Some(fork_identity.execution_chain_id);
1877 if !self.has_explicit_network_selection() {
1878 self.networks = self.networks.with_rpc_profile(target_profile);
1879 self.inferred_fork_network = Some(target_network);
1880 self.chain_id_network_base = None;
1881 }
1882
1883 let block = if let Some(block) = block {
1884 block
1885 } else {
1886 if let Ok(latest_block) = provider.get_block_number().await {
1887 let mut message = format!(
1888 "Failed to get block for block number: {fork_block_number}\n\
1889latest block number: {latest_block}"
1890 );
1891 if fork_block_number <= latest_block {
1895 message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
1896 }
1897 eyre::bail!("{message}");
1898 }
1899 eyre::bail!("failed to get block for block number: {fork_block_number}");
1900 };
1901
1902 if let Some(replay) = &fork_transaction_replay {
1903 let source_header = replay.source_block.header();
1904 eyre::ensure!(
1905 block.header.hash == source_header.parent_hash,
1906 "fork transaction block {} at {} has parent {}, but fetched fork block at {} has \
1907 hash {}",
1908 source_header.hash,
1909 source_header.number,
1910 source_header.parent_hash,
1911 block.header.number,
1912 block.header.hash
1913 );
1914 eyre::ensure!(
1915 block.header.number.checked_add(1) == Some(source_header.number),
1916 "fork transaction block {} has number {}, but fetched parent {} has number {}",
1917 source_header.hash,
1918 source_header.number,
1919 block.header.hash,
1920 block.header.number
1921 );
1922 }
1923
1924 let gas_limit = self.fork_gas_limit_with_override(&block, fork_overrides.gas_limit);
1925 self.gas_limit = Some(gas_limit);
1926
1927 let cache_block_env: BlockEnv = block_env_from_header(&block.header);
1930
1931 evm_env.block_env = BlockEnv {
1932 gas_limit,
1933 beneficiary: evm_env.block_env.beneficiary,
1935 basefee: fork_overrides
1936 .base_fee
1937 .or_else(|| block.header.base_fee_per_gas())
1938 .unwrap_or_default(),
1939 ..block_env_from_header(&block.header)
1940 };
1941
1942 let override_chain_id = self.chain_id;
1943 let execution_chain_id = override_chain_id.unwrap_or(fork_identity.execution_chain_id);
1944 if override_chain_id.is_none() {
1945 self.update_wallet_chain_id(fork_identity.execution_chain_id);
1948 }
1949 evm_env.cfg_env.chain_id = execution_chain_id;
1950
1951 let effective_network =
1955 self.networks.resolved_network().unwrap_or(NetworkVariant::Ethereum);
1956 let endpoint_matches_execution = fork_identity.network == Some(effective_network);
1957 let source_hardfork = fork_identity.hardfork.or_else(|| {
1958 FoundryHardfork::from_chain_and_timestamp(source_chain_id, block.header.timestamp())
1959 });
1960 let inferred_hardfork = source_hardfork.filter(|hardfork| {
1961 endpoint_matches_execution
1962 && hardfork.namespace() == effective_network.hardfork_namespace()
1963 });
1964 let source_may_omit_blob_fields = source_hardfork
1965 .map_or(self.hardfork.is_some(), |hardfork| SpecId::from(hardfork) < SpecId::CANCUN);
1966 let fork_hardfork = self.hardfork.or(inferred_hardfork);
1967 let effective_hardfork = fork_hardfork.unwrap_or_else(|| self.get_hardfork());
1968 let effective_spec = SpecId::from(effective_hardfork);
1969 evm_env.cfg_env.set_spec_and_mainnet_gas_params(effective_spec);
1970 fees.set_execution_rules(
1971 effective_spec,
1972 self.networks.base_fee_params(block.header.timestamp()),
1973 self.networks.is_tempo().then(|| TempoHardfork::from(effective_hardfork)),
1974 );
1975 #[cfg(feature = "optimism")]
1976 if self.networks.is_optimism() {
1977 fees.set_optimism_base_fee_rules(block.header.extra_data());
1978 }
1979
1980 self.base_fee = fork_overrides.base_fee.or_else(|| block.header.base_fee_per_gas());
1982 if let Some(base_fee) = fork_overrides.base_fee {
1983 fees.set_base_fee(base_fee);
1984 } else if let Some(base_fee) = block.header.base_fee_per_gas() {
1985 fees.set_base_fee(base_fee);
1988 let next_block_base_fee = fees.get_next_block_base_fee_from_header(&block.header);
1989 fees.set_base_fee(next_block_base_fee);
1990 } else {
1991 fees.set_base_fee(self.get_base_fee());
1992 }
1993
1994 let blob_params = get_blob_params(source_chain_id, block.header.timestamp());
1998 fees.set_blob_params(blob_params);
1999 let blob_update_fraction = blob_params.update_fraction as u64;
2000 let blob_excess_gas = block.header.excess_blob_gas().or_else(|| {
2001 (effective_spec >= SpecId::CANCUN
2007 && ((source_may_omit_blob_fields && block.header.blob_gas_used().is_none())
2008 || Chain::from_id(source_chain_id).is_polygon()
2009 || Chain::from_id(source_chain_id).is_arbitrum()))
2010 .then_some(0)
2011 });
2012 evm_env.block_env.blob_excess_gas_and_price =
2013 blob_excess_gas.map(|excess| BlobExcessGasAndPrice::new(excess, blob_update_fraction));
2014 let next_block_blob_excess_gas = blob_excess_gas.map_or(0, |excess| {
2015 self.networks.next_block_blob_excess_gas(
2016 blob_params,
2017 excess,
2018 block.header.blob_gas_used().unwrap_or_default(),
2019 block.header.base_fee_per_gas().unwrap_or_default(),
2020 )
2021 });
2022 fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
2023 next_block_blob_excess_gas,
2024 blob_update_fraction,
2025 ));
2026
2027 self.gas_price = Some(gas_price);
2029 fees.set_gas_price(gas_price);
2030
2031 let block_hash = block.header.hash;
2032
2033 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
2035 evm_env,
2036 &block,
2037 source_chain_id,
2038 self.networks,
2039 );
2040
2041 for mirror_url in self.fork_urls.iter().skip(1) {
2042 if !self
2043 .fork_context_matches(mirror_url, fork_identity, fork_block_number, block_hash)
2044 .await?
2045 {
2046 eyre::bail!(
2047 "fork fallback endpoint `{}` does not expose the primary endpoint's execution \
2048 and block context",
2049 redact_url(mirror_url)
2050 );
2051 }
2052 }
2053 if self.requires_primary_fork_revalidation(fork_identity)
2054 && !self
2055 .fork_context_matches(ð_rpc_url, fork_identity, fork_block_number, block_hash)
2056 .await?
2057 {
2058 eyre::bail!("primary fork endpoint changed while its context was being validated");
2059 }
2060
2061 let source_id = fork_source_id(&self.fork_urls, &self.fork_headers);
2062 let meta = BlockchainDbMeta::new(cache_block_env, eth_rpc_url.clone())
2063 .with_fork_identity(block_hash, source_id);
2064 let cache_path =
2065 self.block_cache_path_for_rpc(source_chain_id, fork_block_number, ð_rpc_url);
2066 let block_chain_db = BlockchainDb::new(meta, cache_path);
2067
2068 let provider = if self.fork_urls.len() > 1 {
2072 let urls = self.fork_urls.iter().map(|url| redact_url(url)).collect::<Vec<_>>();
2073 debug!(target: "node", ?urls, "using multi-endpoint round-robin provider");
2074 Arc::new(
2075 ProviderBuilder::new(ð_rpc_url)
2076 .timeout(self.fork_request_timeout)
2077 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
2078 .compute_units_per_second(self.compute_units_per_second)
2079 .max_retry(self.fork_request_retries)
2080 .headers(self.fork_headers.clone())
2081 .build_fallback(self.fork_urls.clone())
2082 .wrap_err("failed to establish round-robin provider to fork urls")?,
2083 )
2084 } else {
2085 provider
2086 };
2087
2088 let anchor = ForkBlock::with_rpc_number(
2091 evm_env.block_env.number.saturating_to(),
2092 fork_block_number,
2093 block_hash,
2094 );
2095 let (backend, handler) =
2096 SharedBackend::new_with_anchor(Arc::clone(&provider), block_chain_db.clone(), anchor)?;
2097 tokio::spawn(handler);
2098
2099 let config = ClientForkConfig {
2100 fork_urls: self.fork_urls.clone(),
2101 block_number: fork_block_number,
2102 block_hash,
2103 transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
2104 provider,
2105 chain_id: source_chain_id,
2106 execution_chain_id,
2107 override_chain_id,
2108 fork_chain_id: self.fork_chain_id.map(|chain_id| chain_id.to()),
2109 hardfork: Some(effective_hardfork),
2110 endpoint_identity: fork_identity,
2111 timestamp: block.header.timestamp(),
2112 base_fee: block.header.base_fee_per_gas().map(|g| g as u128),
2113 timeout: self.fork_request_timeout,
2114 retries: self.fork_request_retries,
2115 backoff: self.fork_retry_backoff,
2116 compute_units_per_second: self.compute_units_per_second,
2117 headers: self.fork_headers.clone(),
2118 total_difficulty: block.header.total_difficulty.unwrap_or_default(),
2119 blob_gas_used: block.header.blob_gas_used().map(|g| g as u128),
2120 blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price,
2121 };
2122
2123 debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
2124
2125 let mut db = ForkedDatabase::new(backend, block_chain_db);
2126
2127 db.insert_block_hash(U256::from(config.block_number), config.block_hash);
2129
2130 Ok((db, config, fork_transaction_replay))
2131 }
2132
2133 fn fork_gas_limit_with_override<B: BlockResponse<Header: BlockHeader>>(
2137 &self,
2138 block: &B,
2139 gas_limit: Option<u64>,
2140 ) -> u64 {
2141 if !self.disable_block_gas_limit {
2142 if let Some(gas_limit) = gas_limit {
2143 return gas_limit;
2144 } else if block.header().gas_limit() > 0 {
2145 return block.header().gas_limit();
2146 }
2147 }
2148
2149 u64::MAX
2150 }
2151
2152 pub(crate) const fn restore_fork_overrides(&mut self) {
2154 if let Some(overrides) = self.fork_overrides {
2155 self.gas_limit = overrides.gas_limit;
2156 self.gas_price = overrides.gas_price;
2157 self.base_fee = overrides.base_fee;
2158 }
2159 }
2160
2161 pub(crate) fn gas_limit(&self) -> u64 {
2165 if self.disable_block_gas_limit {
2166 return u64::MAX;
2167 }
2168
2169 self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
2170 }
2171}
2172
2173pub(crate) const fn tempo_default_base_fee(hardfork: TempoHardfork) -> u64 {
2174 if hardfork.is_t1() { TEMPO_T1_BASE_FEE } else { TEMPO_T0_BASE_FEE }
2175}
2176
2177async fn derive_block_and_replay(
2182 fork_choice: &ForkChoice,
2183 provider: &Arc<RetryProvider>,
2184) -> eyre::Result<(BlockNumber, Option<ForkTransactionReplay>)> {
2185 match fork_choice {
2186 ForkChoice::Block(block_number) => {
2187 let block_number = *block_number;
2188 if block_number >= 0 {
2189 return Ok((block_number as u64, None));
2190 }
2191 let latest = provider.get_block_number().await?;
2193
2194 Ok((block_number.saturating_add(latest as i128) as u64, None))
2195 }
2196 ForkChoice::Transaction(transaction_hash) => {
2197 let transaction = provider
2199 .get_transaction_by_hash(transaction_hash.0.into())
2200 .await?
2201 .ok_or_else(|| eyre::eyre!("fork transaction {transaction_hash} was not found"))?;
2202 let transaction_block_number = transaction.block_number().ok_or_else(|| {
2203 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
2204 })?;
2205 let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
2206 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
2207 })?;
2208
2209 let transaction_block =
2211 provider.get_block_by_hash(transaction_block_hash).full().await?.ok_or_else(
2212 || {
2213 eyre::eyre!(
2214 "failed to get fork block {transaction_block_hash} for transaction \
2215 {transaction_hash}"
2216 )
2217 },
2218 )?;
2219 let replay = validate_fork_transaction_replay(
2220 *transaction_hash,
2221 &transaction,
2222 transaction_block,
2223 )?;
2224 Ok((transaction_block_number.saturating_sub(1), Some(replay)))
2225 }
2226 }
2227}
2228
2229fn validate_fork_transaction_replay(
2230 transaction_hash: TxHash,
2231 transaction: &alloy_network::AnyRpcTransaction,
2232 source_block: AnyRpcBlock,
2233) -> eyre::Result<ForkTransactionReplay> {
2234 let source_hash = source_block.header.hash;
2235 let source_number = source_block.header.number;
2236 let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
2237 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
2238 })?;
2239 let transaction_block_number = transaction.block_number().ok_or_else(|| {
2240 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
2241 })?;
2242
2243 eyre::ensure!(
2244 source_hash == transaction_block_hash,
2245 "fork transaction {transaction_hash} reports block {transaction_block_hash}, but fetched \
2246 block hash is {source_hash}"
2247 );
2248 eyre::ensure!(
2249 source_number == transaction_block_number,
2250 "fork transaction {transaction_hash} reports block number {transaction_block_number}, but \
2251 fetched block {source_hash} has number {source_number}"
2252 );
2253 eyre::ensure!(
2254 source_number > 0,
2255 "fork transaction {transaction_hash} is in genesis block {source_hash}, which has no parent"
2256 );
2257
2258 let transactions = source_block.transactions.as_transactions().ok_or_else(|| {
2259 eyre::eyre!("fork block {source_hash} at {source_number} did not include full transactions")
2260 })?;
2261 let mut matches =
2262 transactions.iter().enumerate().filter(|(_, tx)| tx.tx_hash() == transaction_hash);
2263 let target_index = matches.next().map(|(index, _)| index).ok_or_else(|| {
2264 eyre::eyre!(
2265 "fork transaction {transaction_hash} is absent from block {source_hash} at \
2266 {source_number}"
2267 )
2268 })?;
2269 eyre::ensure!(
2270 matches.next().is_none(),
2271 "fork transaction {transaction_hash} occurs more than once in block {source_hash} at \
2272 {source_number}"
2273 );
2274 if let Some(reported_index) = transaction.transaction_index() {
2275 eyre::ensure!(
2276 reported_index == target_index as u64,
2277 "fork transaction {transaction_hash} reports index {reported_index}, but occurs at \
2278 index {target_index} in block {source_hash}"
2279 );
2280 }
2281
2282 Ok(ForkTransactionReplay { source_block, target_index })
2283}
2284
2285#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2287pub enum ForkChoice {
2288 Block(i128),
2292 Transaction(TxHash),
2294}
2295
2296impl ForkChoice {
2297 pub const fn block_number(&self) -> Option<i128> {
2299 match self {
2300 Self::Block(block_number) => Some(*block_number),
2301 Self::Transaction(_) => None,
2302 }
2303 }
2304
2305 pub const fn transaction_hash(&self) -> Option<TxHash> {
2307 match self {
2308 Self::Block(_) => None,
2309 Self::Transaction(transaction_hash) => Some(*transaction_hash),
2310 }
2311 }
2312}
2313
2314impl From<TxHash> for ForkChoice {
2316 fn from(tx_hash: TxHash) -> Self {
2317 Self::Transaction(tx_hash)
2318 }
2319}
2320
2321impl From<u64> for ForkChoice {
2323 fn from(block: u64) -> Self {
2324 Self::Block(block as i128)
2325 }
2326}
2327
2328#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
2329pub struct PruneStateHistoryConfig {
2330 pub enabled: bool,
2331 pub max_memory_history: Option<usize>,
2332}
2333
2334impl PruneStateHistoryConfig {
2335 pub const fn is_state_history_supported(&self) -> bool {
2337 if !self.enabled {
2338 return true;
2339 }
2340
2341 match self.max_memory_history {
2342 Some(limit) => limit > 0,
2343 None => false,
2344 }
2345 }
2346
2347 pub const fn is_config_enabled(&self) -> bool {
2349 self.enabled
2350 }
2351
2352 pub fn from_args(val: Option<Option<usize>>) -> Self {
2353 val.map(|max_memory_history| Self {
2354 enabled: true,
2355 max_memory_history: max_memory_history.filter(|limit| *limit > 0),
2356 })
2357 .unwrap_or_default()
2358 }
2359}
2360
2361#[derive(Clone, Debug)]
2363pub struct AccountGenerator {
2364 chain_id: u64,
2365 amount: usize,
2366 phrase: String,
2367 derivation_path: Option<String>,
2368}
2369
2370impl AccountGenerator {
2371 pub fn new(amount: usize) -> Self {
2372 Self {
2373 chain_id: CHAIN_ID,
2374 amount,
2375 phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
2376 derivation_path: None,
2377 }
2378 }
2379
2380 #[must_use]
2381 pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
2382 self.phrase = phrase.into();
2383 self
2384 }
2385
2386 fn get_phrase(&self) -> &str {
2387 &self.phrase
2388 }
2389
2390 #[must_use]
2391 pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
2392 self.chain_id = chain_id.into();
2393 self
2394 }
2395
2396 #[must_use]
2397 pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
2398 let mut derivation_path = derivation_path.into();
2399 if !derivation_path.ends_with('/') {
2400 derivation_path.push('/');
2401 }
2402 self.derivation_path = Some(derivation_path);
2403 self
2404 }
2405
2406 fn get_derivation_path(&self) -> &str {
2407 self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
2408 }
2409}
2410
2411impl AccountGenerator {
2412 pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
2413 let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
2414
2415 let derivation_path = self.get_derivation_path();
2417 foundry_common::wallet::validate_bip32_path(derivation_path).map_err(|e| eyre::eyre!(e))?;
2418
2419 let mut wallets = Vec::with_capacity(self.amount);
2420 for idx in 0..self.amount {
2421 let idx = u32::try_from(idx).map_err(|_| eyre::eyre!("account index overflows u32"))?;
2422 let full_path = foundry_common::wallet::derive_key_path_checked(derivation_path, idx)
2423 .map_err(|e| eyre::eyre!(e))?;
2424 let builder = builder.clone().derivation_path(full_path)?;
2425 let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
2426 wallets.push(wallet)
2427 }
2428 Ok(wallets)
2429 }
2430}
2431
2432pub fn anvil_dir() -> Option<PathBuf> {
2434 Config::foundry_dir().map(|p| p.join("anvil"))
2435}
2436
2437pub fn anvil_tmp_dir() -> Option<PathBuf> {
2439 anvil_dir().map(|p| p.join("tmp"))
2440}
2441
2442async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
2447 provider: P,
2448) -> Result<u64, TransportError> {
2449 let mut num = provider.get_block_number().await?;
2450
2451 for _ in 0..2 {
2454 if let Some(block) = provider.get_block(num.into()).await?
2455 && !block.header.hash.is_zero()
2456 {
2457 break;
2458 }
2459 num = num.saturating_sub(1)
2461 }
2462
2463 Ok(num)
2464}
2465
2466#[cfg(test)]
2467mod tests {
2468 #[cfg(feature = "optimism")]
2469 use foundry_evm::hardfork::OpHardfork;
2470 use foundry_evm::{hardfork::EthereumHardfork, hardforks::latest_active_tempo_hardfork};
2471
2472 use super::*;
2473
2474 #[tokio::test(flavor = "multi_thread")]
2475 async fn fork_output_redacts_endpoint_credentials() {
2476 let (_api, source) = crate::spawn(NodeConfig::test()).await;
2477 let fork_url = source.http_endpoint().replacen("http://", "http://user:password@", 1)
2478 + "/?api_key=secret";
2479 let mut config = NodeConfig::test().with_eth_rpc_url(Some(fork_url.clone()));
2480 let (api, _handle) = crate::spawn(config.clone()).await;
2481 config.fork_urls.push("https://mirror.example/private-api-key?token=secret".to_string());
2482
2483 let fork = api.backend.get_fork().unwrap();
2484 let output = config.as_string(Some(&fork));
2485 let temp = tempfile::tempdir().unwrap();
2486 let config_out = temp.path().join("config.json");
2487 config.config_out = Some(config_out.clone());
2488 config.print(Some(&fork)).unwrap();
2489 let json = serde_json::from_slice::<Value>(&std::fs::read(config_out).unwrap()).unwrap();
2490
2491 assert!(output.contains(&redact_url(&fork_url)));
2492 assert!(output.contains("https://mirror.example/"));
2493 assert!(!output.contains("user"));
2494 assert!(!output.contains("password"));
2495 assert!(!output.contains("private-api-key"));
2496 assert!(!output.contains("secret"));
2497 assert_eq!(json["endpoint"], redact_url(&fork_url));
2498 assert!(!json.to_string().contains("password"));
2499 assert!(!json.to_string().contains("secret"));
2500 }
2501
2502 #[test]
2503 fn fork_source_identity_includes_all_urls_and_headers() {
2504 let urls = ["http://primary".to_string(), "http://fallback".to_string()];
2505 let headers = ["Authorization: secret".to_string()];
2506 let identity = fork_source_id(&urls, &headers);
2507
2508 assert_ne!(identity, fork_source_id(&urls[..1], &headers));
2509 assert_ne!(identity, fork_source_id(&urls, &[]));
2510 assert_ne!(identity, fork_source_id(&[urls[1].clone(), urls[0].clone()], &headers));
2511 }
2512
2513 #[test]
2514 fn test_prune_history() {
2515 let config = PruneStateHistoryConfig::default();
2516 assert!(config.is_state_history_supported());
2517 let config = PruneStateHistoryConfig::from_args(Some(None));
2518 assert!(!config.is_state_history_supported());
2519 let config = PruneStateHistoryConfig::from_args(Some(Some(0)));
2520 assert!(config.is_config_enabled());
2521 assert!(!config.is_state_history_supported());
2522 let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
2523 assert!(config.is_state_history_supported());
2524 }
2525
2526 #[test]
2527 fn fork_cache_path_can_use_source_chain() {
2528 let rpc_url = "http://localhost:8545";
2529 let mut config = NodeConfig::test()
2530 .with_eth_rpc_url(Some(rpc_url.to_string()))
2531 .with_chain_id(Some(1u64));
2532 let block = 42;
2533 config.fork_source_chain_id = Some(143);
2534 let expected = Config::foundry_block_cache_file(143, block).map(|path| {
2535 path.with_file_name(format!("storage-{}.json", hex::encode(keccak256(rpc_url))))
2536 });
2537
2538 assert_eq!(config.block_cache_path(block), expected);
2539 assert_ne!(
2540 config.block_cache_path_for_rpc(143, block, rpc_url),
2541 config.block_cache_path_for_rpc(143, block, "http://localhost:8546")
2542 );
2543 }
2544
2545 #[test]
2546 fn fork_execution_and_source_chain_ids_remain_distinct() {
2547 let mut config = NodeConfig::test();
2548 config.fork_execution_chain_id = Some(1);
2549 config.fork_source_chain_id = Some(143);
2550
2551 assert_eq!(config.get_chain_id(), 1);
2552 assert_eq!(config.protocol_chain_id(), 143);
2553 }
2554
2555 #[test]
2556 fn fork_chain_id_is_only_an_offline_discovery_hint() {
2557 let mut config = NodeConfig::test()
2558 .with_chain_id(Some(31_337u64))
2559 .with_fork_chain_id(Some(U256::from(143)));
2560
2561 assert_eq!(config.protocol_chain_id(), 31_337);
2562
2563 config.fork_source_chain_id = Some(143);
2564 assert_eq!(config.protocol_chain_id(), 143);
2565
2566 config.fork_source_chain_id = None;
2567 assert_eq!(config.protocol_chain_id(), 31_337);
2568 }
2569
2570 #[test]
2571 fn fork_endpoint_revalidation_requires_authority_or_fallbacks() {
2572 let anonymous = ForkEndpointIdentity {
2573 execution_chain_id: 1,
2574 source_chain_id: 1,
2575 network: Some(NetworkVariant::Ethereum),
2576 network_profile: Some(NetworkConfigs::default()),
2577 hardfork: None,
2578 instance_id: None,
2579 source_fork_block_number: None,
2580 source_fork_block_hash: None,
2581 };
2582 let mut config =
2583 NodeConfig::test().with_eth_rpc_url(Some("http://localhost:8545".to_string()));
2584
2585 assert!(!anonymous.is_authoritative());
2586 assert!(!config.requires_primary_fork_revalidation(anonymous));
2587
2588 config.fork_urls.push("http://localhost:8546".to_string());
2589 assert!(config.requires_primary_fork_revalidation(anonymous));
2590 assert_eq!(
2591 NodeConfig::fork_urls_requiring_revalidation(&config.fork_urls, anonymous),
2592 config.fork_urls
2593 );
2594
2595 config.fork_urls.pop();
2596 let authoritative =
2597 ForkEndpointIdentity { hardfork: Some(EthereumHardfork::Prague.into()), ..anonymous };
2598 assert!(authoritative.is_authoritative());
2599 assert!(config.requires_primary_fork_revalidation(authoritative));
2600 assert_eq!(
2601 NodeConfig::fork_urls_requiring_revalidation(&config.fork_urls, authoritative),
2602 config.fork_urls
2603 );
2604 }
2605
2606 #[tokio::test]
2607 async fn fork_authoritative_identity_keeps_node_info_probe_strict() {
2608 let (_api, origin) =
2609 crate::spawn(NodeConfig::test().with_chain_id(Some(NamedChain::Mainnet as u64))).await;
2610 let fork_url = foundry_test_utils::rpc::spawn_rpc_proxy_rejecting_method_after(
2611 origin.http_endpoint(),
2612 "anvil_nodeInfo",
2613 0,
2614 )
2615 .await;
2616 let expected = ForkEndpointIdentity {
2617 execution_chain_id: NamedChain::Mainnet as u64,
2618 source_chain_id: NamedChain::Mainnet as u64,
2619 network: Some(NetworkVariant::Ethereum),
2620 network_profile: Some(NetworkConfigs::default()),
2621 hardfork: Some(EthereumHardfork::Prague.into()),
2622 instance_id: Some(B256::with_last_byte(1)),
2623 source_fork_block_number: None,
2624 source_fork_block_hash: None,
2625 };
2626
2627 let error = NodeConfig::test()
2628 .fork_context_matches(&fork_url, expected, 0, B256::ZERO)
2629 .await
2630 .unwrap_err();
2631
2632 assert!(
2633 error.to_string().contains("failed to determine network family from fork endpoint"),
2634 "{error}"
2635 );
2636 }
2637
2638 #[cfg(feature = "optimism")]
2639 #[test]
2640 fn set_chain_id_updates_network_config() {
2641 let mut config = NodeConfig::test();
2642 config.set_chain_id(Some(10u64));
2643
2644 assert!(config.networks.is_optimism());
2645 }
2646
2647 #[test]
2648 fn chain_id_network_inference_is_replaceable_and_clearable() {
2649 let mut config = NodeConfig::test();
2650 config.set_chain_id(Some(4217u64));
2651 assert!(config.networks.is_tempo());
2652
2653 config.set_chain_id(Some(NamedChain::Celo as u64));
2654 assert!(config.networks.is_celo());
2655 assert!(!config.networks.is_tempo());
2656
2657 config.set_chain_id(Some(1u64));
2658 assert!(!config.networks.has_network_selection());
2659
2660 config.set_chain_id(Some(4217u64));
2661 config.set_chain_id(None::<u64>);
2662 assert!(!config.networks.has_network_selection());
2663 }
2664
2665 #[test]
2666 fn chain_id_preserves_explicit_network_selection() {
2667 let mut config = NodeConfig::test_tempo();
2668 config.set_chain_id(Some(NamedChain::Celo as u64));
2669
2670 assert!(config.networks.is_tempo());
2671 assert!(!config.networks.is_celo());
2672 }
2673
2674 #[test]
2675 fn get_hardfork_on_tempo_never_returns_non_tempo_variant() {
2676 let shanghai_ts = 1_681_338_455u64;
2678
2679 let config = NodeConfig::test_tempo()
2680 .with_chain_id(Some(1u64))
2681 .with_genesis_timestamp(Some(shanghai_ts));
2682
2683 assert!(config.networks.is_tempo());
2684 assert!(matches!(config.get_hardfork(), FoundryHardfork::Tempo(_)));
2685 }
2686
2687 #[test]
2688 fn get_hardfork_on_ethereum_uses_genesis_timestamp() {
2689 let timestamp = EthereumHardfork::Shanghai.mainnet_activation_timestamp().unwrap();
2690 let config =
2691 NodeConfig::test().with_chain_id(Some(1u64)).with_genesis_timestamp(Some(timestamp));
2692
2693 assert_eq!(config.get_hardfork(), FoundryHardfork::Ethereum(EthereumHardfork::Shanghai));
2694 }
2695
2696 #[test]
2697 #[cfg(feature = "optimism")]
2698 fn get_hardfork_on_optimism_uses_genesis_timestamp() {
2699 let timestamp = 1_704_992_401u64;
2701 let config = NodeConfig::test()
2702 .with_optimism()
2703 .with_chain_id(Some(10u64))
2704 .with_genesis_timestamp(Some(timestamp));
2705
2706 assert_eq!(config.get_hardfork(), FoundryHardfork::Optimism(OpHardfork::Canyon));
2707 }
2708
2709 #[test]
2710 fn get_hardfork_on_local_tempo_defaults_to_latest_active() {
2711 let config = NodeConfig::test_tempo();
2712
2713 assert_eq!(config.get_hardfork(), FoundryHardfork::Tempo(latest_active_tempo_hardfork()));
2714 }
2715
2716 #[test]
2717 #[cfg(feature = "monad")]
2718 fn get_hardfork_on_monad_fork_uses_source_chain_timestamp_mapping() {
2719 let mut config = NodeConfig::test_monad()
2720 .with_chain_id(Some(1u64))
2721 .with_genesis_timestamp(Some(1_763_648_999u64));
2722 config.fork_source_chain_id = Some(143);
2723
2724 assert_eq!(config.get_chain_id(), 1);
2725 assert_eq!(
2726 config.get_hardfork(),
2727 FoundryHardfork::Monad(foundry_evm::hardfork::MonadHardfork::MonadEight)
2728 );
2729 }
2730
2731 #[test]
2732 fn account_generator_rejects_harden_bit_overflow_path() {
2733 let err = AccountGenerator::new(1)
2734 .phrase("test test test test test test test test test test test junk")
2735 .derivation_path("m/44'/60'/0'/0/2147483648'")
2736 .generate()
2737 .unwrap_err()
2738 .to_string();
2739 assert!(err.contains("harden bit"), "{err}");
2740
2741 assert!(
2742 AccountGenerator::new(1)
2743 .phrase("test test test test test test test test test test test junk")
2744 .derivation_path("m/44'/60'/0'/0")
2745 .generate()
2746 .is_ok()
2747 );
2748 }
2749}