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