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