1use crate::{
2 EvmEnv, FoundryBlock, FoundryTransaction,
3 constants::DEFAULT_CREATE2_DEPLOYER,
4 fork::{CreateFork, ResolvedFork},
5 utils::{apply_chain_and_block_specific_env_changes_for_chain, block_env_from_header},
6};
7use alloy_consensus::BlockHeader;
8use alloy_eips::BlockNumHash;
9use alloy_network::{AnyNetwork, BlockResponse, Network, primitives::HeaderResponse};
10use alloy_primitives::{Address, B256, BlockNumber, ChainId, U256};
11use alloy_provider::{Provider, RootProvider};
12use alloy_rpc_types::{
13 BlockId, BlockNumberOrTag,
14 anvil::{Metadata, NodeInfo},
15};
16use eyre::{OptionExt, WrapErr};
17use foundry_common::{
18 ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING,
19 provider::{ProviderBuilder, is_rpc_method_not_found},
20};
21use foundry_compilers::artifacts::EvmVersion;
22use foundry_config::{Chain, Config, ExecutionSpec, FoundryHardfork, GasLimit};
23use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
24use revm::{context::CfgEnv, primitives::hardfork::SpecId};
25use serde::{Deserialize, Serialize};
26use std::fmt::Write;
27use url::Url;
28
29#[derive(Clone, Debug, Serialize, Deserialize)]
39pub struct EvmOpts {
40 #[serde(flatten)]
42 pub env: Env,
43
44 #[serde(rename = "eth_rpc_url")]
46 pub fork_url: Option<String>,
47
48 pub fork_block_number: Option<u64>,
50
51 pub fork_retries: Option<u32>,
53
54 pub fork_retry_backoff: Option<u64>,
56
57 pub fork_headers: Option<Vec<String>>,
59
60 #[serde(rename = "eth_rpc_jwt")]
62 pub rpc_jwt: Option<String>,
63
64 #[serde(rename = "eth_rpc_headers")]
66 pub rpc_headers: Option<Vec<String>>,
67
68 #[serde(rename = "eth_rpc_timeout")]
70 pub rpc_timeout: Option<u64>,
71
72 #[serde(default, rename = "eth_rpc_accept_invalid_certs")]
74 pub rpc_accept_invalid_certs: bool,
75
76 #[serde(default, rename = "eth_rpc_no_proxy")]
78 pub rpc_no_proxy: bool,
79
80 pub compute_units_per_second: Option<u64>,
84
85 pub no_rpc_rate_limit: bool,
87
88 pub no_storage_caching: bool,
90
91 pub initial_balance: U256,
93
94 pub sender: Address,
96
97 pub ffi: bool,
99
100 pub always_use_create_2_factory: bool,
102
103 pub verbosity: u8,
105
106 pub memory_limit: u64,
109
110 pub isolate: bool,
112
113 pub disable_block_gas_limit: bool,
115
116 pub enable_tx_gas_limit: bool,
118
119 #[serde(flatten)]
120 pub networks: NetworkConfigs,
122
123 pub create2_deployer: Address,
125
126 #[serde(skip)]
128 pub fork_endpoint: Option<ForkEndpointIdentity>,
129
130 #[serde(skip)]
132 pub expected_fork_endpoint: Option<ForkEndpointIdentity>,
133
134 #[serde(skip)]
136 pub fork_network_is_inferred: bool,
137
138 #[serde(skip)]
140 pub fork_chain_id_is_inferred: bool,
141
142 #[serde(skip)]
144 pub fork_block_number_is_inferred: bool,
145}
146
147#[derive(Clone, Debug, PartialEq, Eq)]
155pub struct ForkEndpointIdentity {
156 pub endpoint: String,
160 pub execution_chain_id: ChainId,
162 pub source_chain_id: ChainId,
164 pub network: NetworkVariant,
166 pub network_profile: NetworkConfigs,
168 pub reported_hardfork: Option<String>,
170 pub hardfork: Option<FoundryHardfork>,
172 pub instance_id: Option<B256>,
174 pub source_fork_block_number: Option<BlockNumber>,
176 pub source_fork_block_hash: Option<B256>,
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
181enum EndpointHardforkPolicy {
182 Optional,
184 Required,
186}
187
188#[derive(Clone, Copy, Debug, Default)]
195struct AnvilNodeInfoProbe {
196 identified: bool,
197}
198
199impl AnvilNodeInfoProbe {
200 const fn new(identified: bool) -> Self {
201 Self { identified }
202 }
203
204 async fn request<N: Network, P: Provider<N>>(
205 &mut self,
206 provider: &P,
207 ) -> eyre::Result<Option<NodeInfo>> {
208 match provider.raw_request::<_, NodeInfo>("anvil_nodeInfo".into(), ()).await {
209 Ok(node_info) => {
210 self.identified = true;
211 Ok(Some(node_info))
212 }
213 Err(_) if !self.identified => Ok(None),
214 Err(error) => Err(error).wrap_err("failed to determine network family from endpoint"),
215 }
216 }
217}
218
219#[derive(Clone, Copy)]
220enum ForkBlockTarget {
221 Configured(BlockNumberOrTag),
222 Resolved(BlockNumHash),
223}
224
225fn endpoint_hardfork(
226 network: NetworkVariant,
227 hardfork: &str,
228 policy: EndpointHardforkPolicy,
229) -> eyre::Result<Option<FoundryHardfork>> {
230 match network.parse_hardfork(hardfork) {
231 Ok(hardfork) => Ok(Some(hardfork)),
232 Err(_) if policy == EndpointHardforkPolicy::Optional => Ok(None),
233 Err(error) => Err(eyre::Report::msg(error)).wrap_err_with(|| {
234 format!("unsupported hardfork `{hardfork}` reported for `{network}`")
235 }),
236 }
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize)]
245pub struct ForkContext {
246 pub execution_chain_id: ChainId,
248 pub source_chain_id: ChainId,
250 pub network: NetworkVariant,
252 pub network_profile: NetworkConfigs,
254 pub block_number: BlockNumber,
256 pub hardfork: Option<FoundryHardfork>,
258 pub instance_id: Option<B256>,
260 pub source_fork_block_number: Option<BlockNumber>,
262 pub source_fork_block_hash: Option<B256>,
264}
265
266impl ForkContext {
267 pub fn matches_identity(self, identity: &ForkEndpointIdentity) -> bool {
269 self.execution_chain_id == identity.execution_chain_id
270 && self.source_chain_id == identity.source_chain_id
271 && self.network == identity.network
272 && self.network_profile == identity.network_profile
273 && self.hardfork == identity.hardfork
274 && self.instance_id == identity.instance_id
275 && self.source_fork_block_number == identity.source_fork_block_number
276 && self.source_fork_block_hash == identity.source_fork_block_hash
277 }
278
279 pub(crate) fn has_same_endpoint_identity(self, other: Self) -> bool {
281 self.execution_chain_id == other.execution_chain_id
282 && self.source_chain_id == other.source_chain_id
283 && self.network == other.network
284 && self.network_profile == other.network_profile
285 && self.hardfork == other.hardfork
286 && self.instance_id == other.instance_id
287 && self.source_fork_block_number == other.source_fork_block_number
288 && self.source_fork_block_hash == other.source_fork_block_hash
289 }
290
291 pub fn has_same_backend_target(self, other: Self) -> bool {
293 self.has_same_endpoint_identity(other) && self.block_number == other.block_number
294 }
295}
296
297impl Default for EvmOpts {
298 fn default() -> Self {
299 Self {
300 env: Env::default(),
301 fork_url: None,
302 fork_block_number: None,
303 fork_retries: None,
304 fork_retry_backoff: None,
305 fork_headers: None,
306 rpc_jwt: None,
307 rpc_headers: None,
308 rpc_timeout: None,
309 rpc_accept_invalid_certs: false,
310 rpc_no_proxy: false,
311 compute_units_per_second: None,
312 no_rpc_rate_limit: false,
313 no_storage_caching: false,
314 initial_balance: U256::default(),
315 sender: Address::default(),
316 ffi: false,
317 always_use_create_2_factory: false,
318 verbosity: 0,
319 memory_limit: 0,
320 isolate: false,
321 disable_block_gas_limit: false,
322 enable_tx_gas_limit: false,
323 networks: NetworkConfigs::default(),
324 create2_deployer: DEFAULT_CREATE2_DEPLOYER,
325 fork_endpoint: None,
326 expected_fork_endpoint: None,
327 fork_network_is_inferred: false,
328 fork_chain_id_is_inferred: false,
329 fork_block_number_is_inferred: false,
330 }
331 }
332}
333
334impl EvmOpts {
335 fn clear_endpoint_derived_fork_state(&mut self) {
337 if self.fork_block_number_is_inferred {
338 self.fork_block_number = None;
339 self.fork_block_number_is_inferred = false;
340 }
341 self.fork_endpoint = None;
342 self.expected_fork_endpoint = None;
343 if self.fork_network_is_inferred {
344 self.networks = self.networks.with_rpc_profile(NetworkConfigs::default());
345 self.fork_network_is_inferred = false;
346 }
347 if self.fork_chain_id_is_inferred {
348 self.env.chain_id = None;
349 self.fork_chain_id_is_inferred = false;
350 }
351 }
352
353 pub fn set_fork_url(&mut self, fork_url: String) {
355 if self.fork_url.as_ref() == Some(&fork_url) {
356 return;
357 }
358 self.fork_url = Some(fork_url);
359 self.clear_endpoint_derived_fork_state();
360 }
361
362 pub fn invalidate_fork_endpoint_if_source_changed(&mut self, fork: &ResolvedFork) -> bool {
369 let matches = self.fork_url.as_deref().is_some_and(|fork_url| {
370 fork.matches_source(fork_url, self.fork_source_headers(), self.rpc_jwt.as_deref())
371 });
372 if matches {
373 return false;
374 }
375 self.clear_endpoint_derived_fork_state();
376 true
377 }
378
379 pub fn set_explicit_network(&mut self, network: NetworkVariant) {
381 self.networks = network.into();
382 self.fork_network_is_inferred = false;
383 }
384
385 pub fn expect_fork_endpoint(
387 &mut self,
388 identity: ForkEndpointIdentity,
389 network_is_inferred: bool,
390 ) {
391 self.fork_endpoint = Some(identity.clone());
392 self.expected_fork_endpoint = Some(identity);
393 self.fork_network_is_inferred = network_is_inferred;
394 }
395
396 fn ensure_expected_fork_endpoint(&self, identity: &ForkEndpointIdentity) -> eyre::Result<()> {
397 if let Some(expected) = &self.expected_fork_endpoint
398 && expected != identity
399 {
400 eyre::bail!(
401 "fork endpoint {} changed after its execution context was selected",
402 fork_endpoint_description(&identity.endpoint)
403 );
404 }
405 Ok(())
406 }
407
408 fn chain_id_override(&self) -> Option<ChainId> {
409 (!self.fork_chain_id_is_inferred).then_some(self.env.chain_id).flatten()
410 }
411
412 fn endpoint_network_fallback(&self) -> NetworkConfigs {
413 if self.networks.has_network_selection() && !self.fork_network_is_inferred {
414 self.networks
415 } else {
416 NetworkConfigs::default()
417 }
418 }
419
420 fn anvil_node_info_probe(&self) -> AnvilNodeInfoProbe {
421 let endpoint = self.fork_url.as_deref();
422 let identified = [self.fork_endpoint.as_ref(), self.expected_fork_endpoint.as_ref()]
423 .into_iter()
424 .flatten()
425 .any(|identity| {
426 Some(identity.endpoint.as_str()) == endpoint && identity.reported_hardfork.is_some()
427 });
428 AnvilNodeInfoProbe::new(identified)
429 }
430
431 fn fork_source_headers(&self) -> Option<&[String]> {
432 self.fork_headers.as_deref().or(self.rpc_headers.as_deref())
433 }
434
435 fn resolved_fork(
436 &self,
437 fork_url: &str,
438 block: BlockNumHash,
439 context: ForkContext,
440 ) -> ResolvedFork {
441 ResolvedFork::new(
442 fork_url,
443 self.fork_source_headers(),
444 self.rpc_jwt.as_deref(),
445 self.fork_block_number,
446 block,
447 context,
448 )
449 }
450
451 pub async fn pin_fork_block(&mut self) -> eyre::Result<Option<BlockNumber>> {
458 if self.fork_block_number.is_none()
459 && let Some(fork_url) = &self.fork_url
460 {
461 self.fork_block_number = Some(
462 self.fork_provider_with_url::<AnyNetwork>(fork_url)?.get_block_number().await?,
463 );
464 self.fork_block_number_is_inferred = true;
465 }
466 Ok(self.fork_block_number)
467 }
468
469 pub async fn resolve_fork(&self) -> eyre::Result<Option<ResolvedFork>> {
477 let Some(fork_url) = &self.fork_url else { return Ok(None) };
478 let provider = self.fork_provider_with_url::<AnyNetwork>(fork_url)?;
479 let target = ForkBlockTarget::Configured(match self.fork_block_number {
480 Some(block_number) => BlockNumberOrTag::Number(block_number),
481 None => BlockNumberOrTag::Latest,
482 });
483 let mut node_info_probe = self.anvil_node_info_probe();
484 let (_, block, context) =
485 self.resolve_fork_block_with_context(&provider, target, &mut node_info_probe).await?;
486 Ok(Some(self.resolved_fork(fork_url, block, context)))
487 }
488
489 pub async fn can_use_create2_deployer(
494 &self,
495 fork_block: Option<BlockNumber>,
496 ) -> eyre::Result<bool> {
497 let Some(fork_url) = &self.fork_url else {
498 return Ok(self.create2_deployer == DEFAULT_CREATE2_DEPLOYER);
499 };
500 let block = fork_block.ok_or_else(|| eyre::eyre!("fork block must be resolved"))?;
501 let provider = self.fork_provider_with_url::<AnyNetwork>(fork_url)?;
502 Ok(!provider
503 .get_code_at(self.create2_deployer)
504 .block_id(BlockId::number(block))
505 .await?
506 .is_empty())
507 }
508
509 pub async fn can_use_create2_deployer_resolved(
511 &self,
512 fork: Option<&ResolvedFork>,
513 ) -> eyre::Result<bool> {
514 let Some(_) = &self.fork_url else {
515 eyre::ensure!(fork.is_none(), "resolved fork provided without a configured fork");
516 return Ok(self.create2_deployer == DEFAULT_CREATE2_DEPLOYER);
517 };
518 let fork = fork.ok_or_else(|| eyre::eyre!("fork must be resolved"))?;
519 let provider = self.provider_for_resolved_fork::<AnyNetwork>(fork)?;
520 self.ensure_resolved_fork_endpoint(&provider, fork).await?;
521 let available = !provider
522 .get_code_at(self.create2_deployer)
523 .block_id(fork.exact_block_id())
524 .await?
525 .is_empty();
526 self.ensure_resolved_fork_endpoint(&provider, fork).await?;
527 Ok(available)
528 }
529
530 pub fn resolved_fork_matches(&self, fork: &ResolvedFork) -> bool {
532 self.fork_url.as_deref().is_some_and(|fork_url| {
533 fork.matches(
534 fork_url,
535 self.fork_source_headers(),
536 self.rpc_jwt.as_deref(),
537 self.fork_block_number,
538 )
539 })
540 }
541
542 pub fn provider_for_resolved_fork<N: Network>(
544 &self,
545 fork: &ResolvedFork,
546 ) -> eyre::Result<RootProvider<N>> {
547 eyre::ensure!(
548 self.resolved_fork_matches(fork),
549 "resolved fork does not match the configured source and selector"
550 );
551 self.fork_provider_with_url(
552 self.fork_url.as_deref().expect("a matching resolved fork requires a fork URL"),
553 )
554 }
555
556 async fn ensure_resolved_fork_endpoint<N: Network, P: Provider<N>>(
557 &self,
558 provider: &P,
559 fork: &ResolvedFork,
560 ) -> eyre::Result<()> {
561 let mut node_info_probe = self.anvil_node_info_probe();
562 node_info_probe.identified |= fork.context().hardfork.is_some();
563 let identity = self
564 .resolve_fork_endpoint_once(
565 provider,
566 &mut node_info_probe,
567 EndpointHardforkPolicy::Required,
568 )
569 .await?;
570 self.ensure_expected_fork_endpoint(&identity)?;
571 eyre::ensure!(
572 fork.context().matches_identity(&identity),
573 "fork endpoint {} changed after its block and execution context were resolved",
574 fork_endpoint_description(self.fork_url.as_deref().unwrap_or_default())
575 );
576 Ok(())
577 }
578
579 pub async fn transaction_count_at_resolved_fork(
581 &self,
582 account: Address,
583 fork: &ResolvedFork,
584 ) -> eyre::Result<u64> {
585 let provider = self.provider_for_resolved_fork::<AnyNetwork>(fork)?;
586 self.ensure_resolved_fork_endpoint(&provider, fork).await?;
587 let nonce = provider.get_transaction_count(account).block_id(fork.exact_block_id()).await?;
588 self.ensure_resolved_fork_endpoint(&provider, fork).await?;
589 Ok(nonce)
590 }
591
592 pub fn fork_provider_with_url<N: Network>(
595 &self,
596 fork_url: &str,
597 ) -> eyre::Result<RootProvider<N>> {
598 let mut builder = ProviderBuilder::new(fork_url)
599 .maybe_max_retry(self.fork_retries)
600 .maybe_initial_backoff(self.fork_retry_backoff)
601 .maybe_headers(self.fork_headers.clone().or_else(|| self.rpc_headers.clone()))
602 .compute_units_per_second(self.get_compute_units_per_second())
603 .accept_invalid_certs(self.rpc_accept_invalid_certs)
604 .no_proxy(self.rpc_no_proxy);
605 if let Some(jwt) = &self.rpc_jwt {
606 builder = builder.jwt(jwt);
607 }
608 if let Some(timeout) = self.rpc_timeout {
609 builder = builder.timeout(std::time::Duration::from_secs(timeout));
610 }
611 builder.build()
612 }
613
614 pub async fn infer_network_from_fork(&mut self) -> eyre::Result<()> {
621 let previous_identity = self.fork_endpoint.clone();
622 let Some(fork_url) = self.fork_url.clone() else {
623 self.fork_endpoint = None;
624 self.expected_fork_endpoint = None;
625 if self.fork_network_is_inferred {
626 self.networks = self.networks.with_rpc_profile(NetworkConfigs::default());
627 self.fork_network_is_inferred = false;
628 }
629 if self.fork_chain_id_is_inferred {
630 self.env.chain_id = None;
631 self.fork_chain_id_is_inferred = false;
632 }
633 if self.fork_block_number_is_inferred {
634 self.fork_block_number = None;
635 self.fork_block_number_is_inferred = false;
636 }
637 return Ok(());
638 };
639 let explicit_network =
640 self.networks.has_network_selection() && !self.fork_network_is_inferred;
641 let identity = self.discover_fork_endpoint().await?;
642 self.ensure_expected_fork_endpoint(&identity)?;
643 if self.fork_network_is_inferred
644 && previous_identity.as_ref().is_some_and(|previous| {
645 previous.endpoint == fork_url
646 && previous.network_profile != identity.network_profile
647 })
648 {
649 eyre::bail!(
650 "fork endpoint {} changed execution profile from `{}` to `{}`; rebuild \
651 the EVM for the new network",
652 fork_endpoint_description(&fork_url),
653 self.networks.execution_profile_name(),
654 identity.network_profile.execution_profile_name()
655 );
656 }
657
658 if self.env.chain_id.is_none() || self.fork_chain_id_is_inferred {
659 self.env.chain_id = Some(identity.execution_chain_id);
660 self.fork_chain_id_is_inferred = true;
661 }
662 if !explicit_network {
663 self.networks = self.networks.with_rpc_profile(identity.network_profile);
664 self.fork_network_is_inferred = true;
665 }
666 debug_assert_eq!(identity.endpoint, fork_url);
667 self.fork_endpoint = Some(identity);
668 Ok(())
669 }
670
671 pub async fn discover_fork_endpoint(&self) -> eyre::Result<ForkEndpointIdentity> {
679 let fork_url = self.fork_url.as_deref().ok_or_eyre("fork URL is not configured")?;
680 let provider = self.fork_provider_with_url::<AnyNetwork>(fork_url)?;
681 let unknown_fallback = self.endpoint_network_fallback();
682 let mut node_info_probe = self.anvil_node_info_probe();
683 for _ in 0..3 {
684 let before_chain_id = provider
685 .get_chain_id()
686 .await
687 .wrap_err("failed to retrieve chain ID from fork endpoint")?;
688 let before_node_info = node_info_probe.request(&provider).await?;
689 let before = Self::resolve_fork_endpoint_identity(
690 &provider,
691 fork_url,
692 before_chain_id,
693 before_node_info,
694 Some(unknown_fallback),
695 EndpointHardforkPolicy::Optional,
696 )
697 .await?;
698 let after_chain_id = provider
699 .get_chain_id()
700 .await
701 .wrap_err("failed to confirm chain ID from fork endpoint")?;
702 let after_node_info = node_info_probe.request(&provider).await?;
703 let after = Self::resolve_fork_endpoint_identity(
704 &provider,
705 fork_url,
706 after_chain_id,
707 after_node_info,
708 Some(unknown_fallback),
709 EndpointHardforkPolicy::Optional,
710 )
711 .await?;
712 if before_chain_id == after_chain_id && before == after {
713 return Ok(before);
714 }
715 }
716 eyre::bail!(
717 "fork endpoint {} changed while its identity was being resolved",
718 fork_endpoint_description(fork_url)
719 );
720 }
721
722 async fn resolve_fork_endpoint_once<N: Network, P: Provider<N>>(
728 &self,
729 provider: &P,
730 node_info_probe: &mut AnvilNodeInfoProbe,
731 hardfork_policy: EndpointHardforkPolicy,
732 ) -> eyre::Result<ForkEndpointIdentity> {
733 let fork_url = self.fork_url.as_deref().ok_or_eyre("fork URL is not configured")?;
734 let cached_anvil = self.fork_endpoint.as_ref().is_some_and(|identity| {
735 identity.endpoint == fork_url && identity.reported_hardfork.is_some()
736 });
737 let node_info = node_info_probe.request(provider).await?;
738 let execution_chain_id = if cached_anvil && let Some(node_info) = &node_info {
739 node_info.environment.chain_id
740 } else {
741 provider
742 .get_chain_id()
743 .await
744 .wrap_err("failed to retrieve chain ID from fork endpoint")?
745 };
746 Self::resolve_fork_endpoint_identity(
747 provider,
748 fork_url,
749 execution_chain_id,
750 node_info,
751 Some(self.endpoint_network_fallback()),
752 hardfork_policy,
753 )
754 .await
755 }
756
757 pub async fn fork_network(&self) -> eyre::Result<(ChainId, NetworkVariant)> {
763 let identity = self.discover_fork_endpoint().await?;
764
765 Ok((identity.execution_chain_id, identity.network))
766 }
767
768 async fn resolve_fork_endpoint_identity<N: Network, P: Provider<N>>(
772 provider: &P,
773 endpoint: &str,
774 execution_chain_id: ChainId,
775 node_info: Option<NodeInfo>,
776 unknown_fallback: Option<NetworkConfigs>,
777 hardfork_policy: EndpointHardforkPolicy,
778 ) -> eyre::Result<ForkEndpointIdentity> {
779 match node_info {
780 Some(node_info) => {
781 let (
782 source_chain_id,
783 instance_id,
784 source_fork_block_number,
785 source_fork_block_hash,
786 ) = match provider.raw_request::<_, Metadata>("anvil_metadata".into(), ()).await {
787 Ok(metadata) => {
788 let forked_network = metadata.forked_network;
789 (
790 forked_network.map(|fork| fork.chain_id).unwrap_or(execution_chain_id),
791 Some(metadata.instance_id),
792 forked_network.map(|fork| fork.fork_block_number),
793 forked_network.map(|fork| fork.fork_block_hash),
794 )
795 }
796 Err(error) if is_rpc_method_not_found(&error) => {
797 (execution_chain_id, None, None, None)
798 }
799 Err(error) => {
800 return Err(error)
801 .wrap_err("failed to retrieve Anvil fork source identity");
802 }
803 };
804 let identity_chain_id =
805 if node_info.network.is_some() { execution_chain_id } else { source_chain_id };
806 let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
807 identity_chain_id,
808 Some(node_info.network.as_deref()),
809 unknown_fallback,
810 )
811 .map_err(eyre::Report::msg)?
812 .ok_or_else(|| {
813 eyre::eyre!("Anvil metadata did not identify an execution profile")
814 })?;
815 let network = network_profile.execution_network();
816 let hardfork = endpoint_hardfork(network, &node_info.hard_fork, hardfork_policy)?;
817
818 Ok(ForkEndpointIdentity {
819 endpoint: endpoint.to_string(),
820 execution_chain_id,
821 source_chain_id,
822 network,
823 network_profile,
824 reported_hardfork: Some(node_info.hard_fork),
825 hardfork,
826 instance_id,
827 source_fork_block_number,
828 source_fork_block_hash,
829 })
830 }
831 None => {
832 let network_profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
833 execution_chain_id,
834 None,
835 unknown_fallback,
836 )
837 .map_err(eyre::Report::msg)?;
838 if let Some(network_profile) = network_profile {
839 let network = network_profile.execution_network();
840 return Ok(ForkEndpointIdentity {
841 endpoint: endpoint.to_string(),
842 execution_chain_id,
843 source_chain_id: execution_chain_id,
844 network,
845 network_profile,
846 reported_hardfork: None,
847 hardfork: None,
848 instance_id: None,
849 source_fork_block_number: None,
850 source_fork_block_hash: None,
851 });
852 }
853 Err(eyre::eyre!(
854 "cannot determine network family for unknown chain ID \
855 {execution_chain_id}: the fork endpoint does not expose `anvil_nodeInfo`"
856 ))
857 }
858 }
859 }
860
861 pub async fn env<
870 SPEC: Into<SpecId> + Default + Copy,
871 BLOCK: FoundryBlock + Default,
872 TX: FoundryTransaction + Default,
873 >(
874 &self,
875 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, TX, Option<BlockNumber>)> {
876 let (evm_env, tx, fork) = self.env_resolved().await?;
877 Ok((evm_env, tx, fork.as_ref().map(ResolvedFork::number)))
878 }
879
880 pub async fn env_resolved<
886 SPEC: Into<SpecId> + Default + Copy,
887 BLOCK: FoundryBlock + Default,
888 TX: FoundryTransaction + Default,
889 >(
890 &self,
891 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, TX, Option<ResolvedFork>)> {
892 let Some(fork_url) = &self.fork_url else {
893 return Ok((self.local_evm_env(), self.local_tx_env(), None));
894 };
895 let provider = self.fork_provider_with_url::<AnyNetwork>(fork_url)?;
896 let mut node_info_probe = self.anvil_node_info_probe();
897 for _ in 0..3 {
898 let (evm_env, block, context) =
899 self.fork_evm_env_resolved_with_context(&provider, &mut node_info_probe).await?;
900 let gas_price =
901 option_try_or_else(self.env.gas_price.map(|value| value as u128), async || {
902 provider.get_gas_price().await
903 })
904 .await?;
905 let identity = self
906 .resolve_fork_endpoint_once(
907 &provider,
908 &mut node_info_probe,
909 EndpointHardforkPolicy::Required,
910 )
911 .await?;
912 self.ensure_expected_fork_endpoint(&identity)?;
913 if context.matches_identity(&identity) {
914 let chain_id = self.chain_id_override().unwrap_or(context.execution_chain_id);
915 let tx = self.fork_tx_env(gas_price, chain_id);
916 return Ok((evm_env, tx, Some(self.resolved_fork(fork_url, block, context))));
917 }
918 }
919 eyre::bail!(
920 "fork endpoint {} changed while its EVM and transaction environments were being \
921 resolved",
922 fork_endpoint_description(fork_url)
923 );
924 }
925
926 pub async fn env_with_fork_context<
931 SPEC: Into<SpecId> + Default + Copy,
932 BLOCK: FoundryBlock + Default,
933 TX: FoundryTransaction + Default,
934 >(
935 &self,
936 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, TX, Option<ForkContext>)> {
937 let (evm_env, tx, fork) = self.env_resolved().await?;
938 Ok((evm_env, tx, fork.as_ref().map(ResolvedFork::context)))
939 }
940
941 pub async fn env_with_resolved_fork<
943 SPEC: Into<SpecId> + Default + Copy,
944 BLOCK: FoundryBlock + Default,
945 TX: FoundryTransaction + Default,
946 >(
947 &self,
948 fork: Option<&ResolvedFork>,
949 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, TX)> {
950 let Some(_) = &self.fork_url else {
951 eyre::ensure!(fork.is_none(), "resolved fork provided without a configured fork");
952 return Ok((self.local_evm_env(), self.local_tx_env()));
953 };
954 let fork = fork.ok_or_else(|| eyre::eyre!("fork must be resolved"))?;
955 let provider = self.provider_for_resolved_fork::<AnyNetwork>(fork)?;
956 let mut node_info_probe = self.anvil_node_info_probe();
957 node_info_probe.identified |= fork.context().hardfork.is_some();
958 for _ in 0..3 {
959 let (evm_env, endpoint) = self
960 .fork_evm_env_at_resolved_with_context(&provider, fork, &mut node_info_probe)
961 .await?;
962 let gas_price =
963 option_try_or_else(self.env.gas_price.map(|value| value as u128), async || {
964 provider.get_gas_price().await
965 })
966 .await?;
967 let identity = self
968 .resolve_fork_endpoint_once(
969 &provider,
970 &mut node_info_probe,
971 EndpointHardforkPolicy::Required,
972 )
973 .await?;
974 self.ensure_expected_fork_endpoint(&identity)?;
975 if endpoint == fork.context() && endpoint.matches_identity(&identity) {
976 let chain_id = self.chain_id_override().unwrap_or(endpoint.execution_chain_id);
977 return Ok((evm_env, self.fork_tx_env(gas_price, chain_id)));
978 }
979 }
980 eyre::bail!(
981 "fork endpoint {} changed while its resolved environment was being reconstructed",
982 fork_endpoint_description(self.fork_url.as_deref().unwrap_or_default())
983 );
984 }
985
986 pub async fn fork_evm_env<
989 SPEC: Into<SpecId> + Default + Copy,
990 BLOCK: FoundryBlock + Default,
991 N: Network,
992 P: Provider<N>,
993 >(
994 &self,
995 provider: &P,
996 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, BlockNumber)> {
997 let mut node_info_probe = self.anvil_node_info_probe();
998 let (evm_env, block, _) =
999 self.fork_evm_env_resolved_with_context(provider, &mut node_info_probe).await?;
1000 Ok((evm_env, block.number))
1001 }
1002
1003 pub(crate) async fn fork_evm_env_resolved<
1005 SPEC: Into<SpecId> + Default + Copy,
1006 BLOCK: FoundryBlock + Default,
1007 N: Network,
1008 P: Provider<N>,
1009 >(
1010 &self,
1011 provider: &P,
1012 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, ResolvedFork)> {
1013 let mut node_info_probe = self.anvil_node_info_probe();
1014 let (evm_env, block, context) =
1015 self.fork_evm_env_resolved_with_context(provider, &mut node_info_probe).await?;
1016 let fork_url = self.fork_url.as_deref().unwrap_or_default();
1017 Ok((evm_env, self.resolved_fork(fork_url, block, context)))
1018 }
1019
1020 async fn fork_evm_env_resolved_with_context<
1022 SPEC: Into<SpecId> + Default + Copy,
1023 BLOCK: FoundryBlock + Default,
1024 N: Network,
1025 P: Provider<N>,
1026 >(
1027 &self,
1028 provider: &P,
1029 node_info_probe: &mut AnvilNodeInfoProbe,
1030 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, BlockNumHash, ForkContext)> {
1031 trace!(
1032 memory_limit = %self.memory_limit,
1033 override_chain_id = ?self.env.chain_id,
1034 pin_block = ?self.fork_block_number,
1035 origin = %self.sender,
1036 disable_block_gas_limit = %self.disable_block_gas_limit,
1037 enable_tx_gas_limit = %self.enable_tx_gas_limit,
1038 configs = ?self.networks,
1039 "creating fork environment"
1040 );
1041
1042 let target = ForkBlockTarget::Configured(match self.fork_block_number {
1043 Some(block_number) => BlockNumberOrTag::Number(block_number),
1044 None => BlockNumberOrTag::Latest,
1045 });
1046 let (block, fork_block, context) =
1047 self.resolve_fork_block_with_context(provider, target, node_info_probe).await?;
1048 let chain_id = self.chain_id_override().unwrap_or(context.execution_chain_id);
1049 let evm_env =
1050 self.fork_env_from_block::<SPEC, BLOCK, N>(chain_id, context.source_chain_id, &block);
1051 Ok((evm_env, fork_block, context))
1052 }
1053
1054 fn fork_env_error_context(&self) -> String {
1055 let mut message = "could not instantiate forked environment".to_string();
1056 if let Some(fork_url) = self.fork_url.as_deref()
1057 && let Ok(url) = Url::parse(fork_url)
1058 && let Some(host) = url.host()
1059 {
1060 write!(message, " with provider {host}").unwrap();
1061 }
1062 message
1063 }
1064
1065 async fn resolve_fork_block_with_context<N: Network, P: Provider<N>>(
1066 &self,
1067 provider: &P,
1068 target: ForkBlockTarget,
1069 node_info_probe: &mut AnvilNodeInfoProbe,
1070 ) -> eyre::Result<(N::BlockResponse, BlockNumHash, ForkContext)> {
1071 let endpoint = self.fork_url.as_deref().unwrap_or_default();
1072 let mut stable = None;
1073 for _ in 0..3 {
1074 let before = self
1075 .resolve_fork_endpoint_once(
1076 provider,
1077 node_info_probe,
1078 EndpointHardforkPolicy::Required,
1079 )
1080 .await
1081 .wrap_err_with(|| self.fork_env_error_context())?;
1082 let block = match target {
1083 ForkBlockTarget::Configured(block_number) => provider
1084 .get_block_by_number(block_number)
1085 .await
1086 .wrap_err_with(|| self.fork_env_error_context())?,
1087 ForkBlockTarget::Resolved(block) => provider
1088 .get_block_by_hash(block.hash)
1089 .await
1090 .wrap_err("could not instantiate resolved fork environment")?,
1091 };
1092 let after = self
1093 .resolve_fork_endpoint_once(
1094 provider,
1095 node_info_probe,
1096 EndpointHardforkPolicy::Required,
1097 )
1098 .await
1099 .wrap_err_with(|| self.fork_env_error_context())?;
1100 if before == after {
1101 stable = Some((before, block));
1102 break;
1103 }
1104 }
1105 let (identity, block) = stable.ok_or_else(|| {
1106 eyre::eyre!(
1107 "fork endpoint {} changed while its block and execution context were being \
1108 resolved",
1109 fork_endpoint_description(endpoint)
1110 )
1111 })?;
1112 self.ensure_expected_fork_endpoint(&identity)?;
1113 if self.fork_network_is_inferred
1114 && !self.networks.has_same_execution_profile(&identity.network_profile)
1115 {
1116 eyre::bail!(
1117 "fork endpoint {} changed execution profile from `{}` to `{}`; rebuild the EVM \
1118 for the new network",
1119 fork_endpoint_description(endpoint),
1120 self.networks.execution_profile_name(),
1121 identity.network_profile.execution_profile_name()
1122 );
1123 }
1124
1125 let block = match block {
1126 Some(block) => block,
1127 None => match target {
1128 ForkBlockTarget::Configured(block_number) => {
1129 let block_number_message = match block_number {
1130 BlockNumberOrTag::Number(block_number) => {
1131 format!("block number: {block_number}")
1132 }
1133 block_number => format!("{block_number} block"),
1134 };
1135 let latest_message = if let Ok(latest_block) = provider.get_block_number().await
1136 {
1137 if let Some(block_number) = self.fork_block_number
1138 && block_number <= latest_block
1139 {
1140 error!("{NON_ARCHIVE_NODE_WARNING}");
1141 }
1142 format!("; latest block number: {latest_block}")
1143 } else {
1144 Default::default()
1145 };
1146 eyre::bail!("failed to get {block_number_message}{latest_message}");
1147 }
1148 ForkBlockTarget::Resolved(block) => {
1149 eyre::bail!("failed to get block hash: {}", block.hash);
1150 }
1151 },
1152 };
1153 let actual = BlockNumHash::new(block.header().number(), block.header().hash());
1154 if let ForkBlockTarget::Resolved(expected) = target {
1155 eyre::ensure!(
1156 actual == expected,
1157 "resolved fork block changed: expected {expected:?}, got {actual:?}"
1158 );
1159 }
1160
1161 let context = ForkContext {
1162 execution_chain_id: identity.execution_chain_id,
1163 source_chain_id: identity.source_chain_id,
1164 network: identity.network,
1165 network_profile: identity.network_profile,
1166 block_number: actual.number,
1167 hardfork: identity.hardfork,
1168 instance_id: identity.instance_id,
1169 source_fork_block_number: identity.source_fork_block_number,
1170 source_fork_block_hash: identity.source_fork_block_hash,
1171 };
1172 Ok((block, actual, context))
1173 }
1174
1175 async fn fork_evm_env_at_resolved_with_context<
1176 SPEC: Into<SpecId> + Default + Copy,
1177 BLOCK: FoundryBlock + Default,
1178 N: Network,
1179 P: Provider<N>,
1180 >(
1181 &self,
1182 provider: &P,
1183 expected: &ResolvedFork,
1184 node_info_probe: &mut AnvilNodeInfoProbe,
1185 ) -> eyre::Result<(EvmEnv<SPEC, BLOCK>, ForkContext)> {
1186 let (block, _, context) = self
1187 .resolve_fork_block_with_context(
1188 provider,
1189 ForkBlockTarget::Resolved(expected.block()),
1190 node_info_probe,
1191 )
1192 .await?;
1193 eyre::ensure!(
1194 context == expected.context(),
1195 "fork endpoint {} changed after its block and execution context were resolved",
1196 fork_endpoint_description(self.fork_url.as_deref().unwrap_or_default())
1197 );
1198 let chain_id = self.chain_id_override().unwrap_or(context.execution_chain_id);
1199 let evm_env =
1200 self.fork_env_from_block::<SPEC, BLOCK, N>(chain_id, context.source_chain_id, &block);
1201 Ok((evm_env, context))
1202 }
1203
1204 pub(crate) async fn fork_evm_env_at_resolved<
1206 SPEC: Into<SpecId> + Default + Copy,
1207 BLOCK: FoundryBlock + Default,
1208 N: Network,
1209 P: Provider<N>,
1210 >(
1211 &self,
1212 provider: &P,
1213 expected: &ResolvedFork,
1214 ) -> eyre::Result<EvmEnv<SPEC, BLOCK>> {
1215 let mut node_info_probe = self.anvil_node_info_probe();
1216 node_info_probe.identified |= expected.context().hardfork.is_some();
1217 let (evm_env, _) = self
1218 .fork_evm_env_at_resolved_with_context(provider, expected, &mut node_info_probe)
1219 .await?;
1220 Ok(evm_env)
1221 }
1222
1223 fn fork_env_from_block<
1224 SPEC: Into<SpecId> + Default + Copy,
1225 BLOCK: FoundryBlock + Default,
1226 N: Network,
1227 >(
1228 &self,
1229 chain_id: ChainId,
1230 source_chain_id: ChainId,
1231 block: &N::BlockResponse,
1232 ) -> EvmEnv<SPEC, BLOCK> {
1233 let mut evm_env = EvmEnv {
1234 cfg_env: self.cfg_env(chain_id),
1235 block_env: block_env_from_header(block.header()),
1236 };
1237
1238 apply_chain_and_block_specific_env_changes_for_chain::<N, _, _>(
1239 &mut evm_env,
1240 block,
1241 source_chain_id,
1242 self.networks,
1243 );
1244
1245 evm_env
1246 }
1247
1248 fn local_evm_env<SPEC: Into<SpecId> + Default + Clone, BLOCK: FoundryBlock + Default>(
1250 &self,
1251 ) -> EvmEnv<SPEC, BLOCK> {
1252 let cfg_env = self.cfg_env(self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID));
1253 let mut block_env = BLOCK::default();
1254 block_env.set_number(self.env.block_number);
1255 block_env.set_beneficiary(self.env.block_coinbase);
1256 block_env.set_timestamp(self.env.block_timestamp);
1257 block_env.set_difficulty(U256::from(self.env.block_difficulty));
1258 block_env.set_prevrandao(Some(self.env.block_prevrandao));
1259 block_env.set_basefee(self.env.block_base_fee_per_gas);
1260 block_env.set_gas_limit(self.gas_limit());
1261 EvmEnv::new(cfg_env, block_env)
1262 }
1263
1264 fn fork_tx_env<TX: FoundryTransaction + Default>(
1266 &self,
1267 gas_price: u128,
1268 chain_id: ChainId,
1269 ) -> TX {
1270 let mut tx_env = TX::default();
1271 tx_env.set_caller(self.sender);
1272 tx_env.set_chain_id(Some(chain_id));
1273 tx_env.set_gas_price(gas_price);
1274 tx_env.set_gas_limit(self.gas_limit());
1275 tx_env
1276 }
1277
1278 fn local_tx_env<TX: FoundryTransaction + Default>(&self) -> TX {
1280 let mut tx_env = TX::default();
1281 tx_env.set_caller(self.sender);
1282 tx_env.set_gas_price(self.env.gas_price.unwrap_or_default().into());
1283 tx_env.set_gas_limit(self.gas_limit());
1284 tx_env
1285 }
1286
1287 fn cfg_env<SPEC: Into<SpecId> + Default + Clone>(&self, chain_id: ChainId) -> CfgEnv<SPEC> {
1289 let mut cfg = CfgEnv::default();
1290 cfg.chain_id = chain_id;
1291 cfg.memory_limit = self.memory_limit;
1292 cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX));
1293 cfg.disable_eip3607 = true;
1297 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
1298 cfg.disable_nonce_check = true;
1299 if !self.enable_tx_gas_limit {
1302 cfg.tx_gas_limit_cap = Some(u64::MAX);
1303 }
1304 cfg
1305 }
1306
1307 pub fn get_fork(
1325 &self,
1326 config: &Config,
1327 chain_id: u64,
1328 fork_block_number: Option<BlockNumber>,
1329 ) -> Option<CreateFork> {
1330 self.get_fork_with_identity(config, chain_id, fork_block_number, None)
1331 }
1332
1333 pub fn get_fork_resolved(
1335 &self,
1336 config: &Config,
1337 chain_id: u64,
1338 fork: Option<&ResolvedFork>,
1339 ) -> Option<CreateFork> {
1340 let fork_block_number = fork.map(ResolvedFork::number);
1341 let source_chain_id = fork.map(|fork| fork.context().source_chain_id).unwrap_or(chain_id);
1342 self.get_fork_with_identity(config, source_chain_id, fork_block_number, fork.cloned())
1343 }
1344
1345 fn get_fork_with_identity(
1346 &self,
1347 config: &Config,
1348 chain_id: u64,
1349 fork_block_number: Option<BlockNumber>,
1350 resolved: Option<ResolvedFork>,
1351 ) -> Option<CreateFork> {
1352 let url = self.fork_url.clone()?;
1353 let enable_caching = config.enable_caching(&url, chain_id);
1354
1355 let mut evm_opts = self.clone();
1359 if evm_opts.fork_block_number.is_none() {
1360 evm_opts.fork_block_number = fork_block_number;
1361 evm_opts.fork_block_number_is_inferred = fork_block_number.is_some();
1362 }
1363
1364 Some(CreateFork { url, enable_caching, evm_opts, resolved })
1365 }
1366
1367 pub fn gas_limit(&self) -> u64 {
1369 self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
1370 }
1371
1372 const fn get_compute_units_per_second(&self) -> u64 {
1377 if self.no_rpc_rate_limit {
1378 u64::MAX
1379 } else if let Some(cups) = self.compute_units_per_second {
1380 cups
1381 } else {
1382 ALCHEMY_FREE_TIER_CUPS
1383 }
1384 }
1385
1386 pub async fn get_remote_chain_id(&self) -> Option<Chain> {
1388 if let Some(url) = &self.fork_url
1389 && let Ok(provider) = self.fork_provider_with_url::<AnyNetwork>(url)
1390 {
1391 trace!(?url, "retrieving chain via eth_chainId");
1392
1393 if let Ok(id) = provider.get_chain_id().await {
1394 return Some(Chain::from(id));
1395 }
1396
1397 if url.contains("mainnet") {
1401 trace!(?url, "auto detected mainnet chain");
1402 return Some(Chain::mainnet());
1403 }
1404 }
1405
1406 None
1407 }
1408}
1409
1410#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1412pub enum ExecutionSpecContext {
1413 Local,
1415 Fork {
1417 source_chain_id: ChainId,
1419 endpoint_hardfork: Option<FoundryHardfork>,
1421 },
1422 Historical {
1424 source_chain_id: ChainId,
1426 endpoint_hardfork: Option<FoundryHardfork>,
1428 },
1429}
1430
1431impl ExecutionSpecContext {
1432 pub const fn fork(
1434 source_chain_id: ChainId,
1435 endpoint_hardfork: Option<FoundryHardfork>,
1436 ) -> Self {
1437 Self::Fork { source_chain_id, endpoint_hardfork }
1438 }
1439
1440 pub const fn local_or_fork(
1442 source_chain_id: Option<ChainId>,
1443 endpoint_hardfork: Option<FoundryHardfork>,
1444 ) -> Self {
1445 if let Some(source_chain_id) = source_chain_id {
1446 Self::Fork { source_chain_id, endpoint_hardfork }
1447 } else {
1448 Self::Local
1449 }
1450 }
1451
1452 pub const fn historical(
1454 source_chain_id: ChainId,
1455 endpoint_hardfork: Option<FoundryHardfork>,
1456 ) -> Self {
1457 Self::Historical { source_chain_id, endpoint_hardfork }
1458 }
1459
1460 fn hardfork<SPEC: ExecutionSpec>(self, timestamp: u64) -> Option<FoundryHardfork> {
1461 match self {
1462 Self::Local => None,
1463 Self::Fork { source_chain_id, endpoint_hardfork } => {
1464 SPEC::fork_hardfork(source_chain_id, timestamp, endpoint_hardfork)
1465 }
1466 Self::Historical { source_chain_id, endpoint_hardfork } => endpoint_hardfork
1467 .filter(|&hardfork| SPEC::from_foundry_hardfork(hardfork).is_some())
1468 .or_else(|| SPEC::historical_hardfork(source_chain_id, timestamp)),
1469 }
1470 }
1471}
1472
1473pub fn resolve_execution_spec<SPEC, BLOCK>(
1483 evm_version: EvmVersion,
1484 configured_hardfork: Option<FoundryHardfork>,
1485 evm_env: &mut EvmEnv<SPEC, BLOCK>,
1486 context: ExecutionSpecContext,
1487 explicit_spec: Option<SPEC>,
1488) -> Option<FoundryHardfork>
1489where
1490 SPEC: ExecutionSpec + Into<SpecId> + Copy,
1491 BLOCK: FoundryBlock,
1492{
1493 let (spec, hardfork) = if let Some(spec) = explicit_spec {
1494 (spec, spec.reported_hardfork())
1495 } else if let Some((hardfork, spec)) = configured_hardfork
1496 .filter(|&hardfork| SPEC::from_foundry_hardfork(hardfork).is_some())
1497 .or_else(|| context.hardfork::<SPEC>(evm_env.block_env.timestamp().saturating_to()))
1498 .and_then(|hardfork| SPEC::from_foundry_hardfork(hardfork).map(|spec| (hardfork, spec)))
1499 {
1500 (spec, Some(hardfork))
1501 } else {
1502 let spec = SPEC::from_evm_version(evm_version);
1503 (spec, spec.reported_hardfork())
1504 };
1505 evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec);
1506 hardfork
1507}
1508
1509#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1510pub struct Env {
1511 pub gas_limit: GasLimit,
1513
1514 pub chain_id: Option<u64>,
1516
1517 #[serde(default, skip_serializing_if = "Option::is_none")]
1522 pub gas_price: Option<u64>,
1523
1524 pub block_base_fee_per_gas: u64,
1526
1527 pub tx_origin: Address,
1529
1530 pub block_coinbase: Address,
1532
1533 #[serde(
1535 deserialize_with = "foundry_config::deserialize_u64_to_u256",
1536 serialize_with = "foundry_config::serialize_u64_or_u256"
1537 )]
1538 pub block_timestamp: U256,
1539
1540 #[serde(
1542 deserialize_with = "foundry_config::deserialize_u64_to_u256",
1543 serialize_with = "foundry_config::serialize_u64_or_u256"
1544 )]
1545 pub block_number: U256,
1546
1547 pub block_difficulty: u64,
1549
1550 pub block_prevrandao: B256,
1552
1553 #[serde(default, skip_serializing_if = "Option::is_none")]
1555 pub block_gas_limit: Option<GasLimit>,
1556
1557 #[serde(default, skip_serializing_if = "Option::is_none")]
1559 pub code_size_limit: Option<usize>,
1560}
1561
1562fn fork_endpoint_description(endpoint: &str) -> String {
1563 Url::parse(endpoint)
1564 .ok()
1565 .and_then(|url| url.host_str().map(|host| format!("provider {host}")))
1566 .unwrap_or_else(|| "configured provider".to_string())
1567}
1568
1569async fn option_try_or_else<T, E>(
1570 option: Option<T>,
1571 f: impl AsyncFnOnce() -> Result<T, E>,
1572) -> Result<T, E> {
1573 if let Some(value) = option { Ok(value) } else { f().await }
1574}
1575
1576#[cfg(test)]
1577mod tests {
1578 use super::*;
1579 use alloy_chains::NamedChain;
1580 use alloy_network::TransactionBuilder;
1581 use alloy_primitives::bytes;
1582 use alloy_rpc_types::TransactionRequest;
1583 use alloy_serde::WithOtherFields;
1584 use foundry_test_utils::rpc::{
1585 spawn_rpc_proxy_internal_error_after, spawn_rpc_proxy_method_not_found_before,
1586 spawn_rpc_proxy_rejecting_method_after,
1587 };
1588 use revm::context::{BlockEnv, TxEnv};
1589
1590 #[cfg(feature = "base")]
1591 use foundry_evm_hardforks::BaseUpgrade;
1592
1593 #[cfg(feature = "optimism")]
1594 use op_revm::OpSpecId;
1595
1596 fn resolved_context(block_number: BlockNumber) -> ForkContext {
1597 ForkContext {
1598 execution_chain_id: 1,
1599 source_chain_id: 1,
1600 network: NetworkVariant::Ethereum,
1601 network_profile: NetworkConfigs::default(),
1602 block_number,
1603 hardfork: None,
1604 instance_id: None,
1605 source_fork_block_number: None,
1606 source_fork_block_hash: None,
1607 }
1608 }
1609
1610 #[test]
1611 fn fork_endpoint_description_redacts_credentials_and_paths() {
1612 let endpoint = "https://user:secret@example.com/v2/private-api-key?token=another-secret";
1613 let description = fork_endpoint_description(endpoint);
1614
1615 assert_eq!(description, "provider example.com");
1616 assert!(!description.contains("secret"));
1617 assert!(!description.contains("private-api-key"));
1618 }
1619
1620 #[test]
1621 fn endpoint_network_fallback_preserves_explicit_selection() {
1622 let default = EvmOpts::default().endpoint_network_fallback();
1623 assert!(!default.has_network_selection());
1624
1625 let explicit = EvmOpts { networks: NetworkConfigs::with_ethereum(), ..Default::default() }
1626 .endpoint_network_fallback();
1627 assert!(explicit.has_network_selection());
1628 assert_eq!(explicit.execution_network(), NetworkVariant::Ethereum);
1629 }
1630
1631 #[test]
1632 fn backend_target_matches_number_pinned_fork_identity() {
1633 let context = ForkContext {
1634 execution_chain_id: 1,
1635 source_chain_id: 1,
1636 network: NetworkVariant::Ethereum,
1637 network_profile: NetworkConfigs::default(),
1638 block_number: 10,
1639 hardfork: None,
1640 instance_id: None,
1641 source_fork_block_number: None,
1642 source_fork_block_hash: None,
1643 };
1644
1645 let mut different_number = context;
1646 different_number.block_number += 1;
1647 assert!(!context.has_same_backend_target(different_number));
1648
1649 let mut different_profile = context;
1650 different_profile.network_profile = NetworkConfigs::with_celo();
1651 assert!(!context.has_same_backend_target(different_profile));
1652
1653 let mut different_hardfork = context;
1654 different_hardfork.hardfork =
1655 Some(FoundryHardfork::Ethereum(foundry_evm_hardforks::EthereumHardfork::Prague));
1656 assert!(!context.has_same_backend_target(different_hardfork));
1657
1658 let mut different_instance = context;
1659 different_instance.instance_id = Some(B256::with_last_byte(3));
1660 assert!(!context.has_same_backend_target(different_instance));
1661 }
1662
1663 #[tokio::test]
1664 async fn expected_fork_endpoint_requires_exact_identity() {
1665 let identity = ForkEndpointIdentity {
1666 endpoint: "http://localhost:8545".to_string(),
1667 execution_chain_id: 1,
1668 source_chain_id: 1,
1669 network: NetworkVariant::Ethereum,
1670 network_profile: NetworkConfigs::default(),
1671 reported_hardfork: Some("Prague".to_string()),
1672 hardfork: Some(FoundryHardfork::Ethereum(
1673 foundry_evm_hardforks::EthereumHardfork::Prague,
1674 )),
1675 instance_id: Some(B256::with_last_byte(1)),
1676 source_fork_block_number: None,
1677 source_fork_block_hash: None,
1678 };
1679 let mut evm_opts = EvmOpts::default();
1680 evm_opts.expect_fork_endpoint(identity.clone(), true);
1681
1682 assert!(evm_opts.ensure_expected_fork_endpoint(&identity).is_ok());
1683 assert_eq!(evm_opts.fork_endpoint, Some(identity.clone()));
1684 assert_eq!(evm_opts.expected_fork_endpoint, Some(identity.clone()));
1685 assert!(evm_opts.fork_network_is_inferred);
1686
1687 let mut changed_instance = identity.clone();
1688 changed_instance.instance_id = Some(B256::with_last_byte(2));
1689 assert!(evm_opts.ensure_expected_fork_endpoint(&changed_instance).is_err());
1690
1691 let mut changed_unknown_hardfork = identity;
1692 changed_unknown_hardfork.reported_hardfork = Some("Future".to_string());
1693 changed_unknown_hardfork.hardfork = None;
1694 assert!(evm_opts.ensure_expected_fork_endpoint(&changed_unknown_hardfork).is_err());
1695
1696 evm_opts.infer_network_from_fork().await.unwrap();
1697 assert_eq!(evm_opts.fork_endpoint, None);
1698 assert_eq!(evm_opts.expected_fork_endpoint, None);
1699 }
1700
1701 #[test]
1702 fn fork_expected_anvil_identity_seeds_strict_node_info_probe() {
1703 let authoritative = ForkEndpointIdentity {
1704 endpoint: "http://localhost:8545".to_string(),
1705 execution_chain_id: 1,
1706 source_chain_id: 1,
1707 network: NetworkVariant::Ethereum,
1708 network_profile: NetworkConfigs::default(),
1709 reported_hardfork: Some("Prague".to_string()),
1710 hardfork: Some(FoundryHardfork::Ethereum(
1711 foundry_evm_hardforks::EthereumHardfork::Prague,
1712 )),
1713 instance_id: Some(B256::with_last_byte(1)),
1714 source_fork_block_number: None,
1715 source_fork_block_hash: None,
1716 };
1717 let mut anonymous = authoritative.clone();
1718 anonymous.reported_hardfork = None;
1719 anonymous.hardfork = None;
1720 anonymous.instance_id = None;
1721 let mut evm_opts = EvmOpts {
1722 fork_url: Some(authoritative.endpoint.clone()),
1723 fork_endpoint: Some(anonymous),
1724 expected_fork_endpoint: Some(authoritative),
1725 ..Default::default()
1726 };
1727
1728 assert!(evm_opts.anvil_node_info_probe().identified);
1729
1730 evm_opts.fork_url = Some("http://localhost:9545".to_string());
1731 assert!(!evm_opts.anvil_node_info_probe().identified);
1732 }
1733
1734 #[test]
1735 fn selecting_new_fork_url_clears_only_endpoint_derived_state() {
1736 let identity = ForkEndpointIdentity {
1737 endpoint: "http://localhost:8545".to_string(),
1738 execution_chain_id: 42,
1739 source_chain_id: 42,
1740 network: NetworkVariant::Ethereum,
1741 network_profile: NetworkConfigs::with_celo(),
1742 reported_hardfork: None,
1743 hardfork: None,
1744 instance_id: None,
1745 source_fork_block_number: None,
1746 source_fork_block_hash: None,
1747 };
1748 let mut inferred = EvmOpts {
1749 fork_url: Some(identity.endpoint.clone()),
1750 fork_block_number: Some(123),
1751 fork_endpoint: Some(identity.clone()),
1752 expected_fork_endpoint: Some(identity.clone()),
1753 networks: identity.network_profile,
1754 fork_network_is_inferred: true,
1755 fork_chain_id_is_inferred: true,
1756 fork_block_number_is_inferred: true,
1757 ..Default::default()
1758 };
1759 inferred.env.chain_id = Some(identity.execution_chain_id);
1760
1761 inferred.set_fork_url("http://localhost:9545".to_string());
1762
1763 assert_eq!(inferred.fork_url.as_deref(), Some("http://localhost:9545"));
1764 assert_eq!(inferred.fork_block_number, None);
1765 assert_eq!(inferred.fork_endpoint, None);
1766 assert_eq!(inferred.expected_fork_endpoint, None);
1767 assert_eq!(inferred.networks, NetworkConfigs::default());
1768 assert_eq!(inferred.env.chain_id, None);
1769 assert!(!inferred.fork_network_is_inferred);
1770 assert!(!inferred.fork_chain_id_is_inferred);
1771 assert!(!inferred.fork_block_number_is_inferred);
1772
1773 let mut explicit = EvmOpts {
1774 fork_url: Some(identity.endpoint.clone()),
1775 fork_block_number: Some(123),
1776 fork_endpoint: Some(identity.clone()),
1777 expected_fork_endpoint: Some(identity),
1778 networks: NetworkConfigs::with_celo(),
1779 ..Default::default()
1780 };
1781 explicit.env.chain_id = Some(42);
1782
1783 explicit.set_fork_url("http://localhost:9545".to_string());
1784
1785 assert_eq!(explicit.networks, NetworkConfigs::with_celo());
1786 assert_eq!(explicit.env.chain_id, Some(42));
1787 assert_eq!(explicit.fork_block_number, Some(123));
1788 assert_eq!(explicit.fork_endpoint, None);
1789 assert_eq!(explicit.expected_fork_endpoint, None);
1790 assert!(!explicit.fork_block_number_is_inferred);
1791 }
1792
1793 #[test]
1794 fn changed_fork_auth_clears_only_endpoint_derived_state() {
1795 let endpoint = "http://localhost:8545";
1796 let identity = ForkEndpointIdentity {
1797 endpoint: endpoint.to_string(),
1798 execution_chain_id: 42,
1799 source_chain_id: 42,
1800 network: NetworkVariant::Ethereum,
1801 network_profile: NetworkConfigs::with_celo(),
1802 reported_hardfork: None,
1803 hardfork: None,
1804 instance_id: Some(B256::with_last_byte(1)),
1805 source_fork_block_number: None,
1806 source_fork_block_hash: None,
1807 };
1808 let context = ForkContext {
1809 execution_chain_id: identity.execution_chain_id,
1810 source_chain_id: identity.source_chain_id,
1811 network: identity.network,
1812 network_profile: identity.network_profile,
1813 block_number: 123,
1814 hardfork: identity.hardfork,
1815 instance_id: identity.instance_id,
1816 source_fork_block_number: identity.source_fork_block_number,
1817 source_fork_block_hash: identity.source_fork_block_hash,
1818 };
1819 let mut evm_opts = EvmOpts {
1820 fork_url: Some(endpoint.to_string()),
1821 fork_block_number: Some(123),
1822 fork_headers: Some(vec!["Authorization: first".to_string()]),
1823 rpc_jwt: Some("first-jwt".to_string()),
1824 fork_endpoint: Some(identity.clone()),
1825 expected_fork_endpoint: Some(identity),
1826 networks: NetworkConfigs::with_celo(),
1827 ..Default::default()
1828 };
1829 evm_opts.env.chain_id = Some(42);
1830 let fork = evm_opts.resolved_fork(
1831 endpoint,
1832 BlockNumHash::new(context.block_number, B256::with_last_byte(2)),
1833 context,
1834 );
1835
1836 evm_opts.fork_headers = Some(vec!["Authorization: second".to_string()]);
1837 evm_opts.rpc_jwt = Some("second-jwt".to_string());
1838
1839 assert!(evm_opts.invalidate_fork_endpoint_if_source_changed(&fork));
1840 assert_eq!(evm_opts.fork_endpoint, None);
1841 assert_eq!(evm_opts.expected_fork_endpoint, None);
1842 assert_eq!(evm_opts.fork_block_number, Some(123));
1843 assert_eq!(evm_opts.networks, NetworkConfigs::with_celo());
1844 assert_eq!(evm_opts.env.chain_id, Some(42));
1845 }
1846
1847 #[test]
1848 fn changed_fork_selector_preserves_endpoint_identity() {
1849 let endpoint = "http://localhost:8545";
1850 let identity = ForkEndpointIdentity {
1851 endpoint: endpoint.to_string(),
1852 execution_chain_id: 1,
1853 source_chain_id: 1,
1854 network: NetworkVariant::Ethereum,
1855 network_profile: NetworkConfigs::default(),
1856 reported_hardfork: None,
1857 hardfork: None,
1858 instance_id: Some(B256::with_last_byte(1)),
1859 source_fork_block_number: None,
1860 source_fork_block_hash: None,
1861 };
1862 let mut evm_opts = EvmOpts {
1863 fork_url: Some(endpoint.to_string()),
1864 fork_headers: Some(vec!["x-source: stable".to_string()]),
1865 rpc_jwt: Some("stable-jwt".to_string()),
1866 fork_endpoint: Some(identity.clone()),
1867 expected_fork_endpoint: Some(identity.clone()),
1868 ..Default::default()
1869 };
1870 let fork = evm_opts.resolved_fork(
1871 endpoint,
1872 BlockNumHash::new(1, B256::with_last_byte(2)),
1873 resolved_context(1),
1874 );
1875
1876 evm_opts.fork_block_number = Some(1);
1877
1878 assert!(!evm_opts.invalidate_fork_endpoint_if_source_changed(&fork));
1879 assert_eq!(evm_opts.fork_endpoint, Some(identity.clone()));
1880 assert_eq!(evm_opts.expected_fork_endpoint, Some(identity));
1881 }
1882
1883 #[test]
1884 fn selecting_same_fork_url_preserves_pinned_state() {
1885 let mut evm_opts = EvmOpts {
1886 fork_url: Some("http://localhost:8545".to_string()),
1887 fork_block_number: Some(123),
1888 networks: NetworkConfigs::with_celo(),
1889 fork_network_is_inferred: true,
1890 fork_chain_id_is_inferred: true,
1891 fork_block_number_is_inferred: true,
1892 ..Default::default()
1893 };
1894 evm_opts.env.chain_id = Some(42);
1895
1896 evm_opts.set_fork_url("http://localhost:8545".to_string());
1897
1898 assert_eq!(evm_opts.fork_block_number, Some(123));
1899 assert_eq!(evm_opts.networks, NetworkConfigs::with_celo());
1900 assert_eq!(evm_opts.env.chain_id, Some(42));
1901 assert!(evm_opts.fork_network_is_inferred);
1902 assert!(evm_opts.fork_chain_id_is_inferred);
1903 assert!(evm_opts.fork_block_number_is_inferred);
1904 }
1905
1906 #[cfg(feature = "monad")]
1907 fn monad_env(timestamp: u64) -> EvmEnv<foundry_evm_hardforks::MonadHardfork, BlockEnv> {
1908 let mut block = BlockEnv::default();
1909 block.set_timestamp(U256::from(timestamp));
1910 let mut cfg = CfgEnv::new_with_spec(foundry_evm_hardforks::MonadHardfork::default());
1911 cfg.chain_id = NamedChain::Monad as u64;
1912 EvmEnv::new(cfg, block)
1913 }
1914
1915 #[test]
1916 #[cfg(feature = "monad")]
1917 fn resolve_execution_spec_uses_monad_ten_activation_timestamp() {
1918 let config = Config::default();
1919 let activation =
1920 foundry_evm_hardforks::MonadHardfork::MonadTen.mainnet_activation_timestamp().unwrap();
1921
1922 let mut before = monad_env(activation - 1);
1923 assert_eq!(
1924 resolve_execution_spec(
1925 config.evm_version,
1926 config.hardfork,
1927 &mut before,
1928 ExecutionSpecContext::fork(NamedChain::Monad as u64, None),
1929 None,
1930 ),
1931 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadNine))
1932 );
1933 assert_eq!(before.cfg_env.spec, foundry_evm_hardforks::MonadHardfork::MonadNine);
1934
1935 let mut after = monad_env(activation);
1936 assert_eq!(
1937 resolve_execution_spec(
1938 config.evm_version,
1939 config.hardfork,
1940 &mut after,
1941 ExecutionSpecContext::fork(NamedChain::Monad as u64, None),
1942 None,
1943 ),
1944 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadTen))
1945 );
1946 assert_eq!(after.cfg_env.spec, foundry_evm_hardforks::MonadHardfork::MonadTen);
1947 }
1948
1949 #[test]
1950 #[cfg(feature = "monad")]
1951 fn resolve_execution_spec_prefers_exact_endpoint_hardfork() {
1952 let config = Config::default();
1953 let activation =
1954 foundry_evm_hardforks::MonadHardfork::MonadNine.mainnet_activation_timestamp().unwrap();
1955 let mut env = monad_env(activation);
1956 let endpoint_hardfork =
1957 FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadEight);
1958
1959 assert_eq!(
1960 resolve_execution_spec(
1961 config.evm_version,
1962 config.hardfork,
1963 &mut env,
1964 ExecutionSpecContext::fork(NamedChain::Monad as u64, Some(endpoint_hardfork),),
1965 None,
1966 ),
1967 Some(endpoint_hardfork)
1968 );
1969 assert_eq!(env.cfg_env.spec, foundry_evm_hardforks::MonadHardfork::MonadEight);
1970 }
1971
1972 #[test]
1973 #[cfg(feature = "monad")]
1974 fn resolve_execution_spec_ignores_schedule_for_local_env() {
1975 let config = Config::default();
1976 let activation =
1977 foundry_evm_hardforks::MonadHardfork::MonadTen.mainnet_activation_timestamp().unwrap();
1978 let mut env = monad_env(activation - 1);
1979
1980 assert_eq!(
1981 resolve_execution_spec(
1982 config.evm_version,
1983 config.hardfork,
1984 &mut env,
1985 ExecutionSpecContext::Local,
1986 None,
1987 ),
1988 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadTen))
1989 );
1990 assert_eq!(env.cfg_env.spec, foundry_evm_hardforks::MonadHardfork::MonadTen);
1991 }
1992
1993 #[test]
1994 fn resolve_execution_spec_preserves_ethereum_config_for_local_forks() {
1995 let config = Config::default();
1996 let mut block = BlockEnv::default();
1997 block.set_timestamp(U256::from(1_500_000_000u64));
1998 let mut env = EvmEnv::new(CfgEnv::new_with_spec(SpecId::LONDON), block);
1999
2000 assert_eq!(
2001 resolve_execution_spec(
2002 config.evm_version,
2003 config.hardfork,
2004 &mut env,
2005 ExecutionSpecContext::fork(NamedChain::Mainnet as u64, None),
2006 None,
2007 ),
2008 None
2009 );
2010 assert_eq!(env.cfg_env.spec, config.evm_spec_id::<SpecId>());
2011
2012 let endpoint_hardfork =
2013 FoundryHardfork::Ethereum(foundry_evm_hardforks::EthereumHardfork::Frontier);
2014 assert_eq!(
2015 resolve_execution_spec(
2016 config.evm_version,
2017 config.hardfork,
2018 &mut env,
2019 ExecutionSpecContext::fork(NamedChain::Mainnet as u64, Some(endpoint_hardfork),),
2020 None,
2021 ),
2022 None
2023 );
2024 assert_eq!(env.cfg_env.spec, config.evm_spec_id::<SpecId>());
2025 }
2026
2027 #[test]
2028 fn resolve_execution_spec_uses_ethereum_schedule_for_historical_execution() {
2029 let config = Config::default();
2030 let timestamp = 1_500_000_000u64;
2031 let expected =
2032 FoundryHardfork::from_chain_and_timestamp(NamedChain::Mainnet as u64, timestamp)
2033 .unwrap();
2034 let mut block = BlockEnv::default();
2035 block.set_timestamp(U256::from(timestamp));
2036 let mut env = EvmEnv::new(CfgEnv::new_with_spec(SpecId::LONDON), block);
2037
2038 assert_eq!(
2039 resolve_execution_spec(
2040 config.evm_version,
2041 config.hardfork,
2042 &mut env,
2043 ExecutionSpecContext::historical(NamedChain::Mainnet as u64, None),
2044 None,
2045 ),
2046 Some(expected)
2047 );
2048 assert_eq!(env.cfg_env.spec, SpecId::from(expected));
2049 }
2050
2051 #[test]
2052 #[cfg(feature = "optimism")]
2053 fn resolve_execution_spec_uses_optimism_schedule_for_local_forks() {
2054 let config =
2055 Config { hardfork: Some("ethereum:london".parse().unwrap()), ..Default::default() };
2056 let chain_id = NamedChain::Optimism as u64;
2057 let timestamp = u64::MAX;
2058 let expected = FoundryHardfork::from_chain_and_timestamp(chain_id, timestamp).unwrap();
2059 let mut block = BlockEnv::default();
2060 block.set_timestamp(U256::from(timestamp));
2061 let mut env = EvmEnv::new(CfgEnv::new_with_spec(OpSpecId::default()), block);
2062
2063 assert_eq!(
2064 resolve_execution_spec(
2065 config.evm_version,
2066 config.hardfork,
2067 &mut env,
2068 ExecutionSpecContext::fork(chain_id, Some("tempo:T3".parse().unwrap())),
2069 None,
2070 ),
2071 Some(expected)
2072 );
2073 assert_eq!(env.cfg_env.spec, OpSpecId::from_foundry_hardfork(expected).unwrap());
2074 }
2075
2076 #[test]
2077 #[cfg(feature = "monad")]
2078 fn resolve_execution_spec_honors_explicit_precedence() {
2079 let activation =
2080 foundry_evm_hardforks::MonadHardfork::MonadNine.mainnet_activation_timestamp().unwrap();
2081 let mut configured = Config {
2082 hardfork: Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadNine)),
2083 ..Default::default()
2084 };
2085 let mut env = monad_env(activation - 1);
2086
2087 assert_eq!(
2088 resolve_execution_spec(
2089 configured.evm_version,
2090 configured.hardfork,
2091 &mut env,
2092 ExecutionSpecContext::fork(NamedChain::Monad as u64, None),
2093 None,
2094 ),
2095 configured.hardfork
2096 );
2097 assert_eq!(env.cfg_env.spec, foundry_evm_hardforks::MonadHardfork::MonadNine);
2098
2099 configured.hardfork = None;
2100 assert_eq!(
2101 resolve_execution_spec(
2102 configured.evm_version,
2103 configured.hardfork,
2104 &mut env,
2105 ExecutionSpecContext::fork(NamedChain::Monad as u64, None),
2106 Some(foundry_evm_hardforks::MonadHardfork::MonadEight),
2107 ),
2108 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadEight))
2109 );
2110 assert_eq!(env.cfg_env.spec, foundry_evm_hardforks::MonadHardfork::MonadEight);
2111 }
2112
2113 #[tokio::test(flavor = "multi_thread")]
2114 #[cfg(feature = "monad")]
2115 async fn fork_context_preserves_source_chain_with_execution_override() {
2116 let activation =
2117 foundry_evm_hardforks::MonadHardfork::MonadNine.mainnet_activation_timestamp().unwrap();
2118 let (_api, handle) = anvil::spawn(
2119 anvil::NodeConfig::test()
2120 .with_networks(NetworkConfigs::with_ethereum())
2121 .with_chain_id(Some(NamedChain::Monad as u64))
2122 .with_genesis_timestamp(Some(activation - 1)),
2123 )
2124 .await;
2125 let mut inferred = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2126 inferred.infer_network_from_fork().await.unwrap();
2127 assert_eq!(inferred.networks, NetworkConfigs::default());
2128
2129 let mut evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2130 evm_opts.env.chain_id = Some(NamedChain::Mainnet as u64);
2131 evm_opts.networks = NetworkConfigs::with_monad();
2132
2133 let (mut evm_env, tx_env, fork_context) = evm_opts
2134 .env_with_fork_context::<foundry_evm_hardforks::MonadHardfork, BlockEnv, TxEnv>()
2135 .await
2136 .unwrap();
2137 let fork_context = fork_context.unwrap();
2138
2139 assert_eq!(fork_context.source_chain_id, NamedChain::Monad as u64);
2140 assert_eq!(fork_context.network, NetworkVariant::Ethereum);
2141 assert!(matches!(fork_context.hardfork, Some(FoundryHardfork::Ethereum(_))));
2142 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
2143 assert_eq!(tx_env.chain_id, Some(NamedChain::Mainnet as u64));
2144 assert_eq!(
2145 resolve_execution_spec(
2146 Config::default().evm_version,
2147 Config::default().hardfork,
2148 &mut evm_env,
2149 ExecutionSpecContext::fork(fork_context.source_chain_id, fork_context.hardfork,),
2150 None,
2151 ),
2152 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadEight))
2153 );
2154 }
2155
2156 #[tokio::test(flavor = "multi_thread")]
2157 async fn infer_network_default_and_custom_anvil_selects_ethereum() {
2158 for chain_id in [NamedChain::Mainnet as u64, NamedChain::AnvilHardhat as u64, 98_765_432] {
2159 let (_api, handle) =
2160 anvil::spawn(anvil::NodeConfig::test().with_chain_id(Some(chain_id))).await;
2161
2162 let config = Config::figment();
2163 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2164 evm_opts.fork_url = Some(handle.http_endpoint());
2165 assert_eq!(evm_opts.networks, NetworkConfigs::default());
2166
2167 evm_opts.infer_network_from_fork().await.unwrap();
2168
2169 assert_eq!(evm_opts.env.chain_id, Some(chain_id));
2170 assert!(!evm_opts.networks.is_tempo());
2171 #[cfg(feature = "optimism")]
2172 assert!(!evm_opts.networks.is_optimism());
2173 #[cfg(feature = "base")]
2174 assert!(!evm_opts.networks.is_base());
2175 assert!(!evm_opts.networks.is_celo());
2176 assert_eq!(evm_opts.networks, NetworkConfigs::default());
2177 }
2178 }
2179
2180 #[tokio::test(flavor = "multi_thread")]
2181 async fn infer_network_preserves_explicit_ethereum() {
2182 let networks = NetworkConfigs::with_ethereum();
2183 let mut evm_opts = EvmOpts {
2184 fork_url: Some("http://127.0.0.1:1".to_string()),
2185 networks,
2186 ..Default::default()
2187 };
2188
2189 let error = evm_opts.infer_network_from_fork().await.unwrap_err();
2190
2191 assert!(error.to_string().contains("failed to retrieve chain ID"));
2192 assert_eq!(evm_opts.networks, networks);
2193 assert_eq!(evm_opts.env.chain_id, None);
2194 }
2195
2196 #[tokio::test]
2197 async fn clearing_fork_restores_inferred_network_defaults() {
2198 let mut profiles = vec![NetworkConfigs::with_celo(), NetworkConfigs::with_tempo()];
2199 #[cfg(feature = "optimism")]
2200 profiles.push(NetworkConfigs::with_optimism());
2201 #[cfg(feature = "monad")]
2202 profiles.push(NetworkConfigs::with_monad());
2203 #[cfg(feature = "base")]
2204 profiles.push(NetworkConfigs::with_base());
2205
2206 for networks in profiles {
2207 let mut evm_opts = EvmOpts {
2208 networks,
2209 fork_endpoint: Some(ForkEndpointIdentity {
2210 endpoint: "http://localhost:8545".to_string(),
2211 execution_chain_id: 1,
2212 source_chain_id: 1,
2213 network: networks.execution_network(),
2214 network_profile: networks,
2215 reported_hardfork: None,
2216 hardfork: None,
2217 instance_id: None,
2218 source_fork_block_number: None,
2219 source_fork_block_hash: None,
2220 }),
2221 fork_block_number: Some(123),
2222 fork_network_is_inferred: true,
2223 fork_chain_id_is_inferred: true,
2224 fork_block_number_is_inferred: true,
2225 ..Default::default()
2226 };
2227 evm_opts.env.chain_id = Some(1);
2228
2229 evm_opts.infer_network_from_fork().await.unwrap();
2230
2231 assert_eq!(evm_opts.networks, NetworkConfigs::default());
2232 assert_eq!(evm_opts.fork_endpoint, None);
2233 assert!(!evm_opts.fork_network_is_inferred);
2234 assert_eq!(evm_opts.env.chain_id, None);
2235 assert!(!evm_opts.fork_chain_id_is_inferred);
2236 assert_eq!(evm_opts.fork_block_number, None);
2237 assert!(!evm_opts.fork_block_number_is_inferred);
2238 }
2239 }
2240
2241 #[tokio::test(flavor = "multi_thread")]
2242 async fn fork_network_detects_ethereum_anvil() {
2243 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2244 let evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2245
2246 assert_eq!(
2247 evm_opts.fork_network().await.unwrap(),
2248 (NamedChain::AnvilHardhat as u64, NetworkVariant::Ethereum)
2249 );
2250 }
2251
2252 #[tokio::test(flavor = "multi_thread")]
2253 async fn fork_network_uses_anvil_metadata_for_unknown_chain() {
2254 let chain_id = 98_765_432;
2255 let (_api, handle) =
2256 anvil::spawn(anvil::NodeConfig::test().with_chain_id(Some(chain_id))).await;
2257 let evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2258
2259 assert_eq!(evm_opts.fork_network().await.unwrap(), (chain_id, NetworkVariant::Ethereum));
2260 }
2261
2262 #[tokio::test]
2263 #[cfg(feature = "base")]
2264 async fn base_endpoint_identity_uses_generic_chain_profile() {
2265 let endpoint = "http://127.0.0.1:1";
2266 let provider = EvmOpts::default().fork_provider_with_url::<AnyNetwork>(endpoint).unwrap();
2267
2268 let identity = EvmOpts::resolve_fork_endpoint_identity(
2269 &provider,
2270 endpoint,
2271 NamedChain::Base as u64,
2272 None,
2273 None,
2274 EndpointHardforkPolicy::Optional,
2275 )
2276 .await
2277 .unwrap();
2278
2279 assert_eq!(identity.execution_chain_id, NamedChain::Base as u64);
2280 assert_eq!(identity.source_chain_id, NamedChain::Base as u64);
2281 assert_eq!(identity.network, NetworkVariant::Base);
2282 assert!(identity.network_profile.is_base());
2283 assert_eq!(identity.reported_hardfork, None);
2284 assert_eq!(identity.hardfork, None);
2285 }
2286
2287 #[tokio::test(flavor = "multi_thread")]
2288 async fn fork_non_anvil_node_info_rpc_error_is_optional() {
2289 let (_api, handle) =
2290 anvil::spawn(anvil::NodeConfig::test().with_chain_id(Some(NamedChain::Mainnet as u64)))
2291 .await;
2292 let fork_url =
2293 spawn_rpc_proxy_internal_error_after(handle.http_endpoint(), "anvil_nodeInfo", 0).await;
2294 let mut evm_opts = EvmOpts { fork_url: Some(fork_url), ..Default::default() };
2295
2296 evm_opts.infer_network_from_fork().await.unwrap();
2297
2298 assert_eq!(evm_opts.networks, NetworkConfigs::default());
2299 assert_eq!(evm_opts.env.chain_id, Some(NamedChain::Mainnet as u64));
2300 }
2301
2302 #[tokio::test(flavor = "multi_thread")]
2303 async fn fork_node_info_failure_is_strict_after_anvil_is_identified() {
2304 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2305 let fork_url =
2306 spawn_rpc_proxy_rejecting_method_after(handle.http_endpoint(), "anvil_nodeInfo", 1)
2307 .await;
2308 let evm_opts = EvmOpts { fork_url: Some(fork_url), ..Default::default() };
2309
2310 let error = evm_opts.discover_fork_endpoint().await.unwrap_err();
2311
2312 assert!(
2313 error.to_string().contains("failed to determine network family from endpoint"),
2314 "{error}"
2315 );
2316 }
2317
2318 #[tokio::test(flavor = "multi_thread")]
2319 async fn fork_node_info_probe_becomes_strict_after_initial_failure() {
2320 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2321 let fork_url =
2322 spawn_rpc_proxy_method_not_found_before(handle.http_endpoint(), "anvil_nodeInfo", 1)
2323 .await;
2324 let evm_opts = EvmOpts { fork_url: Some(fork_url), ..Default::default() };
2325
2326 let identity = evm_opts.discover_fork_endpoint().await.unwrap();
2327
2328 assert!(identity.reported_hardfork.is_some());
2329 assert!(identity.instance_id.is_some());
2330 }
2331
2332 #[tokio::test(flavor = "multi_thread")]
2333 async fn fork_cached_anvil_identity_keeps_node_info_probe_strict() {
2334 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2335 let fork_url =
2336 spawn_rpc_proxy_rejecting_method_after(handle.http_endpoint(), "anvil_nodeInfo", 2)
2337 .await;
2338 let mut evm_opts = EvmOpts { fork_url: Some(fork_url), ..Default::default() };
2339 evm_opts.infer_network_from_fork().await.unwrap();
2340 assert!(evm_opts.fork_endpoint.as_ref().unwrap().reported_hardfork.is_some());
2341
2342 let error = evm_opts.fork_network().await.unwrap_err();
2343
2344 assert!(
2345 error.to_string().contains("failed to determine network family from endpoint"),
2346 "{error}"
2347 );
2348 }
2349
2350 #[test]
2351 fn known_network_variant_does_not_guess_unknown_chain() {
2352 assert_eq!(NetworkVariant::from_known_chain_id(98_765_432).unwrap(), None);
2353 }
2354
2355 #[test]
2356 #[cfg(feature = "monad")]
2357 fn known_network_variant_classifies_monad() {
2358 assert_eq!(
2359 NetworkVariant::from_known_chain_id(NamedChain::Monad as u64).unwrap(),
2360 Some(NetworkVariant::Monad)
2361 );
2362 }
2363
2364 #[test]
2365 #[cfg(not(feature = "monad"))]
2366 fn known_network_variant_rejects_disabled_monad() {
2367 assert_eq!(
2368 NetworkVariant::from_known_chain_id(NamedChain::Monad as u64).unwrap_err(),
2369 "network family `monad` is not enabled in this build"
2370 );
2371 }
2372
2373 #[tokio::test(flavor = "multi_thread")]
2374 async fn infer_network_tempo_anvil_via_node_info() {
2375 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test_tempo()).await;
2376
2377 let config = Config::figment();
2378 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2379 evm_opts.fork_url = Some(handle.http_endpoint());
2380 assert_eq!(evm_opts.networks, NetworkConfigs::default());
2382
2383 evm_opts.infer_network_from_fork().await.unwrap();
2384
2385 assert!(evm_opts.networks.is_tempo(), "should detect tempo via anvil_nodeInfo");
2386 }
2387
2388 #[tokio::test(flavor = "multi_thread")]
2389 async fn infer_network_tempo_propagates_unavailable_rpc() {
2390 let config = Config::figment();
2391 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2392 evm_opts.fork_url = Some("http://127.0.0.1:1".to_string());
2393 evm_opts.networks = NetworkConfigs::with_tempo();
2394
2395 let error = evm_opts.infer_network_from_fork().await.unwrap_err();
2396
2397 assert!(error.to_string().contains("failed to retrieve chain ID"));
2398 assert!(evm_opts.networks.is_tempo());
2399 }
2400
2401 #[tokio::test]
2402 async fn create2_deployer_availability_requires_resolved_fork_block() {
2403 let evm_opts =
2404 EvmOpts { fork_url: Some("http://127.0.0.1:1".to_string()), ..Default::default() };
2405
2406 let err = evm_opts.can_use_create2_deployer(None).await.unwrap_err();
2407 assert!(err.to_string().contains("fork block must be resolved"));
2408 }
2409
2410 #[test]
2411 fn resolved_fork_matches_source_headers_and_selector() {
2412 let headers = vec!["Authorization: one".to_string()];
2413 let fork = ResolvedFork::new(
2414 "http://127.0.0.1:1",
2415 Some(&headers),
2416 None,
2417 None,
2418 BlockNumHash::new(1, B256::ZERO),
2419 resolved_context(1),
2420 );
2421 let mut evm_opts = EvmOpts {
2422 fork_url: Some("http://127.0.0.1:1".to_string()),
2423 fork_headers: Some(headers),
2424 rpc_headers: Some(vec!["x-fallback: ignored".to_string()]),
2425 ..Default::default()
2426 };
2427
2428 assert!(evm_opts.resolved_fork_matches(&fork));
2429 let debug = format!("{fork:?}");
2430 assert!(!debug.contains("127.0.0.1"));
2431 assert!(!debug.contains("Authorization"));
2432 assert!(!debug.contains("one"));
2433
2434 evm_opts.fork_headers = Some(vec!["Authorization: two".to_string()]);
2435 assert!(!evm_opts.resolved_fork_matches(&fork));
2436
2437 evm_opts.fork_headers = Some(vec!["Authorization: one".to_string()]);
2438 evm_opts.fork_block_number = Some(1);
2439 assert!(!evm_opts.resolved_fork_matches(&fork));
2440
2441 evm_opts.fork_block_number = None;
2442 evm_opts.fork_url = Some("http://127.0.0.1:2".to_string());
2443 assert!(!evm_opts.resolved_fork_matches(&fork));
2444
2445 let fork = ResolvedFork::new(
2446 "http://127.0.0.1:1",
2447 None,
2448 None,
2449 None,
2450 BlockNumHash::new(1, B256::ZERO),
2451 resolved_context(1),
2452 );
2453 evm_opts.fork_url = Some("http://127.0.0.1:1".to_string());
2454 evm_opts.fork_headers = Some(Vec::new());
2455 evm_opts.rpc_headers = None;
2456 assert!(evm_opts.resolved_fork_matches(&fork));
2457
2458 let rpc_headers = vec!["Authorization: fallback".to_string()];
2459 let fork = ResolvedFork::new(
2460 "http://127.0.0.1:1",
2461 Some(&rpc_headers),
2462 Some("secret-jwt"),
2463 None,
2464 BlockNumHash::new(1, B256::ZERO),
2465 resolved_context(1),
2466 );
2467 evm_opts.fork_headers = None;
2468 evm_opts.rpc_headers = Some(rpc_headers);
2469 evm_opts.rpc_jwt = Some("secret-jwt".to_string());
2470 assert!(evm_opts.resolved_fork_matches(&fork));
2471 assert!(!format!("{fork:?}").contains("secret-jwt"));
2472
2473 evm_opts.rpc_jwt = Some("different-jwt".to_string());
2474 assert!(!evm_opts.resolved_fork_matches(&fork));
2475 }
2476
2477 #[tokio::test(flavor = "multi_thread")]
2478 async fn create2_deployer_preflight_uses_exact_fork_hash() {
2479 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2480 let provider = handle.http_provider();
2481 let sender = handle.dev_accounts().next().unwrap();
2482
2483 let init_code = bytes!("6001600c60003960016000f300");
2484 let receipt = provider
2485 .send_transaction(WithOtherFields::new(
2486 TransactionRequest::default().from(sender).with_deploy_code(init_code),
2487 ))
2488 .await
2489 .unwrap()
2490 .get_receipt()
2491 .await
2492 .unwrap();
2493 let deployer = receipt.contract_address.unwrap();
2494 let block_number = receipt.block_number.unwrap();
2495
2496 let evm_opts = EvmOpts {
2497 fork_url: Some(handle.http_endpoint()),
2498 fork_block_number: Some(block_number),
2499 create2_deployer: deployer,
2500 ..Default::default()
2501 };
2502 let (_, _, fork) = evm_opts.env_resolved::<SpecId, BlockEnv, TxEnv>().await.unwrap();
2503 let fork = fork.unwrap();
2504 assert!(evm_opts.can_use_create2_deployer_resolved(Some(&fork)).await.unwrap());
2505
2506 provider
2507 .raw_request::<_, ()>("anvil_reorg".into(), (1_u64, Vec::<serde_json::Value>::new()))
2508 .await
2509 .unwrap();
2510 let replacement = provider.get_block_by_number(block_number.into()).await.unwrap().unwrap();
2511 assert_ne!(replacement.header().hash(), fork.hash());
2512 assert!(
2513 !evm_opts.can_use_create2_deployer(Some(block_number)).await.unwrap(),
2514 "the replacement block must not contain the deployer"
2515 );
2516
2517 assert!(
2518 !matches!(evm_opts.can_use_create2_deployer_resolved(Some(&fork)).await, Ok(false)),
2519 "the exact lookup fell back to the replacement block"
2520 );
2521 }
2522
2523 #[tokio::test(flavor = "multi_thread")]
2524 #[cfg(feature = "monad")]
2525 async fn infer_network_monad_anvil_via_node_info() {
2526 for chain_id in [NamedChain::Mainnet as u64, NamedChain::AnvilHardhat as u64, 98_765_432] {
2527 let (_api, handle) =
2528 anvil::spawn(anvil::NodeConfig::test_monad().with_chain_id(Some(chain_id))).await;
2529
2530 let config = Config::figment();
2531 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2532 evm_opts.fork_url = Some(handle.http_endpoint());
2533 assert_eq!(evm_opts.networks, NetworkConfigs::default());
2534
2535 evm_opts.infer_network_from_fork().await.unwrap();
2536
2537 assert_eq!(evm_opts.env.chain_id, Some(chain_id));
2538 assert!(
2539 evm_opts.networks.is_monad(),
2540 "should detect Monad via anvil_nodeInfo for chain {chain_id}"
2541 );
2542 }
2543 }
2544
2545 #[tokio::test(flavor = "multi_thread")]
2546 #[cfg(feature = "monad")]
2547 async fn fork_context_carries_exact_monad_anvil_hardfork() {
2548 let activation =
2549 foundry_evm_hardforks::MonadHardfork::MonadNine.mainnet_activation_timestamp().unwrap();
2550 let (_api, handle) = anvil::spawn(
2551 anvil::NodeConfig::test_monad()
2552 .with_chain_id(Some(NamedChain::Monad as u64))
2553 .with_genesis_timestamp(Some(activation))
2554 .with_hardfork(Some(foundry_evm_hardforks::MonadHardfork::MonadEight.into())),
2555 )
2556 .await;
2557 let mut evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2558
2559 evm_opts.infer_network_from_fork().await.unwrap();
2560 let (mut evm_env, _, context) = evm_opts
2561 .env_with_fork_context::<foundry_evm_hardforks::MonadHardfork, BlockEnv, TxEnv>()
2562 .await
2563 .unwrap();
2564 let context = context.unwrap();
2565
2566 assert_eq!(
2567 context.hardfork,
2568 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadEight))
2569 );
2570 assert_eq!(
2571 resolve_execution_spec(
2572 Config::default().evm_version,
2573 Config::default().hardfork,
2574 &mut evm_env,
2575 ExecutionSpecContext::fork(context.source_chain_id, context.hardfork),
2576 None,
2577 ),
2578 context.hardfork
2579 );
2580 assert_eq!(evm_env.cfg_env.spec, foundry_evm_hardforks::MonadHardfork::MonadEight);
2581 }
2582
2583 #[tokio::test(flavor = "multi_thread")]
2584 #[cfg(feature = "monad")]
2585 async fn fork_context_refreshes_identity_after_same_url_reset() {
2586 let (_api, monad_eight) = anvil::spawn(
2587 anvil::NodeConfig::test_monad()
2588 .with_chain_id(Some(NamedChain::MonadTestnet as u64))
2589 .with_hardfork(Some(foundry_evm_hardforks::MonadHardfork::MonadEight.into())),
2590 )
2591 .await;
2592 let (_api, monad_nine) = anvil::spawn(
2593 anvil::NodeConfig::test_monad()
2594 .with_chain_id(Some(NamedChain::Monad as u64))
2595 .with_hardfork(Some(foundry_evm_hardforks::MonadHardfork::MonadNine.into())),
2596 )
2597 .await;
2598 let (fork_api, fork_handle) = anvil::spawn(
2599 anvil::NodeConfig::test()
2600 .with_chain_id(Some(NamedChain::Mainnet as u64))
2601 .with_no_storage_caching(true)
2602 .with_eth_rpc_url(Some(monad_eight.http_endpoint()))
2603 .with_fork_block_number(Some(0u64)),
2604 )
2605 .await;
2606
2607 let mut evm_opts =
2608 EvmOpts { fork_url: Some(fork_handle.http_endpoint()), ..Default::default() };
2609 evm_opts.infer_network_from_fork().await.unwrap();
2610 let cached = evm_opts.fork_endpoint.as_ref().unwrap();
2611 assert_eq!(cached.source_chain_id, NamedChain::MonadTestnet as u64);
2612 assert_eq!(
2613 cached.hardfork,
2614 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadEight))
2615 );
2616
2617 let (_, _, first) = evm_opts
2618 .env_with_fork_context::<foundry_evm_hardforks::MonadHardfork, BlockEnv, TxEnv>()
2619 .await
2620 .unwrap();
2621 assert_eq!(first.unwrap().source_chain_id, NamedChain::MonadTestnet as u64);
2622
2623 fork_api
2624 .anvil_reset(Some(alloy_rpc_types::anvil::Forking {
2625 json_rpc_url: Some(monad_nine.http_endpoint()),
2626 block_number: Some(0),
2627 }))
2628 .await
2629 .unwrap();
2630
2631 let (_, _, second) = evm_opts
2632 .env_with_fork_context::<foundry_evm_hardforks::MonadHardfork, BlockEnv, TxEnv>()
2633 .await
2634 .unwrap();
2635 let second = second.unwrap();
2636 assert_eq!(second.source_chain_id, NamedChain::Monad as u64);
2637 assert_eq!(
2638 second.hardfork,
2639 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadNine))
2640 );
2641 }
2642
2643 #[tokio::test(flavor = "multi_thread")]
2644 async fn expected_fork_endpoint_detects_same_url_reset() {
2645 let (api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2646 let mut evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2647 evm_opts.infer_network_from_fork().await.unwrap();
2648 let identity = evm_opts.fork_endpoint.clone().unwrap();
2649 let original_instance = identity.instance_id;
2650 let network_is_inferred = evm_opts.fork_network_is_inferred;
2651 evm_opts.expect_fork_endpoint(identity, network_is_inferred);
2652
2653 api.anvil_reset(None).await.unwrap();
2654
2655 assert_ne!(api.instance_id(), original_instance.unwrap());
2656 let error = evm_opts.env_with_fork_context::<SpecId, BlockEnv, TxEnv>().await.unwrap_err();
2657 assert!(
2658 error.to_string().contains("changed after its execution context was selected"),
2659 "{error}"
2660 );
2661 }
2662
2663 #[tokio::test(flavor = "multi_thread")]
2664 async fn resolved_fork_detects_same_url_reset_without_cached_expectation() {
2665 let (api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2666 let evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2667 let fork = evm_opts.resolve_fork().await.unwrap().unwrap();
2668 let original_instance = fork.context().instance_id;
2669
2670 api.anvil_reset(None).await.unwrap();
2671
2672 assert_ne!(api.instance_id(), original_instance.unwrap());
2673 let error = evm_opts
2674 .env_with_resolved_fork::<SpecId, BlockEnv, TxEnv>(Some(&fork))
2675 .await
2676 .unwrap_err();
2677 assert!(
2678 error
2679 .to_string()
2680 .contains("changed after its block and execution context were resolved"),
2681 "{error}"
2682 );
2683 }
2684
2685 #[test]
2686 #[cfg(feature = "monad")]
2687 fn unknown_endpoint_hardfork_is_optional_only_for_remote_execution() {
2688 assert_eq!(NetworkVariant::from_node_info_name("monad").unwrap(), NetworkVariant::Monad);
2689 assert_eq!(
2690 endpoint_hardfork(
2691 NetworkVariant::Monad,
2692 "MonadFuture",
2693 EndpointHardforkPolicy::Optional
2694 )
2695 .unwrap(),
2696 None
2697 );
2698
2699 let error = endpoint_hardfork(
2700 NetworkVariant::Monad,
2701 "MonadFuture",
2702 EndpointHardforkPolicy::Required,
2703 )
2704 .unwrap_err();
2705 assert!(
2706 error.to_string().contains("unsupported hardfork `MonadFuture` reported for `monad`")
2707 );
2708 assert_eq!(
2709 endpoint_hardfork(NetworkVariant::Monad, "MonadNine", EndpointHardforkPolicy::Required)
2710 .unwrap(),
2711 Some(FoundryHardfork::Monad(foundry_evm_hardforks::MonadHardfork::MonadNine))
2712 );
2713 }
2714
2715 #[test]
2716 #[cfg(feature = "base")]
2717 fn unknown_base_endpoint_hardfork_is_optional_only_for_remote_execution() {
2718 assert_eq!(NetworkVariant::from_node_info_name("base").unwrap(), NetworkVariant::Base);
2719 assert_eq!(
2720 endpoint_hardfork(NetworkVariant::Base, "BaseFuture", EndpointHardforkPolicy::Optional)
2721 .unwrap(),
2722 None
2723 );
2724
2725 let error =
2726 endpoint_hardfork(NetworkVariant::Base, "BaseFuture", EndpointHardforkPolicy::Required)
2727 .unwrap_err();
2728 assert!(
2729 error.to_string().contains("unsupported hardfork `BaseFuture` reported for `base`")
2730 );
2731 assert_eq!(
2732 endpoint_hardfork(NetworkVariant::Base, "Beryl", EndpointHardforkPolicy::Required)
2733 .unwrap(),
2734 Some(FoundryHardfork::Base(BaseUpgrade::Beryl))
2735 );
2736 }
2737
2738 #[tokio::test(flavor = "multi_thread")]
2739 #[cfg(feature = "monad")]
2740 async fn fork_network_detects_monad_anvil() {
2741 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test_monad()).await;
2742 let evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2743
2744 assert_eq!(
2745 evm_opts.fork_network().await.unwrap(),
2746 (NamedChain::AnvilHardhat as u64, NetworkVariant::Monad)
2747 );
2748 }
2749
2750 #[tokio::test(flavor = "multi_thread")]
2751 #[cfg(feature = "monad")]
2752 async fn infer_network_monad_propagates_unavailable_rpc() {
2753 let config = Config::figment();
2754 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2755 evm_opts.fork_url = Some("http://127.0.0.1:1".to_string());
2756 evm_opts.networks = NetworkConfigs::with_monad();
2757
2758 let error = evm_opts.infer_network_from_fork().await.unwrap_err();
2759
2760 assert!(error.to_string().contains("failed to retrieve chain ID"));
2761 assert!(evm_opts.networks.is_monad());
2762 }
2763
2764 #[tokio::test(flavor = "multi_thread")]
2765 #[cfg(feature = "base")]
2766 async fn infer_network_base_propagates_unavailable_rpc() {
2767 let mut evm_opts = EvmOpts {
2768 fork_url: Some("http://127.0.0.1:1".to_string()),
2769 networks: NetworkConfigs::with_base(),
2770 ..Default::default()
2771 };
2772
2773 let error = evm_opts.infer_network_from_fork().await.unwrap_err();
2774
2775 assert!(error.to_string().contains("failed to retrieve chain ID"));
2776 assert!(evm_opts.networks.is_base());
2777 assert_eq!(evm_opts.fork_endpoint, None);
2778 }
2779
2780 #[tokio::test(flavor = "multi_thread")]
2781 async fn flaky_infer_network_tempo_moderato_rpc() {
2782 let config = Config::figment();
2783 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2784 evm_opts.fork_url = Some("https://rpc.moderato.tempo.xyz".to_string());
2785 assert_eq!(evm_opts.networks, NetworkConfigs::default());
2786
2787 evm_opts.infer_network_from_fork().await.unwrap();
2788
2789 assert!(evm_opts.networks.is_tempo(), "should detect tempo from Moderato chain ID");
2791 }
2792
2793 #[tokio::test(flavor = "multi_thread")]
2794 async fn get_fork_pins_block_number_from_env() {
2795 let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint();
2796
2797 let config = Config::figment();
2798 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2799 evm_opts.fork_url = Some(endpoint.clone());
2800 assert!(evm_opts.fork_block_number.is_none());
2802
2803 let (evm_env, _, fork_block) = evm_opts.env::<SpecId, BlockEnv, TxEnv>().await.unwrap();
2805 assert!(fork_block.is_some(), "should have resolved a fork block number");
2806 let resolved_block = fork_block.unwrap();
2807 assert!(resolved_block > 0, "should have resolved to a real block number");
2808
2809 let fork =
2811 evm_opts.get_fork(&Config::default(), evm_env.cfg_env.chain_id, fork_block).unwrap();
2812
2813 assert_eq!(
2815 fork.evm_opts.fork_block_number,
2816 Some(resolved_block),
2817 "get_fork should pin fork_block_number to the block from env"
2818 );
2819 assert!(fork.evm_opts.fork_block_number_is_inferred);
2820 }
2821
2822 #[tokio::test(flavor = "multi_thread")]
2823 async fn fork_backend_rejects_changed_resolved_context() {
2824 let (_api, handle) = anvil::spawn(anvil::NodeConfig::test()).await;
2825 let mut evm_opts = EvmOpts { fork_url: Some(handle.http_endpoint()), ..Default::default() };
2826 evm_opts.infer_network_from_fork().await.unwrap();
2827 let (_, _, resolved) = evm_opts.env_resolved::<SpecId, BlockEnv, TxEnv>().await.unwrap();
2828 let resolved = resolved.unwrap();
2829 let mut context = resolved.context();
2830 let mut invalid_instance = context.instance_id.unwrap_or_default();
2831 invalid_instance[31] ^= 1;
2832 context.instance_id = Some(invalid_instance);
2833 let invalid = ResolvedFork::new(
2834 evm_opts.fork_url.as_deref().unwrap(),
2835 evm_opts.fork_source_headers(),
2836 evm_opts.rpc_jwt.as_deref(),
2837 evm_opts.fork_block_number,
2838 resolved.block(),
2839 context,
2840 );
2841 let fork = evm_opts
2842 .get_fork_resolved(&Config::default(), context.execution_chain_id, Some(&invalid))
2843 .unwrap();
2844
2845 let error =
2846 crate::backend::Backend::<crate::evm::EthEvmNetwork>::spawn(Some(fork)).unwrap_err();
2847 assert!(
2848 error
2849 .to_string()
2850 .contains("changed after its block and execution context were resolved"),
2851 "{error}"
2852 );
2853 }
2854
2855 #[tokio::test(flavor = "multi_thread")]
2860 async fn flaky_get_fork_uses_l2_block_number_on_arbitrum() {
2861 let endpoint =
2862 foundry_test_utils::rpc::next_rpc_endpoint(foundry_config::NamedChain::Arbitrum);
2863
2864 let config = Config::figment();
2865 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2866 evm_opts.fork_url = Some(endpoint.clone());
2867 assert!(evm_opts.fork_block_number.is_none());
2868
2869 let (evm_env, _, fork_block) = evm_opts.env::<SpecId, BlockEnv, TxEnv>().await.unwrap();
2870 let fork_block = fork_block.expect("should have resolved a fork block number");
2871
2872 let block_env_number: u64 = evm_env.block_env.number.to();
2875 assert!(
2876 fork_block > block_env_number,
2877 "fork_block ({fork_block}) should be the L2 block, which is larger than \
2878 block_env.number ({block_env_number}) which is the L1 block on Arbitrum"
2879 );
2880
2881 let fork = evm_opts
2883 .get_fork(&Config::default(), evm_env.cfg_env.chain_id, Some(fork_block))
2884 .unwrap();
2885 assert_eq!(
2886 fork.evm_opts.fork_block_number,
2887 Some(fork_block),
2888 "get_fork should pin to the L2 block number, not the L1 block number"
2889 );
2890 }
2891
2892 #[tokio::test(flavor = "multi_thread")]
2893 async fn get_fork_preserves_explicit_block_number() {
2894 let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint();
2895
2896 let config = Config::figment();
2897 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
2898 evm_opts.fork_url = Some(endpoint.clone());
2899 evm_opts.fork_block_number = Some(12345678);
2901
2902 let (evm_env, _, fork_block) = evm_opts.env::<SpecId, BlockEnv, TxEnv>().await.unwrap();
2903
2904 let fork =
2905 evm_opts.get_fork(&Config::default(), evm_env.cfg_env.chain_id, fork_block).unwrap();
2906
2907 assert_eq!(
2909 fork.evm_opts.fork_block_number,
2910 Some(12345678),
2911 "get_fork should preserve explicitly set fork_block_number"
2912 );
2913 assert!(!fork.evm_opts.fork_block_number_is_inferred);
2914 }
2915}