1use crate::{
2 EthereumHardfork, FeeManager, PrecompileFactory,
3 eth::{
4 backend::{
5 db::{Db, SerializableState},
6 fork::{ClientFork, ClientForkConfig},
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::{PoolTransaction, TransactionOrder},
13 },
14 mem::{self, in_memory_db::MemDb},
15};
16use alloy_consensus::BlockHeader;
17use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
18use alloy_evm::EvmEnv;
19use alloy_genesis::Genesis;
20use alloy_network::{AnyNetwork, BlockResponse, TransactionResponse};
21use alloy_primitives::{Address, BlockNumber, TxHash, U256, hex, map::HashMap, utils::Unit};
22use alloy_provider::Provider;
23use alloy_rpc_types::BlockNumberOrTag;
24use alloy_signer::Signer;
25use alloy_signer_local::{
26 MnemonicBuilder, PrivateKeySigner,
27 coins_bip39::{English, Mnemonic},
28};
29use alloy_transport::TransportError;
30use anvil_server::ServerConfig;
31use eyre::{Context, Result};
32use foundry_common::{
33 ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT,
34 provider::{ProviderBuilder, RetryProvider},
35};
36use foundry_config::Config;
37use foundry_evm::{
38 backend::{BlockchainDb, BlockchainDbMeta, SharedBackend},
39 constants::DEFAULT_CREATE2_DEPLOYER,
40 hardfork::FoundryHardfork,
41 utils::{
42 apply_chain_and_block_specific_env_changes, block_env_from_header,
43 get_blob_base_fee_update_fraction,
44 },
45};
46use foundry_primitives::FoundryTxEnvelope;
47use itertools::Itertools;
48use parking_lot::RwLock;
49use rand_08::thread_rng;
50use revm::{
51 context::{BlockEnv, CfgEnv},
52 context_interface::block::BlobExcessGasAndPrice,
53 primitives::hardfork::SpecId,
54};
55use serde_json::{Value, json};
56use std::{
57 fmt::Write as FmtWrite,
58 net::{IpAddr, Ipv4Addr},
59 path::PathBuf,
60 sync::Arc,
61 time::Duration,
62};
63use tempo_chainspec::hardfork::TempoHardfork;
64use tokio::sync::RwLock as TokioRwLock;
65use yansi::Paint;
66
67pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
68use foundry_evm::{
69 traces::{CallTraceDecoderBuilder, identifier::SignaturesIdentifier},
70 utils::get_blob_params,
71};
72use foundry_evm_networks::NetworkConfigs;
73
74pub const NODE_PORT: u16 = 8545;
76pub const CHAIN_ID: u64 = 31337;
78pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
80pub const DEFAULT_SLOTS_IN_AN_EPOCH: u64 = 32;
82pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
84
85pub const DEFAULT_IPC_ENDPOINT: &str =
87 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
88
89const BANNER: &str = r"
90 _ _
91 (_) | |
92 __ _ _ __ __ __ _ | |
93 / _` | | '_ \ \ \ / / | | | |
94 | (_| | | | | | \ V / | | | |
95 \__,_| |_| |_| \_/ |_| |_|
96";
97
98#[derive(Clone, Debug)]
100pub struct NodeConfig {
101 pub chain_id: Option<u64>,
103 pub gas_limit: Option<u64>,
105 pub disable_block_gas_limit: bool,
107 pub enable_tx_gas_limit: bool,
109 pub gas_price: Option<u128>,
111 pub base_fee: Option<u64>,
113 pub disable_min_priority_fee: bool,
115 pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
117 pub hardfork: Option<FoundryHardfork>,
119 pub genesis_accounts: Vec<PrivateKeySigner>,
121 pub genesis_balance: U256,
123 pub genesis_timestamp: Option<u64>,
125 pub genesis_block_number: Option<u64>,
127 pub signer_accounts: Vec<PrivateKeySigner>,
129 pub block_time: Option<Duration>,
131 pub no_mining: bool,
133 pub mixed_mining: bool,
135 pub port: u16,
137 pub max_transactions: usize,
139 pub fork_urls: Vec<String>,
143 pub fork_choice: Option<ForkChoice>,
145 pub fork_headers: Vec<String>,
147 pub fork_chain_id: Option<U256>,
149 pub account_generator: Option<AccountGenerator>,
151 pub enable_tracing: bool,
153 pub no_storage_caching: bool,
155 pub server_config: ServerConfig,
157 pub host: Vec<IpAddr>,
159 pub transaction_order: TransactionOrder,
161 pub config_out: Option<PathBuf>,
163 pub genesis: Option<Genesis>,
165 pub fork_request_timeout: Duration,
167 pub fork_request_retries: u32,
169 pub fork_retry_backoff: Duration,
171 pub compute_units_per_second: u64,
173 pub ipc_path: Option<Option<String>>,
175 pub enable_steps_tracing: bool,
177 pub print_logs: bool,
179 pub print_traces: bool,
181 pub enable_auto_impersonate: bool,
183 pub code_size_limit: Option<usize>,
185 pub prune_history: PruneStateHistoryConfig,
189 pub max_persisted_states: Option<usize>,
191 pub init_state: Option<SerializableState>,
193 pub transaction_block_keeper: Option<usize>,
195 pub disable_default_create2_deployer: bool,
197 pub disable_pool_balance_checks: bool,
199 pub slots_in_an_epoch: u64,
201 pub memory_limit: Option<u64>,
203 pub precompile_factory: Option<Arc<dyn PrecompileFactory>>,
205 pub networks: NetworkConfigs,
207 pub silent: bool,
209 pub cache_path: Option<PathBuf>,
212 pub funded_accounts: HashMap<Address, U256>,
214}
215
216impl NodeConfig {
217 fn as_string(&self, fork: Option<&ClientFork>) -> String {
218 let mut s: String = String::new();
219 let _ = write!(s, "\n{}", BANNER.green());
220 let _ = write!(s, "\n {VERSION_MESSAGE}");
221 let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green());
222
223 let _ = write!(
224 s,
225 r#"
226
227Available Accounts
228==================
229"#
230 );
231 let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
232 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
233 write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap();
234 }
235
236 let _ = write!(
237 s,
238 r#"
239
240Private Keys
241==================
242"#
243 );
244
245 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
246 let hex = hex::encode(wallet.credential().to_bytes());
247 let _ = write!(s, "\n({idx}) 0x{hex}");
248 }
249
250 if let Some(generator) = &self.account_generator {
251 let _ = write!(
252 s,
253 r#"
254
255Wallet
256==================
257Mnemonic: {}
258Derivation path: {}
259"#,
260 generator.phrase,
261 generator.get_derivation_path()
262 );
263 }
264
265 if let Some(fork) = fork {
266 let _ = write!(
267 s,
268 r#"
269
270Fork
271==================
272Endpoint: {}
273Block number: {}
274Block hash: {:?}
275Chain ID: {}
276"#,
277 fork.eth_rpc_url().as_deref().unwrap_or("none"),
278 fork.block_number(),
279 fork.block_hash(),
280 fork.chain_id()
281 );
282
283 if self.fork_urls.len() > 1 {
284 let _ = writeln!(s, "Endpoints: {}", self.fork_urls.len());
285 for (i, url) in self.fork_urls.iter().enumerate() {
286 let _ = writeln!(s, " ({i}) {url}");
287 }
288 }
289
290 if let Some(tx_hash) = fork.transaction_hash() {
291 let _ = writeln!(s, "Transaction hash: {tx_hash}");
292 }
293 } else {
294 let _ = write!(
295 s,
296 r#"
297
298Chain ID
299==================
300
301{}
302"#,
303 self.get_chain_id().green()
304 );
305 }
306
307 if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) {
308 let _ = write!(
309 s,
310 r#"
311Gas Price
312==================
313
314{}
315"#,
316 self.get_gas_price().green()
317 );
318 } else {
319 let _ = write!(
320 s,
321 r#"
322Base Fee
323==================
324
325{}
326"#,
327 self.get_base_fee().green()
328 );
329 }
330
331 let _ = write!(
332 s,
333 r#"
334Gas Limit
335==================
336
337{}
338"#,
339 {
340 if self.disable_block_gas_limit {
341 "Disabled".to_string()
342 } else {
343 self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| {
344 if self.fork_choice.is_some() {
345 "Forked".to_string()
346 } else {
347 DEFAULT_GAS_LIMIT.to_string()
348 }
349 })
350 }
351 }
352 .green()
353 );
354
355 let _ = write!(
356 s,
357 r#"
358Genesis Timestamp
359==================
360
361{}
362"#,
363 self.get_genesis_timestamp().green()
364 );
365
366 let _ = write!(
367 s,
368 r#"
369Genesis Number
370==================
371
372{}
373"#,
374 self.get_genesis_number().green()
375 );
376
377 s
378 }
379
380 fn as_json(&self, fork: Option<&ClientFork>) -> Value {
381 let mut wallet_description = HashMap::new();
382 let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
383 let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
384
385 for wallet in &self.genesis_accounts {
386 available_accounts.push(format!("{:?}", wallet.address()));
387 private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes())));
388 }
389
390 if let Some(generator) = &self.account_generator {
391 let phrase = generator.get_phrase().to_string();
392 let derivation_path = generator.get_derivation_path().to_string();
393
394 wallet_description.insert("derivation_path".to_string(), derivation_path);
395 wallet_description.insert("mnemonic".to_string(), phrase);
396 };
397
398 let gas_limit = match self.gas_limit {
399 Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()),
401 Some(limit) => Some(limit.to_string()),
402 _ => None,
403 };
404
405 if let Some(fork) = fork {
406 json!({
407 "available_accounts": available_accounts,
408 "private_keys": private_keys,
409 "endpoint": fork.eth_rpc_url().unwrap_or_default(),
410 "block_number": fork.block_number(),
411 "block_hash": fork.block_hash(),
412 "chain_id": fork.chain_id(),
413 "wallet": wallet_description,
414 "base_fee": format!("{}", self.get_base_fee()),
415 "gas_price": format!("{}", self.get_gas_price()),
416 "gas_limit": gas_limit,
417 })
418 } else {
419 json!({
420 "available_accounts": available_accounts,
421 "private_keys": private_keys,
422 "wallet": wallet_description,
423 "base_fee": format!("{}", self.get_base_fee()),
424 "gas_price": format!("{}", self.get_gas_price()),
425 "gas_limit": gas_limit,
426 "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
427 })
428 }
429 }
430}
431
432impl NodeConfig {
433 #[doc(hidden)]
436 pub fn test() -> Self {
437 Self { enable_tracing: true, port: 0, silent: true, ..Default::default() }
438 }
439
440 #[doc(hidden)]
442 pub fn test_tempo() -> Self {
443 Self { networks: NetworkConfigs::with_tempo(), ..Self::test() }
444 }
445
446 pub fn empty_state() -> Self {
448 Self {
449 genesis_accounts: vec![],
450 signer_accounts: vec![],
451 disable_default_create2_deployer: true,
452 ..Default::default()
453 }
454 }
455}
456
457impl Default for NodeConfig {
458 fn default() -> Self {
459 let genesis_accounts = AccountGenerator::new(10)
461 .phrase(DEFAULT_MNEMONIC)
462 .generate()
463 .expect("Invalid mnemonic.");
464 Self {
465 chain_id: None,
466 gas_limit: None,
467 disable_block_gas_limit: false,
468 enable_tx_gas_limit: false,
469 gas_price: None,
470 hardfork: None,
471 signer_accounts: genesis_accounts.clone(),
472 genesis_timestamp: None,
473 genesis_block_number: None,
474 genesis_accounts,
475 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
477 block_time: None,
478 no_mining: false,
479 mixed_mining: false,
480 port: NODE_PORT,
481 max_transactions: 1_000,
482 fork_urls: vec![],
483 fork_choice: None,
484 account_generator: None,
485 base_fee: None,
486 disable_min_priority_fee: false,
487 blob_excess_gas_and_price: None,
488 enable_tracing: true,
489 enable_steps_tracing: false,
490 print_logs: true,
491 print_traces: false,
492 enable_auto_impersonate: false,
493 no_storage_caching: false,
494 server_config: Default::default(),
495 host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
496 transaction_order: Default::default(),
497 config_out: None,
498 genesis: None,
499 fork_request_timeout: REQUEST_TIMEOUT,
500 fork_headers: vec![],
501 fork_request_retries: 5,
502 fork_retry_backoff: Duration::from_millis(1_000),
503 fork_chain_id: None,
504 compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
506 ipc_path: None,
507 code_size_limit: None,
508 prune_history: Default::default(),
509 max_persisted_states: None,
510 init_state: None,
511 transaction_block_keeper: None,
512 disable_default_create2_deployer: false,
513 disable_pool_balance_checks: false,
514 slots_in_an_epoch: DEFAULT_SLOTS_IN_AN_EPOCH,
515 memory_limit: None,
516 precompile_factory: None,
517 networks: Default::default(),
518 silent: false,
519 cache_path: None,
520 funded_accounts: HashMap::default(),
521 }
522 }
523}
524
525impl NodeConfig {
526 #[must_use]
528 pub const fn with_memory_limit(mut self, mems_value: Option<u64>) -> Self {
529 self.memory_limit = mems_value;
530 self
531 }
532
533 pub fn get_base_fee(&self) -> u64 {
537 let default = if self.networks.is_tempo() {
538 TempoHardfork::from(self.get_hardfork()).base_fee()
539 } else {
540 INITIAL_BASE_FEE
541 };
542 self.base_fee
543 .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64)))
544 .unwrap_or(default)
545 }
546
547 pub fn get_gas_price(&self) -> u128 {
551 let default = if self.networks.is_tempo() {
552 TempoHardfork::from(self.get_hardfork()).base_fee() as u128
553 } else {
554 INITIAL_GAS_PRICE
555 };
556 self.gas_price.unwrap_or(default)
557 }
558
559 pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice {
560 if let Some(value) = self.blob_excess_gas_and_price {
561 value
562 } else {
563 let excess_blob_gas =
564 self.genesis.as_ref().and_then(|g| g.excess_blob_gas).unwrap_or(0);
565 BlobExcessGasAndPrice::new(
566 excess_blob_gas,
567 get_blob_base_fee_update_fraction(
568 self.get_chain_id(),
569 self.get_genesis_timestamp(),
570 ),
571 )
572 }
573 }
574
575 pub fn get_blob_params(&self) -> BlobParams {
577 get_blob_params(self.get_chain_id(), self.get_genesis_timestamp())
578 }
579
580 pub fn get_hardfork(&self) -> FoundryHardfork {
582 if let Some(hardfork) = self.hardfork {
583 return hardfork;
584 }
585 #[cfg(feature = "optimism")]
586 if self.networks.is_optimism() {
587 return foundry_evm::hardforks::OpHardfork::default().into();
588 }
589 if self.networks.is_tempo() {
590 return TempoHardfork::default().into();
591 }
592 EthereumHardfork::default().into()
593 }
594
595 #[must_use]
597 pub const fn with_code_size_limit(mut self, code_size_limit: Option<usize>) -> Self {
598 self.code_size_limit = code_size_limit;
599 self
600 }
601 #[must_use]
603 pub const fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self {
604 if disable_code_size_limit {
605 self.code_size_limit = Some(usize::MAX);
606 }
607 self
608 }
609
610 #[must_use]
612 pub fn with_init_state(mut self, init_state: Option<SerializableState>) -> Self {
613 self.init_state = init_state;
614 self
615 }
616
617 #[must_use]
619 #[cfg(feature = "cmd")]
620 pub fn with_init_state_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
621 self.init_state = crate::cmd::StateFile::parse_path(path).ok().and_then(|file| file.state);
622 self
623 }
624
625 #[must_use]
627 pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
628 self.set_chain_id(chain_id);
629 self
630 }
631
632 pub fn get_chain_id(&self) -> u64 {
634 self.chain_id
635 .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
636 .unwrap_or(CHAIN_ID)
637 }
638
639 pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
641 self.chain_id = chain_id.map(Into::into);
642 let chain_id = self.get_chain_id();
643 self.networks = self.networks.with_chain_id(chain_id);
644 self.genesis_accounts.iter_mut().for_each(|wallet| {
645 *wallet = wallet.clone().with_chain_id(Some(chain_id));
646 });
647 self.signer_accounts.iter_mut().for_each(|wallet| {
648 *wallet = wallet.clone().with_chain_id(Some(chain_id));
649 })
650 }
651
652 #[must_use]
654 pub const fn with_gas_limit(mut self, gas_limit: Option<u64>) -> Self {
655 self.gas_limit = gas_limit;
656 self
657 }
658
659 #[must_use]
663 pub const fn disable_block_gas_limit(mut self, disable_block_gas_limit: bool) -> Self {
664 self.disable_block_gas_limit = disable_block_gas_limit;
665 self
666 }
667
668 #[must_use]
672 pub const fn enable_tx_gas_limit(mut self, enable_tx_gas_limit: bool) -> Self {
673 self.enable_tx_gas_limit = enable_tx_gas_limit;
674 self
675 }
676
677 #[must_use]
679 pub const fn with_gas_price(mut self, gas_price: Option<u128>) -> Self {
680 self.gas_price = gas_price;
681 self
682 }
683
684 #[must_use]
686 pub fn set_pruned_history(mut self, prune_history: Option<Option<usize>>) -> Self {
687 self.prune_history = PruneStateHistoryConfig::from_args(prune_history);
688 self
689 }
690
691 #[must_use]
693 pub fn with_max_persisted_states<U: Into<usize>>(
694 mut self,
695 max_persisted_states: Option<U>,
696 ) -> Self {
697 self.max_persisted_states = max_persisted_states.map(Into::into);
698 self
699 }
700
701 #[must_use]
703 pub const fn with_max_transactions(mut self, max_transactions: Option<usize>) -> Self {
704 if let Some(max_transactions) = max_transactions {
705 self.max_transactions = max_transactions;
706 }
707 self
708 }
709
710 #[must_use]
712 pub fn with_transaction_block_keeper<U: Into<usize>>(
713 mut self,
714 transaction_block_keeper: Option<U>,
715 ) -> Self {
716 self.transaction_block_keeper = transaction_block_keeper.map(Into::into);
717 self
718 }
719
720 #[must_use]
722 pub const fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
723 self.base_fee = base_fee;
724 self
725 }
726
727 #[must_use]
729 pub const fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self {
730 self.disable_min_priority_fee = disable_min_priority_fee;
731 self
732 }
733
734 #[must_use]
736 pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
737 self.genesis = genesis;
738 self
739 }
740
741 pub fn get_genesis_timestamp(&self) -> u64 {
743 self.genesis_timestamp
744 .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
745 .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
746 }
747
748 #[must_use]
750 pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
751 if let Some(timestamp) = timestamp {
752 self.genesis_timestamp = Some(timestamp.into());
753 }
754 self
755 }
756
757 #[must_use]
759 pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
760 if let Some(number) = number {
761 self.genesis_block_number = Some(number.into());
762 }
763 self
764 }
765
766 pub fn get_genesis_number(&self) -> u64 {
768 self.genesis_block_number
769 .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
770 .unwrap_or(0)
771 }
772
773 #[must_use]
775 pub const fn with_hardfork(mut self, hardfork: Option<FoundryHardfork>) -> Self {
776 self.hardfork = hardfork;
777 self
778 }
779
780 #[must_use]
782 pub fn with_genesis_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
783 self.genesis_accounts = accounts;
784 self
785 }
786
787 #[must_use]
789 pub fn with_signer_accounts(mut self, accounts: Vec<PrivateKeySigner>) -> Self {
790 self.signer_accounts = accounts;
791 self
792 }
793
794 pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
797 let accounts = generator.generate()?;
798 self.account_generator = Some(generator);
799 Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
800 }
801
802 #[must_use]
804 pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
805 self.genesis_balance = balance.into();
806 self
807 }
808
809 #[must_use]
811 pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
812 self.block_time = block_time.map(Into::into);
813 self
814 }
815
816 #[must_use]
817 pub fn with_mixed_mining<D: Into<Duration>>(
818 mut self,
819 mixed_mining: bool,
820 block_time: Option<D>,
821 ) -> Self {
822 self.block_time = block_time.map(Into::into);
823 self.mixed_mining = mixed_mining;
824 self
825 }
826
827 #[must_use]
829 pub const fn with_no_mining(mut self, no_mining: bool) -> Self {
830 self.no_mining = no_mining;
831 self
832 }
833
834 #[must_use]
836 pub const fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self {
837 self.slots_in_an_epoch = slots_in_an_epoch;
838 self
839 }
840
841 #[must_use]
843 pub const fn with_port(mut self, port: u16) -> Self {
844 self.port = port;
845 self
846 }
847
848 #[must_use]
855 pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
856 self.ipc_path = ipc_path;
857 self
858 }
859
860 #[must_use]
862 pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
863 self.config_out = config_out;
864 self
865 }
866
867 #[must_use]
868 pub const fn with_no_storage_caching(mut self, no_storage_caching: bool) -> Self {
869 self.no_storage_caching = no_storage_caching;
870 self
871 }
872
873 #[must_use]
875 pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
876 if let Some(url) = eth_rpc_url {
877 self.fork_urls = vec![url.into()];
878 }
879 self
880 }
881
882 #[must_use]
884 pub fn with_fork_urls(mut self, fork_urls: Vec<String>) -> Self {
885 self.fork_urls = fork_urls;
886 self
887 }
888
889 #[must_use]
891 pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
892 self.with_fork_choice(fork_block_number.map(Into::into))
893 }
894
895 #[must_use]
897 pub fn with_fork_transaction_hash<U: Into<TxHash>>(
898 self,
899 fork_transaction_hash: Option<U>,
900 ) -> Self {
901 self.with_fork_choice(fork_transaction_hash.map(Into::into))
902 }
903
904 #[must_use]
906 pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
907 self.fork_choice = fork_choice.map(Into::into);
908 self
909 }
910
911 #[must_use]
913 pub const fn with_fork_chain_id(mut self, fork_chain_id: Option<U256>) -> Self {
914 self.fork_chain_id = fork_chain_id;
915 self
916 }
917
918 #[must_use]
920 pub fn with_fork_headers(mut self, headers: Vec<String>) -> Self {
921 self.fork_headers = headers;
922 self
923 }
924
925 #[must_use]
927 pub const fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
928 if let Some(fork_request_timeout) = fork_request_timeout {
929 self.fork_request_timeout = fork_request_timeout;
930 }
931 self
932 }
933
934 #[must_use]
936 pub const fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
937 if let Some(fork_request_retries) = fork_request_retries {
938 self.fork_request_retries = fork_request_retries;
939 }
940 self
941 }
942
943 #[must_use]
945 pub const fn fork_retry_backoff(mut self, fork_retry_backoff: Option<Duration>) -> Self {
946 if let Some(fork_retry_backoff) = fork_retry_backoff {
947 self.fork_retry_backoff = fork_retry_backoff;
948 }
949 self
950 }
951
952 #[must_use]
956 pub const fn fork_compute_units_per_second(
957 mut self,
958 compute_units_per_second: Option<u64>,
959 ) -> Self {
960 if let Some(compute_units_per_second) = compute_units_per_second {
961 self.compute_units_per_second = compute_units_per_second;
962 }
963 self
964 }
965
966 #[must_use]
968 pub const fn with_tracing(mut self, enable_tracing: bool) -> Self {
969 self.enable_tracing = enable_tracing;
970 self
971 }
972
973 #[must_use]
975 pub const fn with_steps_tracing(mut self, enable_steps_tracing: bool) -> Self {
976 self.enable_steps_tracing = enable_steps_tracing;
977 self
978 }
979
980 #[must_use]
982 pub const fn with_print_logs(mut self, print_logs: bool) -> Self {
983 self.print_logs = print_logs;
984 self
985 }
986
987 #[must_use]
989 pub const fn with_print_traces(mut self, print_traces: bool) -> Self {
990 self.print_traces = print_traces;
991 self
992 }
993
994 #[must_use]
996 pub const fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
997 self.enable_auto_impersonate = enable_auto_impersonate;
998 self
999 }
1000
1001 #[must_use]
1002 pub fn with_server_config(mut self, config: ServerConfig) -> Self {
1003 self.server_config = config;
1004 self
1005 }
1006
1007 #[must_use]
1009 pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
1010 self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
1011 self
1012 }
1013
1014 #[must_use]
1015 pub const fn with_transaction_order(mut self, transaction_order: TransactionOrder) -> Self {
1016 self.transaction_order = transaction_order;
1017 self
1018 }
1019
1020 pub fn get_ipc_path(&self) -> Option<String> {
1022 match &self.ipc_path {
1023 Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
1024 None => None,
1025 }
1026 }
1027
1028 pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> {
1030 if let Some(path) = &self.config_out {
1031 let value = self.as_json(fork);
1032 foundry_common::fs::write_json_file(path, &value).wrap_err("failed writing JSON")?;
1033 }
1034 if !self.silent {
1035 sh_println!("{}", self.as_string(fork))?;
1036 }
1037 Ok(())
1038 }
1039
1040 pub fn block_cache_path(&self, block: u64) -> Option<PathBuf> {
1044 if self.no_storage_caching || self.fork_urls.is_empty() {
1045 return None;
1046 }
1047 let chain_id = self.get_chain_id();
1048
1049 Config::foundry_block_cache_file(chain_id, block)
1050 }
1051
1052 #[must_use]
1054 pub const fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self {
1055 self.disable_default_create2_deployer = yes;
1056 self
1057 }
1058
1059 #[must_use]
1061 pub const fn with_disable_pool_balance_checks(mut self, yes: bool) -> Self {
1062 self.disable_pool_balance_checks = yes;
1063 self
1064 }
1065
1066 #[must_use]
1068 pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self {
1069 self.precompile_factory = Some(Arc::new(factory));
1070 self
1071 }
1072
1073 #[must_use]
1075 pub const fn with_networks(mut self, networks: NetworkConfigs) -> Self {
1076 self.networks = networks;
1077 self
1078 }
1079
1080 #[must_use]
1082 pub fn with_tempo(mut self) -> Self {
1083 self.networks = NetworkConfigs::with_tempo();
1084 self
1085 }
1086
1087 #[cfg(feature = "optimism")]
1089 #[must_use]
1090 pub fn with_optimism(mut self) -> Self {
1091 self.networks = NetworkConfigs::with_optimism();
1092 self
1093 }
1094
1095 #[must_use]
1097 pub const fn silent(self) -> Self {
1098 self.set_silent(true)
1099 }
1100
1101 #[must_use]
1102 pub const fn set_silent(mut self, silent: bool) -> Self {
1103 self.silent = silent;
1104 self
1105 }
1106
1107 #[must_use]
1112 pub fn with_cache_path(mut self, cache_path: Option<PathBuf>) -> Self {
1113 self.cache_path = cache_path;
1114 self
1115 }
1116
1117 #[must_use]
1119 pub fn with_funded_accounts(mut self, accounts: HashMap<Address, U256>) -> Self {
1120 self.funded_accounts = accounts;
1121 self
1122 }
1123
1124 pub(crate) async fn setup<N>(&mut self) -> Result<mem::Backend<N>>
1129 where
1130 N: alloy_network::Network<
1131 TxEnvelope = foundry_primitives::FoundryTxEnvelope,
1132 ReceiptEnvelope = foundry_primitives::FoundryReceiptEnvelope,
1133 >,
1134 {
1135 let mut cfg = CfgEnv::default();
1138 cfg.spec = self.get_hardfork().into();
1139
1140 cfg.chain_id = self.get_chain_id();
1141 cfg.limit_contract_code_size = self.code_size_limit;
1142 cfg.disable_eip3607 = true;
1146 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1147
1148 if !self.enable_tx_gas_limit {
1149 cfg.tx_gas_limit_cap = Some(u64::MAX);
1150 }
1151
1152 if let Some(value) = self.memory_limit {
1153 cfg.memory_limit = value;
1154 }
1155
1156 let spec_id = cfg.spec;
1157 let mut evm_env = EvmEnv::new(
1158 cfg,
1159 BlockEnv {
1160 gas_limit: self.gas_limit(),
1161 basefee: self.get_base_fee(),
1162 ..Default::default()
1163 },
1164 );
1165
1166 let base_fee_params: BaseFeeParams =
1167 self.networks.base_fee_params(self.get_genesis_timestamp());
1168
1169 let fees = FeeManager::new(
1170 spec_id,
1171 self.get_base_fee(),
1172 !self.disable_min_priority_fee,
1173 self.get_gas_price(),
1174 self.get_blob_excess_gas_and_price(),
1175 self.get_blob_params(),
1176 base_fee_params,
1177 );
1178
1179 let (db, fork): (Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>) =
1180 if let Some(eth_rpc_url) = self.fork_urls.first().cloned() {
1181 self.setup_fork_db(eth_rpc_url, &mut evm_env, &fees).await?
1182 } else {
1183 (Arc::new(TokioRwLock::new(Box::<MemDb>::default())), None)
1184 };
1185
1186 if let Some(ref genesis) = self.genesis {
1188 if self.chain_id.is_none() {
1191 evm_env.cfg_env.chain_id = genesis.config.chain_id;
1192 }
1193 evm_env.block_env.timestamp = U256::from(genesis.timestamp);
1194 if let Some(base_fee) = genesis.base_fee_per_gas {
1195 evm_env.block_env.basefee = base_fee.try_into()?;
1196 }
1197 if let Some(number) = genesis.number {
1198 evm_env.block_env.number = U256::from(number);
1199 }
1200 evm_env.block_env.beneficiary = genesis.coinbase;
1201 }
1202
1203 let genesis = GenesisConfig {
1204 number: self.get_genesis_number(),
1205 timestamp: self.get_genesis_timestamp(),
1206 balance: self.genesis_balance,
1207 accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(),
1208 genesis_init: self.genesis.clone(),
1209 };
1210
1211 let mut decoder_builder = CallTraceDecoderBuilder::new();
1212 if self.print_traces {
1213 if let Ok(identifier) = SignaturesIdentifier::new(false) {
1215 debug!(target: "node", "using signature identifier");
1216 decoder_builder = decoder_builder.with_signature_identifier(identifier);
1217 }
1218 }
1219
1220 let backend = mem::Backend::with_genesis(
1222 db,
1223 Arc::new(RwLock::new(evm_env)),
1224 self.networks,
1225 genesis,
1226 fees,
1227 Arc::new(RwLock::new(fork)),
1228 self.enable_steps_tracing,
1229 self.print_logs,
1230 self.print_traces,
1231 Arc::new(decoder_builder.build()),
1232 self.prune_history,
1233 self.max_persisted_states,
1234 self.transaction_block_keeper,
1235 self.block_time,
1236 self.cache_path.clone(),
1237 Arc::new(TokioRwLock::new(self.clone())),
1238 )
1239 .await?;
1240
1241 if !self.disable_default_create2_deployer && self.fork_urls.is_empty() {
1244 backend
1245 .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER)
1246 .await
1247 .wrap_err("failed to create default create2 deployer")?;
1248 }
1249
1250 if !self.funded_accounts.is_empty() {
1251 for (address, balance) in &self.funded_accounts {
1252 backend
1253 .set_balance(*address, *balance)
1254 .await
1255 .wrap_err_with(|| format!("failed to fund account {address}"))?;
1256 }
1257 }
1258
1259 Ok(backend)
1260 }
1261
1262 pub async fn setup_fork_db(
1269 &mut self,
1270 eth_rpc_url: String,
1271 evm_env: &mut EvmEnv,
1272 fees: &FeeManager,
1273 ) -> Result<(Arc<TokioRwLock<Box<dyn Db>>>, Option<ClientFork>)> {
1274 let (db, config) = self.setup_fork_db_config(eth_rpc_url, evm_env, fees).await?;
1275 let db: Arc<TokioRwLock<Box<dyn Db>>> = Arc::new(TokioRwLock::new(Box::new(db)));
1276 let fork = ClientFork::new(config, Arc::clone(&db));
1277 Ok((db, Some(fork)))
1278 }
1279
1280 pub async fn setup_fork_db_config(
1286 &mut self,
1287 eth_rpc_url: String,
1288 evm_env: &mut EvmEnv,
1289 fees: &FeeManager,
1290 ) -> Result<(ForkedDatabase<AnyNetwork>, ClientForkConfig)> {
1291 debug!(target: "node", ?eth_rpc_url, "setting up fork db");
1292
1293 let provider = Arc::new(
1297 ProviderBuilder::new(ð_rpc_url)
1298 .timeout(self.fork_request_timeout)
1299 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1300 .compute_units_per_second(self.compute_units_per_second)
1301 .max_retry(self.fork_request_retries)
1302 .headers(self.fork_headers.clone())
1303 .build()
1304 .wrap_err("failed to establish provider to fork url")?,
1305 );
1306
1307 let (fork_block_number, fork_chain_id, force_transactions) = if let Some(fork_choice) =
1308 &self.fork_choice
1309 {
1310 let (fork_block_number, force_transactions) =
1311 derive_block_and_transactions(fork_choice, &provider).await.wrap_err(
1312 "failed to derive fork block number and force transactions from fork choice",
1313 )?;
1314 let chain_id = if let Some(chain_id) = self.fork_chain_id {
1315 Some(chain_id)
1316 } else if self.hardfork.is_none() {
1317 let chain_id =
1318 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?;
1319 Some(U256::from(chain_id))
1320 } else {
1321 None
1322 };
1323
1324 (fork_block_number, chain_id, force_transactions)
1325 } else {
1326 let bn = find_latest_fork_block(&provider)
1328 .await
1329 .wrap_err("failed to get fork block number")?;
1330 (bn, None, None)
1331 };
1332
1333 let block = provider
1334 .get_block(BlockNumberOrTag::Number(fork_block_number).into())
1335 .await
1336 .wrap_err("failed to get fork block")?;
1337
1338 let block = if let Some(block) = block {
1339 block
1340 } else {
1341 if let Ok(latest_block) = provider.get_block_number().await {
1342 let mut message = format!(
1343 "Failed to get block for block number: {fork_block_number}\n\
1344latest block number: {latest_block}"
1345 );
1346 if fork_block_number <= latest_block {
1350 message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}"));
1351 }
1352 eyre::bail!("{message}");
1353 }
1354 eyre::bail!("failed to get block for block number: {fork_block_number}")
1355 };
1356
1357 let gas_limit = self.fork_gas_limit(&block);
1358 self.gas_limit = Some(gas_limit);
1359
1360 evm_env.block_env = BlockEnv {
1361 gas_limit,
1362 beneficiary: evm_env.block_env.beneficiary,
1364 basefee: evm_env.block_env.basefee,
1365 ..block_env_from_header(&block.header)
1366 };
1367
1368 let chain_id = if let Some(chain_id) = self.chain_id {
1370 chain_id
1371 } else {
1372 let chain_id = if let Some(fork_chain_id) = fork_chain_id {
1373 fork_chain_id.to()
1374 } else {
1375 provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?
1376 };
1377
1378 self.set_chain_id(Some(chain_id));
1380 evm_env.cfg_env.chain_id = chain_id;
1381 chain_id
1382 };
1383
1384 if self.hardfork.is_none()
1386 && let Some(hardfork) =
1387 FoundryHardfork::from_chain_and_timestamp(chain_id, block.header.timestamp())
1388 {
1389 evm_env.cfg_env.spec = SpecId::from(hardfork);
1390 self.hardfork = Some(hardfork);
1391 }
1392
1393 if self.base_fee.is_none()
1395 && let Some(base_fee) = block.header.base_fee_per_gas()
1396 {
1397 self.base_fee = Some(base_fee);
1398 evm_env.block_env.basefee = base_fee;
1399 let next_block_base_fee = fees.get_next_block_base_fee_per_gas(
1402 block.header.gas_used(),
1403 gas_limit,
1404 block.header.base_fee_per_gas().unwrap_or_default(),
1405 );
1406
1407 fees.set_base_fee(next_block_base_fee);
1409 }
1410
1411 if let (Some(blob_excess_gas), Some(blob_gas_used)) =
1412 (block.header.excess_blob_gas(), block.header.blob_gas_used())
1413 {
1414 let blob_params = get_blob_params(chain_id, block.header.timestamp());
1416
1417 evm_env.block_env.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
1418 blob_excess_gas,
1419 blob_params.update_fraction as u64,
1420 ));
1421
1422 fees.set_blob_params(blob_params);
1423
1424 let next_block_blob_excess_gas =
1425 fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used);
1426 fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(
1427 next_block_blob_excess_gas,
1428 blob_params.update_fraction as u64,
1429 ));
1430 }
1431
1432 if self.gas_price.is_none()
1434 && let Ok(gas_price) = provider.get_gas_price().await
1435 {
1436 self.gas_price = Some(gas_price);
1437 fees.set_gas_price(gas_price);
1438 }
1439
1440 let block_hash = block.header.hash;
1441
1442 let override_chain_id = self.chain_id;
1443 apply_chain_and_block_specific_env_changes::<AnyNetwork, _, _>(
1445 evm_env,
1446 &block,
1447 self.networks,
1448 );
1449
1450 let meta = BlockchainDbMeta::new(evm_env.block_env.clone(), eth_rpc_url.clone());
1451 let block_chain_db = if self.fork_chain_id.is_some() {
1452 BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number))
1453 } else {
1454 BlockchainDb::new(meta, self.block_cache_path(fork_block_number))
1455 };
1456
1457 let provider = if self.fork_urls.len() > 1 {
1461 debug!(target: "node", urls=?self.fork_urls, "using multi-endpoint round-robin provider");
1462 Arc::new(
1463 ProviderBuilder::new(ð_rpc_url)
1464 .timeout(self.fork_request_timeout)
1465 .initial_backoff(self.fork_retry_backoff.as_millis() as u64)
1466 .compute_units_per_second(self.compute_units_per_second)
1467 .max_retry(self.fork_request_retries)
1468 .headers(self.fork_headers.clone())
1469 .build_fallback(self.fork_urls.clone())
1470 .wrap_err("failed to establish round-robin provider to fork urls")?,
1471 )
1472 } else {
1473 provider
1474 };
1475
1476 let backend = SharedBackend::spawn_backend(
1479 Arc::clone(&provider),
1480 block_chain_db.clone(),
1481 Some(fork_block_number.into()),
1482 )
1483 .await;
1484
1485 let config = ClientForkConfig {
1486 fork_urls: self.fork_urls.clone(),
1487 block_number: fork_block_number,
1488 block_hash,
1489 transaction_hash: self.fork_choice.and_then(|fc| fc.transaction_hash()),
1490 provider,
1491 chain_id,
1492 override_chain_id,
1493 hardfork: self.hardfork,
1494 timestamp: block.header.timestamp(),
1495 base_fee: block.header.base_fee_per_gas().map(|g| g as u128),
1496 timeout: self.fork_request_timeout,
1497 retries: self.fork_request_retries,
1498 backoff: self.fork_retry_backoff,
1499 compute_units_per_second: self.compute_units_per_second,
1500 headers: self.fork_headers.clone(),
1501 total_difficulty: block.header.total_difficulty.unwrap_or_default(),
1502 blob_gas_used: block.header.blob_gas_used().map(|g| g as u128),
1503 blob_excess_gas_and_price: evm_env.block_env.blob_excess_gas_and_price,
1504 force_transactions,
1505 };
1506
1507 debug!(target: "node", fork_number=config.block_number, fork_hash=%config.block_hash, "set up fork db");
1508
1509 let mut db = ForkedDatabase::new(backend, block_chain_db);
1510
1511 db.insert_block_hash(U256::from(config.block_number), config.block_hash);
1513
1514 Ok((db, config))
1515 }
1516
1517 pub(crate) fn fork_gas_limit<B: BlockResponse<Header: BlockHeader>>(&self, block: &B) -> u64 {
1521 if !self.disable_block_gas_limit {
1522 if let Some(gas_limit) = self.gas_limit {
1523 return gas_limit;
1524 } else if block.header().gas_limit() > 0 {
1525 return block.header().gas_limit();
1526 }
1527 }
1528
1529 u64::MAX
1530 }
1531
1532 pub(crate) fn gas_limit(&self) -> u64 {
1536 if self.disable_block_gas_limit {
1537 return u64::MAX;
1538 }
1539
1540 self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)
1541 }
1542}
1543
1544async fn derive_block_and_transactions(
1549 fork_choice: &ForkChoice,
1550 provider: &Arc<RetryProvider>,
1551) -> eyre::Result<(BlockNumber, Option<Vec<PoolTransaction<FoundryTxEnvelope>>>)> {
1552 match fork_choice {
1553 ForkChoice::Block(block_number) => {
1554 let block_number = *block_number;
1555 if block_number >= 0 {
1556 return Ok((block_number as u64, None));
1557 }
1558 let latest = provider.get_block_number().await?;
1560
1561 Ok((block_number.saturating_add(latest as i128) as u64, None))
1562 }
1563 ForkChoice::Transaction(transaction_hash) => {
1564 let transaction = provider
1566 .get_transaction_by_hash(transaction_hash.0.into())
1567 .await?
1568 .ok_or_else(|| eyre::eyre!("failed to get fork transaction by hash"))?;
1569 let transaction_block_number = transaction.block_number().ok_or_else(|| {
1570 eyre::eyre!("fork transaction is not mined yet (no block number)")
1571 })?;
1572
1573 let transaction_block = provider
1575 .get_block_by_number(transaction_block_number.into())
1576 .full()
1577 .await?
1578 .ok_or_else(|| eyre::eyre!("failed to get fork block by number"))?;
1579
1580 let filtered_transactions = transaction_block
1582 .transactions
1583 .as_transactions()
1584 .ok_or_else(|| eyre::eyre!("failed to get transactions from full fork block"))?
1585 .iter()
1586 .take_while_inclusive(|&transaction| transaction.tx_hash() != transaction_hash.0)
1587 .collect::<Vec<_>>();
1588
1589 let force_transactions = filtered_transactions
1591 .iter()
1592 .map(|&transaction| PoolTransaction::try_from(transaction.clone()))
1593 .collect::<Result<Vec<_>, _>>()
1594 .map_err(|e| eyre::eyre!("Err converting to pool transactions {e}"))?;
1595 Ok((transaction_block_number.saturating_sub(1), Some(force_transactions)))
1596 }
1597 }
1598}
1599
1600#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1602pub enum ForkChoice {
1603 Block(i128),
1607 Transaction(TxHash),
1609}
1610
1611impl ForkChoice {
1612 pub const fn block_number(&self) -> Option<i128> {
1614 match self {
1615 Self::Block(block_number) => Some(*block_number),
1616 Self::Transaction(_) => None,
1617 }
1618 }
1619
1620 pub const fn transaction_hash(&self) -> Option<TxHash> {
1622 match self {
1623 Self::Block(_) => None,
1624 Self::Transaction(transaction_hash) => Some(*transaction_hash),
1625 }
1626 }
1627}
1628
1629impl From<TxHash> for ForkChoice {
1631 fn from(tx_hash: TxHash) -> Self {
1632 Self::Transaction(tx_hash)
1633 }
1634}
1635
1636impl From<u64> for ForkChoice {
1638 fn from(block: u64) -> Self {
1639 Self::Block(block as i128)
1640 }
1641}
1642
1643#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1644pub struct PruneStateHistoryConfig {
1645 pub enabled: bool,
1646 pub max_memory_history: Option<usize>,
1647}
1648
1649impl PruneStateHistoryConfig {
1650 pub const fn is_state_history_supported(&self) -> bool {
1652 !self.enabled || self.max_memory_history.is_some()
1653 }
1654
1655 pub const fn is_config_enabled(&self) -> bool {
1657 self.enabled
1658 }
1659
1660 pub fn from_args(val: Option<Option<usize>>) -> Self {
1661 val.map(|max_memory_history| Self { enabled: true, max_memory_history }).unwrap_or_default()
1662 }
1663}
1664
1665#[derive(Clone, Debug)]
1667pub struct AccountGenerator {
1668 chain_id: u64,
1669 amount: usize,
1670 phrase: String,
1671 derivation_path: Option<String>,
1672}
1673
1674impl AccountGenerator {
1675 pub fn new(amount: usize) -> Self {
1676 Self {
1677 chain_id: CHAIN_ID,
1678 amount,
1679 phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
1680 derivation_path: None,
1681 }
1682 }
1683
1684 #[must_use]
1685 pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
1686 self.phrase = phrase.into();
1687 self
1688 }
1689
1690 fn get_phrase(&self) -> &str {
1691 &self.phrase
1692 }
1693
1694 #[must_use]
1695 pub fn chain_id(mut self, chain_id: impl Into<u64>) -> Self {
1696 self.chain_id = chain_id.into();
1697 self
1698 }
1699
1700 #[must_use]
1701 pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
1702 let mut derivation_path = derivation_path.into();
1703 if !derivation_path.ends_with('/') {
1704 derivation_path.push('/');
1705 }
1706 self.derivation_path = Some(derivation_path);
1707 self
1708 }
1709
1710 fn get_derivation_path(&self) -> &str {
1711 self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
1712 }
1713}
1714
1715impl AccountGenerator {
1716 pub fn generate(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
1717 let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
1718
1719 let derivation_path = self.get_derivation_path();
1721
1722 let mut wallets = Vec::with_capacity(self.amount);
1723 for idx in 0..self.amount {
1724 let builder =
1725 builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap();
1726 let wallet = builder.build()?.with_chain_id(Some(self.chain_id));
1727 wallets.push(wallet)
1728 }
1729 Ok(wallets)
1730 }
1731}
1732
1733pub fn anvil_dir() -> Option<PathBuf> {
1735 Config::foundry_dir().map(|p| p.join("anvil"))
1736}
1737
1738pub fn anvil_tmp_dir() -> Option<PathBuf> {
1740 anvil_dir().map(|p| p.join("tmp"))
1741}
1742
1743async fn find_latest_fork_block<P: Provider<AnyNetwork>>(
1748 provider: P,
1749) -> Result<u64, TransportError> {
1750 let mut num = provider.get_block_number().await?;
1751
1752 for _ in 0..2 {
1755 if let Some(block) = provider.get_block(num.into()).await?
1756 && !block.header.hash.is_zero()
1757 {
1758 break;
1759 }
1760 num = num.saturating_sub(1)
1762 }
1763
1764 Ok(num)
1765}
1766
1767#[cfg(test)]
1768mod tests {
1769 use super::*;
1770
1771 #[test]
1772 fn test_prune_history() {
1773 let config = PruneStateHistoryConfig::default();
1774 assert!(config.is_state_history_supported());
1775 let config = PruneStateHistoryConfig::from_args(Some(None));
1776 assert!(!config.is_state_history_supported());
1777 let config = PruneStateHistoryConfig::from_args(Some(Some(10)));
1778 assert!(config.is_state_history_supported());
1779 }
1780
1781 #[cfg(feature = "optimism")]
1782 #[test]
1783 fn set_chain_id_updates_network_config() {
1784 let mut config = NodeConfig::test();
1785 config.set_chain_id(Some(10u64));
1786
1787 assert!(config.networks.is_optimism());
1788 }
1789}