1use crate::{
2 EthereumHardfork, FeeManager, PrecompileFactory,
3 eth::{
4 backend::{
5 db::{Db, SerializableState},
6 fork::{ClientFork, ClientForkConfig, ensure_fork_network_supported},
7 genesis::GenesisConfig,
8 mem::fork_db::ForkedDatabase,
9 time::duration_since_unix_epoch,
10 },
11 fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE},
12 pool::transactions::TransactionOrder,
13 },
14 mem::{self, in_memory_db::StateRootDb},
15};
16use alloy_chains::NamedChain;
17use alloy_consensus::BlockHeader;
18use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
19use alloy_evm::EvmEnv;
20use alloy_genesis::Genesis;
21use alloy_network::{AnyNetwork, AnyRpcBlock, BlockResponse, TransactionResponse};
22use alloy_primitives::{
23 Address, BlockNumber, TxHash, U256, hex, keccak256, map::HashMap, utils::Unit,
24};
25use alloy_provider::Provider;
26use alloy_rpc_types::BlockNumberOrTag;
27use alloy_signer::Signer;
28use alloy_signer_local::{
29 MnemonicBuilder, PrivateKeySigner,
30 coins_bip39::{English, Mnemonic},
31};
32use alloy_transport::TransportError;
33use anvil_server::ServerConfig;
34use eyre::{Context, Result};
35use foundry_common::{
36 ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT,
37 provider::{ProviderBuilder, RetryProvider},
38};
39use foundry_config::Config;
40use foundry_evm::{
41 backend::{BlockchainDb, BlockchainDbMeta, SharedBackend},
42 constants::DEFAULT_CREATE2_DEPLOYER,
43 hardfork::FoundryHardfork,
44 hardforks::latest_active_tempo_hardfork,
45 utils::{
46 apply_chain_and_block_specific_env_changes, block_env_from_header,
47 get_blob_base_fee_update_fraction,
48 },
49};
50use parking_lot::RwLock;
51use rand_08::thread_rng;
52use revm::{
53 context::{BlockEnv, CfgEnv},
54 context_interface::block::BlobExcessGasAndPrice,
55 primitives::hardfork::SpecId,
56};
57use serde_json::{Value, json};
58use std::{
59 fmt::Write as FmtWrite,
60 net::{IpAddr, Ipv4Addr},
61 path::PathBuf,
62 sync::Arc,
63 time::Duration,
64};
65use tempo_hardfork::{
66 TempoHardfork,
67 constants::gas::{TEMPO_T0_BASE_FEE, TEMPO_T1_BASE_FEE},
68};
69use tokio::sync::RwLock as TokioRwLock;
70use yansi::Paint;
71
72pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
73use foundry_evm::{
74 traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
75 utils::get_blob_params,
76};
77use foundry_evm_networks::NetworkConfigs;
78use tempo_precompiles::TIP_FEE_MANAGER_ADDRESS;
79
80pub const NODE_PORT: u16 = 8545;
82pub const CHAIN_ID: u64 = 31337;
84pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
86pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
88pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
90
91#[derive(Clone, Debug)]
93pub(crate) struct ForkTransactionReplay {
94 pub(crate) source_block: AnyRpcBlock,
95 pub(crate) target_index: usize,
96}
97
98pub const DEFAULT_IPC_ENDPOINT: &str =
100 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
101
102const BANNER: &str = r"
103 _ _
104 (_) | |
105 __ _ _ __ __ __ _ | |
106 / _` | | '_ \ \ \ / / | | | |
107 | (_| | | | | | \ V / | | | |
108 \__,_| |_| |_| \_/ |_| |_|
109";
110
111#[derive(Clone, Debug)]
113pub struct NodeConfig {
114 pub chain_id: Option<u64>,
116 pub gas_limit: Option<u64>,
118 pub disable_block_gas_limit: bool,
120 pub enable_tx_gas_limit: bool,
122 pub gas_price: Option<u128>,
124 pub base_fee: Option<u64>,
126 pub disable_min_priority_fee: bool,
128 pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
130 pub hardfork: Option<FoundryHardfork>,
132 pub genesis_accounts: Vec<PrivateKeySigner>,
134 pub genesis_balance: U256,
136 pub genesis_timestamp: Option<u64>,
138 pub genesis_block_number: Option<u64>,
140 pub signer_accounts: Vec<PrivateKeySigner>,
142 pub block_time: Option<Duration>,
144 pub no_mining: bool,
146 pub mixed_mining: bool,
148 pub port: u16,
150 pub max_transactions: usize,
152 pub fork_urls: Vec<String>,
156 pub fork_choice: Option<ForkChoice>,
158 pub fork_headers: Vec<String>,
160 pub fork_chain_id: Option<U256>,
162 pub account_generator: Option<AccountGenerator>,
164 pub enable_tracing: bool,
166 pub no_storage_caching: bool,
168 pub server_config: ServerConfig,
170 pub host: Vec<IpAddr>,
172 pub transaction_order: TransactionOrder,
174 pub config_out: Option<PathBuf>,
176 pub genesis: Option<Genesis>,
178 pub fork_request_timeout: Duration,
180 pub fork_request_retries: u32,
182 pub fork_retry_backoff: Duration,
184 pub compute_units_per_second: u64,
186 pub ipc_path: Option<Option<String>>,
188 pub enable_steps_tracing: bool,
190 pub print_logs: bool,
192 pub print_traces: bool,
194 pub enable_auto_impersonate: bool,
196 pub code_size_limit: Option<usize>,
198 pub prune_history: PruneStateHistoryConfig,
202 pub max_persisted_states: Option<usize>,
204 pub init_state: Option<SerializableState>,
206 pub transaction_block_keeper: Option<usize>,
208 pub disable_default_create2_deployer: bool,
210 pub disable_pool_balance_checks: bool,
212 pub slots_in_an_epoch: u64,
214 pub memory_limit: Option<u64>,
216 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
218 pub networks: NetworkConfigs,
220 pub silent: bool,
222 pub cache_path: Option<PathBuf>,
225 pub funded_accounts: HashMap<Address, U256>,
227}
228
229impl NodeConfig {
230 fn as_string(&self, fork: Option<&ClientFork>) -> String {
231 let mut s: String = String::new();
232 let _ = write!(s, "\n{}", BANNER.green());
233 let _ = write!(s, "\n {VERSION_MESSAGE}");
234 let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green());
235
236 let _ = write!(
237 s,
238 r#"
239
240Available Accounts
241==================
242"#
243 );
244 let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
245 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
246 write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
247 }
248
249 let _ = write!(
250 s,
251 r#"
252
253Private Keys
254==================
255"#
256 );
257
258 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
259 let hex = hex::encode(wallet.credential().to_bytes());
260 let _ = write!(s, "\n({idx}) 0x{hex}");
261 }
262
263 if let Some(generator) = &self.account_generator {
264 let _ = write!(
265 s,
266 r#"
267
268Wallet
269==================
270Mnemonic: {}
271Derivation path: {}
272"#,
273 generator.phrase,
274 generator.get_derivation_path()
275 );
276 }
277
278 if let Some(fork) = fork {
279 let _ = write!(
280 s,
281 r#"
282
283Fork
284==================
285Endpoint: {}
286Block number: {}
287Block hash: {:?}
288Chain ID: {}
289"#,
290 fork.eth_rpc_url().as_deref().unwrap_or("none"),
291 fork.block_number(),
292 fork.block_hash(),
293 fork.chain_id()
294 );
295
296 if self.fork_urls.len() > 1 {
297 let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len());
298 for (i, url) in self.fork_urls.iter().enumerate() {
299 let _ = writeln!(s, " ({i}) {url}");
300 }
301 }
302
303 if let Some(tx_hash) = fork.transaction_hash() {
304 let _ = writeln!(s, "Transaction hash: {tx_hash}");
305 }
306 } else {
307 let _ = write!(
308 s,
309 r#"
310
311Chain ID
312==================
313
314{}
315"#,
316 self.get_chain_id().green()
317 );
318 }
319
320 if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
321 let _ = write!(
322 s,
323 r#"
324Gas Price
325==================
326
327{}
328"#,
329 self.get_gas_price().green()
330 );
331 } else {
332 let _ = write!(
333 s,
334 r#"
335Base Fee
336==================
337
338{}
339"#,
340 self.get_base_fee().green()
341 );
342 }
343
344 let _ = write!(
345 s,
346 r#"
347Gas Limit
348==================
349
350{}
351"#,
352 {
353 if self.disable_block_gas_limit {
354 "Disabled".to_string()
355 } else {
356 self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
357 if self.fork_choice.is_some() {
358 "Forked".to_string()
359 } else {
360 DEFAULT_GAS_LIMIT.to_string()
361 }
362 })
363 }
364 }
365 .green()
366 );
367
368 let _ = write!(
369 s,
370 r#"
371Genesis Timestamp
372==================
373
374{}
375"#,
376 self.get_genesis_timestamp().green()
377 );
378
379 let _ = write!(
380 s,
381 r#"
382Genesis Number
383==================
384
385{}
386"#,
387 self.get_genesis_number().green()
388 );
389
390 s
391 }
392
393 fn as_json(&self, fork: Option<&ClientFork>) -> Value {
394 let mut wallet_description = HashMap::new();
395 let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
396 let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
397
398 for wallet in &self.genesis_accounts {
399 available_accounts.push(format!("{:?}", wallet.address()));
400 private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes())));
401 }
402
403 if let Some(generator) = &self.account_generator {
404 let phrase = generator.get_phrase().to_string();
405 let derivation_path = generator.get_derivation_path().to_string();
406
407 wallet_description.insert("derivation_path".to_string(), derivation_path);
408 wallet_description.insert("mnemonic".to_string(), phrase);
409 };
410
411 let gas_limit = match self.gas_limit {
412 Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
414 Some(limit) => Some(limit.to_string()),
415 _ => None,
416 };
417
418 if let Some(fork) = fork {
419 json!({
420 "available_accounts": available_accounts,
421 "private_keys": private_keys,
422 "endpoint": fork.eth_rpc_url().unwrap_or_default(),
423 "block_number": fork.block_number(),
424 "block_hash": fork.block_hash(),
425 "chain_id": fork.chain_id(),
426 "wallet": wallet_description,
427 "base_fee": format!("{}", self.get_base_fee()),
428 "gas_price": format!("{}", self.get_gas_price()),
429 "gas_limit": gas_limit,
430 })
431 } else {
432 json!({
433 "available_accounts": available_accounts,
434 "private_keys": private_keys,
435 "wallet": wallet_description,
436 "base_fee": format!("{}", self.get_base_fee()),
437 "gas_price": format!("{}", self.get_gas_price()),
438 "gas_limit": gas_limit,
439 "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
440 })
441 }
442 }
443}
444
445impl NodeConfig {
446 #[doc(hidden)]
449 pub fn test() -> Self {
450 Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
451 }
452
453 #[doc(hidden)]
455 pub fn test_tempo() -> Self {
456 Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
457 }
458
459 pub fn empty_state() -> Self {
461 Self {
462 genesis_accounts: vec![],
463 signer_accounts: vec![],
464 disable_default_create2_deployer: true,
465 ..Default::default()
466 }
467 }
468}
469
470impl Default for NodeConfig {
471 fn default() -> Self {
472 let genesis_accounts = AccountGenerator::new(10)
474 .phrase(DEFAULT_MNEMONIC)
475 .generate()
476 .expect("Invalid mnemonic.");
477 Self {
478 chain_id: None,
479 gas_limit: None,
480 disable_block_gas_limit: false,
481 enable_tx_gas_limit: false,
482 gas_price: None,
483 hardfork: None,
484 signer_accounts: genesis_accounts.clone(),
485 genesis_timestamp: None,
486 genesis_block_number: None,
487 genesis_accounts,
488 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
490 block_time: None,
491 no_mining: false,
492 mixed_mining: false,
493 port: NODE_PORT,
494 max_transactions: 1_000,
495 fork_urls: vec![],
496 fork_choice: None,
497 account_generator: None,
498 base_fee: None,
499 disable_min_priority_fee: false,
500 blob_excess_gas_and_price: None,
501 enable_tracing: true,
502 enable_steps_tracing: false,
503 print_logs: true,
504 print_traces: false,
505 enable_auto_impersonate: false,
506 no_storage_caching: false,
507 server_config: Default::default(),
508 host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
509 transaction_order: Default::default(),
510 config_out: None,
511 genesis: None,
512 fork_request_timeout: REQUEST_TIMEOUT,
513 fork_headers: vec![],
514 fork_request_retries: 5,
515 fork_retry_backoff: Duration::from_millis(1_000),
516 fork_chain_id: None,
517 compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
519 ipc_path: None,
520 code_size_limit: None,
521 prune_history: Default::default(),
522 max_persisted_states: None,
523 init_state: None,
524 transaction_block_keeper: None,
525 disable_default_create2_deployer: false,
526 disable_pool_balance_checks: false,
527 slots_in_an_epoch: DEFAULT_SLOTS_IN_AN_EPOCH,
528 memory_limit: None,
529 precompile_factory: None,
530 networks: Default::default(),
531 silent: false,
532 cache_path: None,
533 funded_accounts: HashMap::default(),
534 }
535 }
536}
537
538impl NodeConfig {
539 pub(crate) fn apply_tempo_fork_beneficiary_default<N>(&self, evm_env: &mut EvmEnv<N>) {
542 if self.networks.is_tempo()
543 && !self.fork_urls.is_empty()
544 && evm_env.block_env.beneficiary.is_zero()
545 {
546 evm_env.block_env.beneficiary = TIP_FEE_MANAGER_ADDRESS;
553 }
554 }
555
556 #[must_use]
558 pub const fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
559 self.memory_limit = mems_value;
560 self
561 }
562
563 pub fn get_base_fee(&self) -> u64 {
567 let default = if self.networks.is_tempo() {
568 tempo_default_base_fee(TempoHardfork::from(self.get_hardfork()))
569 } else {
570 INITIAL_BASE_FEE
571 };
572 self.base_fee
573 .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
574 .unwrap_or(default)
575 }
576
577 pub fn get_gas_price(&self) -> u128 {
581 let default = if self.networks.is_tempo() {
582 tempo_default_base_fee(TempoHardfork::from(self.get_hardfork())) as u128
583 } else {
584 INITIAL_GAS_PRICE
585 };
586 self.gas_price.unwrap_or(default)
587 }
588
589 pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
590 if let Some(value) = self.blob_excess_gas_and_price {
591 value
592 } else {
593 let excess_blob_gas =
594 self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
595 BlobExcessGasAndPrice::new(
596 excess_blob_gas,
597 get_blob_base_fee_update_fraction(
598 self.get_chain_id(),
599 self.get_genesis_timestamp(),
600 ),
601 )
602 }
603 }
604
605 pub fn get_blob_params(&self) -> BlobParams {
607 get_blob_params(self.get_chain_id(), self.get_genesis_timestamp())
608 }
609
610 pub fn get_hardfork(&self) -> FoundryHardfork {
612 if let Some(hardfork) = self.hardfork {
613 return hardfork;
614 }
615 if self.networks.is_tempo()
616 && let Some(hardfork) = TempoHardfork::from_chain_and_timestamp(
617 self.get_chain_id(),
618 self.get_genesis_timestamp(),
619 )
620 {
621 return hardfork.into();
622 }
623 #[cfg(feature = "optimism")]
624 if self.networks.is_optimism() {
625 return foundry_evm::hardforks::OpHardfork::default().into();
626 }
627 if self.networks.is_tempo() {
628 return latest_active_tempo_hardfork().into();
629 }
630 EthereumHardfork::default().into()
631 }
632
633 #[must_use]
635 pub const fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
636 self.code_size_limit = code_size_limit;
637 self
638 }
639 #[must_use]
641 pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
642 if disable_code_size_limit {
643 self.code_size_limit = Some(usize::MAX);
644 }
645 self
646 }
647
648 #[must_use]
650 pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
651 self.init_state = init_state;
652 self
653 }
654
655 #[must_use]
657 #[cfg(feature = "cmd")]
658 pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
659 self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
660 self
661 }
662
663 #[must_use]
665 pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
666 self.set_chain_id(chain_id);
667 self
668 }
669
670 pub fn get_chain_id(&self) -> u64 {
672 self.chain_id
673 .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
674 .unwrap_or(CHAIN_ID)
675 }
676
677 pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
679 self.chain_id = chain_id.map(Into::into);
680 let chain_id = self.get_chain_id();
681 self.networks = self.networks.with_chain_id(chain_id);
682 self.genesis_accounts.iter_mut().for_each(|wallet| {
683 *wallet = wallet.clone().with_chain_id(Some(chain_id));
684 });
685 self.signer_accounts.iter_mut().for_each(|wallet| {
686 *wallet = wallet.clone().with_chain_id(Some(chain_id));
687 })
688 }
689
690 #[must_use]
692 pub const fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
693 self.gas_limit = gas_limit;
694 self
695 }
696
697 #[must_use]
701 pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
702 self.disable_block_gas_limit = disable_block_gas_limit;
703 self
704 }
705
706 #[must_use]
710 pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
711 self.enable_tx_gas_limit = enable_tx_gas_limit;
712 self
713 }
714
715 #[must_use]
717 pub const fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
718 self.gas_price = gas_price;
719 self
720 }
721
722 #[must_use]
724 pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
725 self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
726 self
727 }
728
729 #[must_use]
731 pub fn with_max_persisted_states<U: Into<usize>>(
732 mut self,
733 max_persisted_states: Option<U>,
734 ) -> Self {
735 self.max_persisted_states = max_persisted_states.map(Into::into);
736 self
737 }
738
739 #[must_use]
741 pub const fn with_max_transactions(mut self, max_transactions: Option<usize>) -> Self {
742 if let Some(max_transactions) = max_transactions {
743 self.max_transactions = max_transactions;
744 }
745 self
746 }
747
748 #[must_use]
750 pub fn with_transaction_block_keeper<U: Into<usize>>(
751 mut self,
752 transaction_block_keeper: Option<U>,
753 ) -> Self {
754 self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
755 self
756 }
757
758 #[must_use]
760 pub const fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
761 self.base_fee = base_fee;
762 self
763 }
764
765 #[must_use]
767 pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
768 self.disable_min_priority_fee = disable_min_priority_fee;
769 self
770 }
771
772 #[must_use]
774 pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
775 self.genesis = genesis;
776 self
777 }
778
779 pub fn get_genesis_timestamp(&self) -> u64 {
781 self.genesis_timestamp
782 .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
783 .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
784 }
785
786 #[must_use]
788 pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
789 if let Some(timestamp) = timestamp {
790 self.genesis_timestamp = Some(timestamp.into());
791 }
792 self
793 }
794
795 #[must_use]
797 pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
798 if let Some(number) = number {
799 self.genesis_block_number = Some(number.into());
800 }
801 self
802 }
803
804 pub fn get_genesis_number(&self) -> u64 {
806 self.genesis_block_number
807 .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
808 .unwrap_or(0)
809 }
810
811 #[must_use]
813 pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
814 self.hardfork = hardfork;
815 self
816 }
817
818 #[must_use]
820 pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
821 self.genesis_accounts = accounts;
822 self
823 }
824
825 #[must_use]
827 pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
828 self.signer_accounts = accounts;
829 self
830 }
831
832 pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
835 let accounts = generator.generate()?;
836 self.account_generator = Some(generator);
837 Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
838 }
839
840 #[must_use]
842 pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
843 self.genesis_balance = balance.into();
844 self
845 }
846
847 #[must_use]
849 pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
850 self.block_time = block_time.map(Into::into);
851 self
852 }
853
854 #[must_use]
855 pub fn with_mixed_mining<D: Into<Duration>>(
856 mut self,
857 mixed_mining: bool,
858 block_time: Option<D>,
859 ) -> Self {
860 self.block_time = block_time.map(Into::into);
861 self.mixed_mining = mixed_mining;
862 self
863 }
864
865 #[must_use]
867 pub const fn with_no_mining(mut self, no_mining: bool) -> Self {
868 self.no_mining = no_mining;
869 self
870 }
871
872 #[must_use]
874 pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
875 self.slots_in_an_epoch = slots_in_an_epoch;
876 self
877 }
878
879 #[must_use]
881 pub const fn with_port(mut self, port: u16) -> Self {
882 self.port = port;
883 self
884 }
885
886 #[must_use]
893 pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
894 self.ipc_path = ipc_path;
895 self
896 }
897
898 #[must_use]
900 pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
901 self.config_out = config_out;
902 self
903 }
904
905 #[must_use]
906 pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self {
907 self.no_storage_caching = no_storage_caching;
908 self
909 }
910
911 #[must_use]
913 pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
914 if let Some(url) = eth_rpc_url {
915 self.fork_urls = vec![url.into()];
916 }
917 self
918 }
919
920 #[must_use]
922 pub fn with_fork_urls(mut self, fork_urls: Vec<String>) -> Self {
923 self.fork_urls = fork_urls;
924 self
925 }
926
927 #[must_use]
929 pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
930 self.with_fork_choice(fork_block_number.map(Into::into))
931 }
932
933 #[must_use]
935 pub fn with_fork_transaction_hash<U: Into<TxHash>>(
936 self,
937 fork_transaction_hash: Option<U>,
938 ) -> Self {
939 self.with_fork_choice(fork_transaction_hash.map(Into::into))
940 }
941
942 #[must_use]
944 pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
945 self.fork_choice = fork_choice.map(Into::into);
946 self
947 }
948
949 #[must_use]
951 pub const fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
952 self.fork_chain_id = fork_chain_id;
953 self
954 }
955
956 #[must_use]
958 pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
959 self.fork_headers = headers;
960 self
961 }
962
963 #[must_use]
965 pub const fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
966 if let Some(fork_request_timeout) = fork_request_timeout {
967 self.fork_request_timeout = fork_request_timeout;
968 }
969 self
970 }
971
972 #[must_use]
974 pub const fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
975 if let Some(fork_request_retries) = fork_request_retries {
976 self.fork_request_retries = fork_request_retries;
977 }
978 self
979 }
980
981 #[must_use]
983 pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
984 if let Some(fork_retry_backoff) = fork_retry_backoff {
985 self.fork_retry_backoff = fork_retry_backoff;
986 }
987 self
988 }
989
990 #[must_use]
994 pub const fn fork_compute_units_per_second(
995 mut self,
996 compute_units_per_second: Option<u64>,
997 ) -> Self {
998 if let Some(compute_units_per_second) = compute_units_per_second {
999 self.compute_units_per_second = compute_units_per_second;
1000 }
1001 self
1002 }
1003
1004 #[must_use]
1006 pub const fn with_tracing(mut self, enable_tracing: bool) -> Self {
1007 self.enable_tracing = enable_tracing;
1008 self
1009 }
1010
1011 #[must_use]
1013 pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
1014 self.enable_steps_tracing = enable_steps_tracing;
1015 self
1016 }
1017
1018 #[must_use]
1020 pub const fn with_print_logs(mut self, print_logs: bool) -> Self {
1021 self.print_logs = print_logs;
1022 self
1023 }
1024
1025 #[must_use]
1027 pub const fn with_print_traces(mut self, print_traces: bool) -> Self {
1028 self.print_traces = print_traces;
1029 self
1030 }
1031
1032 #[must_use]
1034 pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
1035 self.enable_auto_impersonate = enable_auto_impersonate;
1036 self
1037 }
1038
1039 #[must_use]
1040 pub fn with_server_config(mut self, config: ServerConfig) -> Self {
1041 self.server_config = config;
1042 self
1043 }
1044
1045 #[must_use]
1047 pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
1048 self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
1049 self
1050 }
1051
1052 #[must_use]
1053 pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
1054 self.transaction_order = transaction_order;
1055 self
1056 }
1057
1058 pub fn get_ipc_path(&self) -> Option<String> {
1060 match &self.ipc_path {
1061 Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
1062 None => None,
1063 }
1064 }
1065
1066 pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
1068 if let Some(path) = &self.config_out {
1069 let value = self.as_json(fork);
1070 foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?;
1071 }
1072 if !self.silent {
1073 sh_println!("{}", self.as_string(fork))?;
1074 }
1075 Ok(())
1076 }
1077
1078 pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
1082 self.block_cache_path_for_rpc(block, self.fork_urls.first()?)
1083 }
1084
1085 fn block_cache_path_for_rpc(&self, block: u64, rpc_url: &str) -> Option<PathBuf> {
1086 if self.no_storage_caching || self.fork_urls.is_empty() {
1087 return None;
1088 }
1089 let chain_id = self.get_chain_id();
1090 let rpc_url_hash = hex::encode(keccak256(rpc_url));
1091 Some(
1092 Config::foundry_block_cache_file(chain_id, block)?
1093 .with_file_name(format!("storage-{rpc_url_hash}.json")),
1094 )
1095 }
1096
1097 #[must_use]
1099 pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1100 self.disable_default_create2_deployer = yes;
1101 self
1102 }
1103
1104 #[must_use]
1106 pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1107 self.disable_pool_balance_checks = yes;
1108 self
1109 }
1110
1111 #[must_use]
1113 pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1114 self.precompile_factory = Some(Arc::new(factory));
1115 self
1116 }
1117
1118 #[must_use]
1120 pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1121 self.networks = networks;
1122 self
1123 }
1124
1125 #[must_use]
1127 pub fn with_tempo(mut self) -> Self {
1128 self.networks = NetworkConfigs::with_tempo();
1129 self
1130 }
1131
1132 #[cfg(feature = "optimism")]
1134 #[must_use]
1135 pub fn with_optimism(mut self) -> Self {
1136 self.networks = NetworkConfigs::with_optimism();
1137 self
1138 }
1139
1140 #[must_use]
1142 pub const fn silent(self) -> Self {
1143 self.set_silent(true)
1144 }
1145
1146 #[must_use]
1147 pub const fn set_silent(mut self, silent: bool) -> Self {
1148 self.silent = silent;
1149 self
1150 }
1151
1152 #[must_use]
1157 pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1158 self.cache_path = cache_path;
1159 self
1160 }
1161
1162 #[must_use]
1164 pub fn with_funded_accounts(mut self, accounts: HashMap<Address, U256>) -> Self {
1165 self.funded_accounts = accounts;
1166 self
1167 }
1168
1169 pub(crate) async fn setup<N>(
1174 &mut self,
1175 ) -> Result<(mem::Backend<N>, Option<ForkTransactionReplay>)>
1176 where
1177 N: alloy_network::Network<
1178 TxEnvelope = foundry_primitives::FoundryTxEnvelope,
1179 ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope,
1180 >,
1181 {
1182 let mut cfg = CfgEnv::default();
1185 cfg.spec = self.get_hardfork().into();
1186
1187 cfg.chain_id = self.get_chain_id();
1188 cfg.limit_contract_code_size = self.code_size_limit;
1189 cfg.disable_eip3607 = true;
1193 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1194
1195 if !self.enable_tx_gas_limit {
1196 cfg.tx_gas_limit_cap = Some(u64::MAX);
1197 }
1198
1199 if let Some(value) = self.memory_limit {
1200 cfg.memory_limit = value;
1201 }
1202
1203 let spec_id = cfg.spec;
1204 let mut evm_env = EvmEnv::new(
1205 cfg,
1206 BlockEnv {
1207 gas_limit: self.gas_limit(),
1208 basefee: self.get_base_fee(),
1209 ..Default::default()
1210 },
1211 );
1212
1213 self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1214
1215 let genesis_timestamp = self.get_genesis_timestamp();
1216 let base_fee_params: BaseFeeParams = self.networks.base_fee_params(genesis_timestamp);
1217
1218 let tempo_hardfork =
1220 self.networks.is_tempo().then(|| TempoHardfork::from(self.get_hardfork()));
1221
1222 let fees = FeeManager::new(
1223 spec_id,
1224 self.get_base_fee(),
1225 !self.disable_min_priority_fee,
1226 self.get_gas_price(),
1227 self.get_blob_excess_gas_and_price(),
1228 self.get_blob_params(),
1229 base_fee_params,
1230 tempo_hardfork,
1231 );
1232
1233 let (db, fork, fork_transaction_replay) =
1234 if let Some(eth_rpc_url) = self.fork_urls.first().cloned() {
1235 self.setup_fork_db_with_replay(eth_rpc_url, &mut evm_env, &fees).await?
1236 } else {
1237 let track_history = self.prune_history.is_state_history_supported();
1238 let db: Arc<TokioRwLock<Box<dyn Db>>> =
1239 Arc::new(TokioRwLock::new(Box::new(StateRootDb::new(track_history))));
1240 (db, None, None)
1241 };
1242
1243 if let Some(ref genesis) = self.genesis {
1245 if self.chain_id.is_none() {
1248 evm_env.cfg_env.chain_id = genesis.config.chain_id;
1249 }
1250 evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1251 if let Some(base_fee) = genesis.base_fee_per_gas {
1252 evm_env.block_env.basefee = base_fee.try_into()?;
1253 }
1254 if let Some(number) = genesis.number {
1255 evm_env.block_env.number = U256::from(number);
1256 }
1257 evm_env.block_env.beneficiary = genesis.coinbase;
1258 }
1259
1260 let is_bsc = matches!(
1264 NamedChain::try_from(evm_env.cfg_env.chain_id),
1265 Ok(NamedChain::BinanceSmartChain | NamedChain::BinanceSmartChainTestnet)
1266 );
1267 if fork.is_none() && (self.genesis_timestamp.is_some() || is_bsc) {
1268 evm_env.block_env.timestamp = U256::from(genesis_timestamp);
1269 }
1270
1271 self.apply_tempo_fork_beneficiary_default(&mut evm_env);
1272
1273 let genesis = GenesisConfig {
1274 number: self.get_genesis_number(),
1275 timestamp: genesis_timestamp,
1276 balance: self.genesis_balance,
1277 accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1278 genesis_init: self.genesis.clone(),
1279 };
1280
1281 let mut decoder_builder = CallTraceDecoderBuilder::new().with_tempo_hardfork(
1282 self.networks.is_tempo().then(|| TempoHardfork::from(self.get_hardfork())),
1283 );
1284 if self.print_traces {
1285 if let Ok(identifier) = SignaturesIdentifier::new(false) {
1287 debug!(target: "node", "using signature identifier");
1288 decoder_builder = decoder_builder.with_signature_identifier(identifier);
1289 }
1290 }
1291
1292 let backend = mem::Backend::with_genesis(
1294 db,
1295 Arc::new(RwLock::new(evm_env)),
1296 self.networks,
1297 genesis,
1298 fees,
1299 Arc::new(RwLock::new(fork)),
1300 self.enable_steps_tracing,
1301 self.print_logs,
1302 self.print_traces,
1303 Arc::new(decoder_builder.build()),
1304 self.prune_history,
1305 self.max_persisted_states,
1306 self.transaction_block_keeper,
1307 self.block_time,
1308 self.cache_path.clone(),
1309 Arc::new(TokioRwLock::new(self.clone())),
1310 )
1311 .await?;
1312
1313 if !self.disable_default_create2_deployer && self.fork_urls.is_empty() {
1316 backend
1317 .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1318 .await
1319 .wrap_err("failed to create default create2 deployer")?;
1320 }
1321
1322 if !self.funded_accounts.is_empty() {
1323 for (address, balance) in &self.funded_accounts {
1324 backend
1325 .set_balance(*address, *balance)
1326 .await
1327 .wrap_err_with(|| format!("failed to fund account {address}"))?;
1328 }
1329 }
1330
1331 Ok((backend, fork_transaction_replay))
1332 }
1333
1334 pub async fn setup_fork_db(
1341 &mut self,
1342 eth_rpc_url: String,
1343 evm_env: &mut EvmEnv,
1344 fees: &FeeManager,
1345 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1346 let (db, fork, replay) = self.setup_fork_db_with_replay(eth_rpc_url, evm_env, fees).await?;
1347 eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1348 Ok((db, fork))
1349 }
1350
1351 async fn setup_fork_db_with_replay(
1352 &mut self,
1353 eth_rpc_url: String,
1354 evm_env: &mut EvmEnv,
1355 fees: &FeeManager,
1356 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>, Option<ForkTransactionReplay>)>
1357 {
1358 let (db, config, replay) =
1359 self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1360 let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1361 let fork = ClientFork::new(config, Arc::clone(&db));
1362 Ok((db, Some(fork), replay))
1363 }
1364
1365 pub async fn setup_fork_db_config(
1371 &mut self,
1372 eth_rpc_url: String,
1373 evm_env: &mut EvmEnv,
1374 fees: &FeeManager,
1375 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1376 let (db, config, replay) =
1377 self.setup_fork_db_config_with_replay(eth_rpc_url, evm_env, fees).await?;
1378 eyre::ensure!(replay.is_none(), "transaction-hash fork replay requires full node startup");
1379 Ok((db, config))
1380 }
1381
1382 pub(crate) async fn setup_fork_db_config_with_replay(
1383 &mut self,
1384 eth_rpc_url: String,
1385 evm_env: &mut EvmEnv,
1386 fees: &FeeManager,
1387 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig, Option<ForkTransactionReplay>)> {
1388 debug!(target: "node", ?eth_rpc_url, "setting up fork db");
1389 let override_chain_id = self.chain_id;
1390
1391 let provider = Arc::new(
1395 ProviderBuilder::new(ð_rpc_url)
1396 .timeout(self.fork_request_timeout)
1397 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1398 .compute_units_per_second(self.compute_units_per_second)
1399 .max_retry(self.fork_request_retries)
1400 .headers(self.fork_headers.clone())
1401 .build()
1402 .wrap_err("failed to establish provider to fork url")?,
1403 );
1404
1405 let source_chain_id = if let Some(chain_id) = self.fork_chain_id {
1406 eyre::ensure!(
1407 self.fork_urls.len() == 1,
1408 "multiple fork URLs cannot be validated with --fork-chain-id; remove \
1409 --fork-chain-id to validate every endpoint"
1410 );
1411 chain_id.to()
1412 } else {
1413 let chain_id = provider
1414 .get_chain_id()
1415 .await
1416 .wrap_err_with(|| format!("failed to fetch network chain ID from {eth_rpc_url}"))?;
1417 ensure_fork_network_supported(chain_id)?;
1418
1419 for url in self.fork_urls.iter().skip(1) {
1420 let endpoint_provider = ProviderBuilder::<AnyNetwork>::new(url)
1421 .timeout(self.fork_request_timeout)
1422 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1423 .compute_units_per_second(self.compute_units_per_second)
1424 .max_retry(self.fork_request_retries)
1425 .headers(self.fork_headers.clone())
1426 .build()
1427 .wrap_err_with(|| format!("failed to establish provider to fork url {url}"))?;
1428 let endpoint_chain_id = endpoint_provider
1429 .get_chain_id()
1430 .await
1431 .wrap_err_with(|| format!("failed to fetch network chain ID from {url}"))?;
1432 ensure_fork_network_supported(endpoint_chain_id)?;
1433 if endpoint_chain_id != chain_id {
1434 eyre::bail!(
1435 "fork endpoints must use the same chain ID: expected {chain_id}, got \
1436 {endpoint_chain_id} from {url}"
1437 );
1438 }
1439 }
1440
1441 chain_id
1442 };
1443 ensure_fork_network_supported(source_chain_id)?;
1444
1445 let (fork_block_number, fork_transaction_replay) =
1446 if let Some(fork_choice) = &self.fork_choice {
1447 let (fork_block_number, fork_transaction_replay) =
1448 derive_block_and_replay(fork_choice, &provider).await.wrap_err(
1449 "failed to derive fork block and transaction replay from fork choice",
1450 )?;
1451 (fork_block_number, fork_transaction_replay)
1452 } else {
1453 let bn = find_latest_fork_block(&provider)
1455 .await
1456 .wrap_err("failed to get fork block number")?;
1457 (bn, None)
1458 };
1459
1460 let block = provider
1461 .get_block(BlockNumberOrTag::Number(fork_block_number).into())
1462 .await
1463 .wrap_err("failed to get fork block")?;
1464
1465 let block = if let Some(block) = block {
1466 block
1467 } else {
1468 if let Ok(latest_block) = provider.get_block_number().await {
1469 let mut message = format!(
1470 "Failed to get block for block number: {fork_block_number}\n\
1471latest block number: {latest_block}"
1472 );
1473 if fork_block_number <= latest_block {
1477 message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
1478 }
1479 eyre::bail!("{message}");
1480 }
1481 eyre::bail!("failed to get block for block number: {fork_block_number}");
1482 };
1483
1484 if let Some(replay) = &fork_transaction_replay {
1485 let source_header = replay.source_block.header();
1486 eyre::ensure!(
1487 block.header.hash == source_header.parent_hash,
1488 "fork transaction block {} at {} has parent {}, but fetched fork block at {} has \
1489 hash {}",
1490 source_header.hash,
1491 source_header.number,
1492 source_header.parent_hash,
1493 block.header.number,
1494 block.header.hash
1495 );
1496 eyre::ensure!(
1497 block.header.number.checked_add(1) == Some(source_header.number),
1498 "fork transaction block {} has number {}, but fetched parent {} has number {}",
1499 source_header.hash,
1500 source_header.number,
1501 block.header.hash,
1502 block.header.number
1503 );
1504 }
1505
1506 let gas_limit = self.fork_gas_limit(&block);
1507 self.gas_limit = Some(gas_limit);
1508
1509 let cache_block_env: BlockEnv = block_env_from_header(&block.header);
1512
1513 evm_env.block_env = BlockEnv {
1514 gas_limit,
1515 beneficiary: evm_env.block_env.beneficiary,
1517 basefee: evm_env.block_env.basefee,
1518 ..block_env_from_header(&block.header)
1519 };
1520
1521 let chain_id = if let Some(chain_id) = self.chain_id {
1523 chain_id
1524 } else {
1525 self.set_chain_id(Some(source_chain_id));
1527 evm_env.cfg_env.chain_id = source_chain_id;
1528 source_chain_id
1529 };
1530
1531 if self.hardfork.is_none()
1533 && let Some(hardfork) =
1534 FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp())
1535 {
1536 evm_env.cfg_env.spec = SpecId::from(hardfork);
1537 self.hardfork = Some(hardfork);
1538 }
1539
1540 if self.networks.is_tempo() {
1543 fees.set_tempo_hardfork(Some(TempoHardfork::from(self.get_hardfork())));
1544 }
1545
1546 if self.base_fee.is_none()
1548 && let Some(base_fee) = block.header.base_fee_per_gas()
1549 {
1550 self.base_fee = Some(base_fee);
1551 evm_env.block_env.basefee = base_fee;
1552 let next_block_base_fee = fees.get_next_block_base_fee_per_gas(
1555 block.header.gas_used(),
1556 gas_limit,
1557 block.header.base_fee_per_gas().unwrap_or_default(),
1558 );
1559
1560 fees.set_base_fee(next_block_base_fee);
1562 }
1563
1564 if let (Some(blob_excess_gas), Some(blob_gas_used)) =
1565 (block.header.excess_blob_gas(), block.header.blob_gas_used())
1566 {
1567 let blob_params = get_blob_params(chain_id, block.header.timestamp());
1569
1570 evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
1571 blob_excess_gas,
1572 blob_params.update_fraction as u64,
1573 ));
1574
1575 fees.set_blob_params(blob_params);
1576
1577 let next_block_blob_excess_gas =
1578 fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used);
1579 fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
1580 next_block_blob_excess_gas,
1581 blob_params.update_fraction as u64,
1582 ));
1583 }
1584
1585 if self.gas_price.is_none()
1587 && let Ok(gas_price) = provider.get_gas_price().await
1588 {
1589 self.gas_price = Some(gas_price);
1590 fees.set_gas_price(gas_price);
1591 }
1592
1593 let block_hash = block.header.hash;
1594
1595 apply_chain_and_block_specific_env_changes::<AnyNetwork, _, _>(
1597 evm_env,
1598 &block,
1599 self.networks,
1600 );
1601
1602 let meta = BlockchainDbMeta::new(cache_block_env, eth_rpc_url.clone());
1603 let block_chain_db = if self.fork_chain_id.is_some() {
1604 BlockchainDb::new_skip_check(
1605 meta,
1606 self.block_cache_path_for_rpc(fork_block_number, ð_rpc_url),
1607 )
1608 } else {
1609 BlockchainDb::new(meta, self.block_cache_path_for_rpc(fork_block_number, ð_rpc_url))
1610 };
1611
1612 let provider = if self.fork_urls.len() > 1 {
1616 debug!(target: "node", urls=?self.fork_urls, "using multi-endpoint round-robin provider");
1617 Arc::new(
1618 ProviderBuilder::new(ð_rpc_url)
1619 .timeout(self.fork_request_timeout)
1620 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1621 .compute_units_per_second(self.compute_units_per_second)
1622 .max_retry(self.fork_request_retries)
1623 .headers(self.fork_headers.clone())
1624 .build_fallback(self.fork_urls.clone())
1625 .wrap_err("failed to establish round-robin provider to fork urls")?,
1626 )
1627 } else {
1628 provider
1629 };
1630
1631 let backend = SharedBackend::spawn_backend(
1634 Arc::clone(&provider),
1635 block_chain_db.clone(),
1636 Some(fork_block_number.into()),
1637 )
1638 .await;
1639
1640 let config = ClientForkConfig {
1641 fork_urls: self.fork_urls.clone(),
1642 block_number: fork_block_number,
1643 block_hash,
1644 transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
1645 provider,
1646 chain_id,
1647 override_chain_id,
1648 hardfork: self.hardfork,
1649 timestamp: block.header.timestamp(),
1650 base_fee: block.header.base_fee_per_gas().map(|g| g as u128),
1651 timeout: self.fork_request_timeout,
1652 retries: self.fork_request_retries,
1653 backoff: self.fork_retry_backoff,
1654 compute_units_per_second: self.compute_units_per_second,
1655 headers: self.fork_headers.clone(),
1656 total_difficulty: block.header.total_difficulty.unwrap_or_default(),
1657 blob_gas_used: block.header.blob_gas_used().map(|g| g as u128),
1658 blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price,
1659 };
1660
1661 debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
1662
1663 let mut db = ForkedDatabase::new(backend, block_chain_db);
1664
1665 db.insert_block_hash(U256::from(config.block_number), config.block_hash);
1667
1668 Ok((db, config, fork_transaction_replay))
1669 }
1670
1671 pub(crate) fn fork_gas_limit<B: BlockResponse<Header: BlockHeader>>(&self, block: &B) -> u64 {
1675 if !self.disable_block_gas_limit {
1676 if let Some(gas_limit) = self.gas_limit {
1677 return gas_limit;
1678 } else if block.header().gas_limit() > 0 {
1679 return block.header().gas_limit();
1680 }
1681 }
1682
1683 u64::MAX
1684 }
1685
1686 pub(crate) fn gas_limit(&self) -> u64 {
1690 if self.disable_block_gas_limit {
1691 return u64::MAX;
1692 }
1693
1694 self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
1695 }
1696}
1697
1698pub(crate) const fn tempo_default_base_fee(hardfork: TempoHardfork) -> u64 {
1699 if hardfork.is_t1() { TEMPO_T1_BASE_FEE } else { TEMPO_T0_BASE_FEE }
1700}
1701
1702async fn derive_block_and_replay(
1707 fork_choice: &ForkChoice,
1708 provider: &Arc<RetryProvider>,
1709) -> eyre::Result<(BlockNumber, Option<ForkTransactionReplay>)> {
1710 match fork_choice {
1711 ForkChoice::Block(block_number) => {
1712 let block_number = *block_number;
1713 if block_number >= 0 {
1714 return Ok((block_number as u64, None));
1715 }
1716 let latest = provider.get_block_number().await?;
1718
1719 Ok((block_number.saturating_add(latest as i128) as u64, None))
1720 }
1721 ForkChoice::Transaction(transaction_hash) => {
1722 let transaction = provider
1724 .get_transaction_by_hash(transaction_hash.0.into())
1725 .await?
1726 .ok_or_else(|| eyre::eyre!("fork transaction {transaction_hash} was not found"))?;
1727 let transaction_block_number = transaction.block_number().ok_or_else(|| {
1728 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
1729 })?;
1730 let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
1731 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
1732 })?;
1733
1734 let transaction_block =
1736 provider.get_block_by_hash(transaction_block_hash).full().await?.ok_or_else(
1737 || {
1738 eyre::eyre!(
1739 "failed to get fork block {transaction_block_hash} for transaction \
1740 {transaction_hash}"
1741 )
1742 },
1743 )?;
1744 let replay = validate_fork_transaction_replay(
1745 *transaction_hash,
1746 &transaction,
1747 transaction_block,
1748 )?;
1749 Ok((transaction_block_number.saturating_sub(1), Some(replay)))
1750 }
1751 }
1752}
1753
1754fn validate_fork_transaction_replay(
1755 transaction_hash: TxHash,
1756 transaction: &alloy_network::AnyRpcTransaction,
1757 source_block: AnyRpcBlock,
1758) -> eyre::Result<ForkTransactionReplay> {
1759 let source_hash = source_block.header.hash;
1760 let source_number = source_block.header.number;
1761 let transaction_block_hash = transaction.block_hash().ok_or_else(|| {
1762 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block hash)")
1763 })?;
1764 let transaction_block_number = transaction.block_number().ok_or_else(|| {
1765 eyre::eyre!("fork transaction {transaction_hash} is not mined (no block number)")
1766 })?;
1767
1768 eyre::ensure!(
1769 source_hash == transaction_block_hash,
1770 "fork transaction {transaction_hash} reports block {transaction_block_hash}, but fetched \
1771 block hash is {source_hash}"
1772 );
1773 eyre::ensure!(
1774 source_number == transaction_block_number,
1775 "fork transaction {transaction_hash} reports block number {transaction_block_number}, but \
1776 fetched block {source_hash} has number {source_number}"
1777 );
1778 eyre::ensure!(
1779 source_number > 0,
1780 "fork transaction {transaction_hash} is in genesis block {source_hash}, which has no parent"
1781 );
1782
1783 let transactions = source_block.transactions.as_transactions().ok_or_else(|| {
1784 eyre::eyre!("fork block {source_hash} at {source_number} did not include full transactions")
1785 })?;
1786 let mut matches =
1787 transactions.iter().enumerate().filter(|(_, tx)| tx.tx_hash() == transaction_hash);
1788 let target_index = matches.next().map(|(index, _)| index).ok_or_else(|| {
1789 eyre::eyre!(
1790 "fork transaction {transaction_hash} is absent from block {source_hash} at \
1791 {source_number}"
1792 )
1793 })?;
1794 eyre::ensure!(
1795 matches.next().is_none(),
1796 "fork transaction {transaction_hash} occurs more than once in block {source_hash} at \
1797 {source_number}"
1798 );
1799 if let Some(reported_index) = transaction.transaction_index() {
1800 eyre::ensure!(
1801 reported_index == target_index as u64,
1802 "fork transaction {transaction_hash} reports index {reported_index}, but occurs at \
1803 index {target_index} in block {source_hash}"
1804 );
1805 }
1806
1807 Ok(ForkTransactionReplay { source_block, target_index })
1808}
1809
1810#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1812pub enum ForkChoice {
1813 Block(i128),
1817 Transaction(TxHash),
1819}
1820
1821impl ForkChoice {
1822 pub const fn block_number(&self) -> Option<i128> {
1824 match self {
1825 Self::Block(block_number) => Some(*block_number),
1826 Self::Transaction(_) => None,
1827 }
1828 }
1829
1830 pub const fn transaction_hash(&self) -> Option<TxHash> {
1832 match self {
1833 Self::Block(_) => None,
1834 Self::Transaction(transaction_hash) => Some(*transaction_hash),
1835 }
1836 }
1837}
1838
1839impl From<TxHash> for ForkChoice {
1841 fn from(tx_hash: TxHash) -> Self {
1842 Self::Transaction(tx_hash)
1843 }
1844}
1845
1846impl From<u64> for ForkChoice {
1848 fn from(block: u64) -> Self {
1849 Self::Block(block as i128)
1850 }
1851}
1852
1853#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1854pub struct PruneStateHistoryConfig {
1855 pub enabled: bool,
1856 pub max_memory_history: Option<usize>,
1857}
1858
1859impl PruneStateHistoryConfig {
1860 pub const fn is_state_history_supported(&self) -> bool {
1862 if !self.enabled {
1863 return true;
1864 }
1865
1866 match self.max_memory_history {
1867 Some(limit) => limit > 0,
1868 None => false,
1869 }
1870 }
1871
1872 pub const fn is_config_enabled(&self) -> bool {
1874 self.enabled
1875 }
1876
1877 pub fn from_args(val: Option<Option<usize>>) -> Self {
1878 val.map(|max_memory_history| Self {
1879 enabled: true,
1880 max_memory_history: max_memory_history.filter(|limit| *limit > 0),
1881 })
1882 .unwrap_or_default()
1883 }
1884}
1885
1886#[derive(Clone, Debug)]
1888pub struct AccountGenerator {
1889 chain_id: u64,
1890 amount: usize,
1891 phrase: String,
1892 derivation_path: Option<String>,
1893}
1894
1895impl AccountGenerator {
1896 pub fn new(amount: usize) -> Self {
1897 Self {
1898 chain_id: CHAIN_ID,
1899 amount,
1900 phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
1901 derivation_path: None,
1902 }
1903 }
1904
1905 #[must_use]
1906 pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
1907 self.phrase = phrase.into();
1908 self
1909 }
1910
1911 fn get_phrase(&self) -> &str {
1912 &self.phrase
1913 }
1914
1915 #[must_use]
1916 pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
1917 self.chain_id = chain_id.into();
1918 self
1919 }
1920
1921 #[must_use]
1922 pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
1923 let mut derivation_path = derivation_path.into();
1924 if !derivation_path.ends_with('/') {
1925 derivation_path.push('/');
1926 }
1927 self.derivation_path = Some(derivation_path);
1928 self
1929 }
1930
1931 fn get_derivation_path(&self) -> &str {
1932 self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
1933 }
1934}
1935
1936impl AccountGenerator {
1937 pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
1938 let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
1939
1940 let derivation_path = self.get_derivation_path();
1942
1943 let mut wallets = Vec::with_capacity(self.amount);
1944 for idx in 0..self.amount {
1945 let builder =
1946 builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap();
1947 let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
1948 wallets.push(wallet)
1949 }
1950 Ok(wallets)
1951 }
1952}
1953
1954pub fn anvil_dir() -> Option<PathBuf> {
1956 Config::foundry_dir().map(|p| p.join("anvil"))
1957}
1958
1959pub fn anvil_tmp_dir() -> Option<PathBuf> {
1961 anvil_dir().map(|p| p.join("tmp"))
1962}
1963
1964async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
1969 provider: P,
1970) -> Result<u64, TransportError> {
1971 let mut num = provider.get_block_number().await?;
1972
1973 for _ in 0..2 {
1976 if let Some(block) = provider.get_block(num.into()).await?
1977 && !block.header.hash.is_zero()
1978 {
1979 break;
1980 }
1981 num = num.saturating_sub(1)
1983 }
1984
1985 Ok(num)
1986}
1987
1988#[cfg(test)]
1989mod tests {
1990 use super::*;
1991
1992 #[test]
1993 fn test_prune_history() {
1994 let config = PruneStateHistoryConfig::default();
1995 assert!(config.is_state_history_supported());
1996 let config = PruneStateHistoryConfig::from_args(Some(None));
1997 assert!(!config.is_state_history_supported());
1998 let config = PruneStateHistoryConfig::from_args(Some(Some(0)));
1999 assert!(config.is_config_enabled());
2000 assert!(!config.is_state_history_supported());
2001 let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
2002 assert!(config.is_state_history_supported());
2003 }
2004
2005 #[cfg(feature = "optimism")]
2006 #[test]
2007 fn set_chain_id_updates_network_config() {
2008 let mut config = NodeConfig::test();
2009 config.set_chain_id(Some(10u64));
2010
2011 assert!(config.networks.is_optimism());
2012 }
2013
2014 #[test]
2015 fn get_hardfork_on_tempo_never_returns_non_tempo_variant() {
2016 let shanghai_ts = 1_681_338_455u64;
2018
2019 let config = NodeConfig::test_tempo()
2020 .with_chain_id(Some(1u64))
2021 .with_genesis_timestamp(Some(shanghai_ts));
2022
2023 assert!(config.networks.is_tempo());
2024 assert!(matches!(config.get_hardfork(), FoundryHardfork::Tempo(_)));
2025 }
2026
2027 #[test]
2028 fn get_hardfork_on_local_tempo_defaults_to_latest_active() {
2029 let config = NodeConfig::test_tempo();
2030
2031 assert_eq!(config.get_hardfork(), FoundryHardfork::Tempo(latest_active_tempo_hardfork()));
2032 }
2033}