1use crate::celo::transfer::{CELO_TRANSFER_ADDRESS, PRECOMPILE_ID_CELO_TRANSFER};
16use alloy_chains::{
17 Chain, NamedChain,
18 NamedChain::{Chiado, Gnosis, Moonbase, Moonbeam, MoonbeamDev, Moonriver, Rsk, RskTestnet},
19};
20use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams};
21use alloy_evm::precompiles::{DynPrecompile, PrecompilesMap};
22use alloy_primitives::{Address, ChainId, address, map::AddressHashMap};
23use clap::Parser;
24use foundry_evm_hardforks::{
25 EthereumHardfork, ExecutionSpec, FoundryHardfork, TempoHardfork, latest_active_tempo_hardfork,
26};
27use revm::precompile::{
28 Precompile as RevmPrecompile,
29 secp256r1::{P256VERIFY, P256VERIFY_OSAKA},
30};
31use serde::{Deserialize, Serialize};
32use std::collections::BTreeMap;
33use tempo_contracts::precompiles::{
34 ACCOUNT_KEYCHAIN_ADDRESS, ADDRESS_REGISTRY_ADDRESS, CURRENT_COMMITTEE_ADDRESS,
35 NONCE_PRECOMPILE_ADDRESS, RECEIVE_POLICY_GUARD_ADDRESS, SIGNATURE_VERIFIER_ADDRESS,
36 STABLECOIN_DEX_ADDRESS, STORAGE_CREDITS_ADDRESS, TIP_FEE_MANAGER_ADDRESS,
37 TIP20_CHANNEL_RESERVE_ADDRESS, TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS,
38 VALIDATOR_CONFIG_ADDRESS, VALIDATOR_CONFIG_V2_ADDRESS,
39};
40
41#[cfg(feature = "base")]
42use base_common_precompiles::{
43 ActivationRegistryStorage, B20FactoryStorage, NonceManagerStorage, PolicyRegistryStorage,
44 TxContextStorage,
45};
46#[cfg(feature = "base")]
47use foundry_evm_hardforks::BaseUpgrade;
48
49#[cfg(feature = "optimism")]
50use foundry_evm_hardforks::OpHardfork;
51
52#[cfg(feature = "monad")]
53use foundry_evm_hardforks::MonadHardfork;
54
55pub const MONAD_CHEATCODE_ADDRESS: Address = address!("0xc0FFeeCD43A10e1C2b0De63c6CDCFe5B7d0e0CEA");
57
58pub mod arbitrum;
59pub mod celo;
60
61#[cfg(feature = "optimism")]
62mod optimism;
63
64pub const TEMPO_PRECOMPILES: &[(&str, Address)] = &[
66 ("Nonce", NONCE_PRECOMPILE_ADDRESS),
67 ("StablecoinDex", STABLECOIN_DEX_ADDRESS),
68 ("TIP20Factory", TIP20_FACTORY_ADDRESS),
69 ("TIP403Registry", TIP403_REGISTRY_ADDRESS),
70 ("FeeManager", TIP_FEE_MANAGER_ADDRESS),
71 ("ValidatorConfig", VALIDATOR_CONFIG_ADDRESS),
72 ("ValidatorConfigV2", VALIDATOR_CONFIG_V2_ADDRESS),
73 ("AccountKeychain", ACCOUNT_KEYCHAIN_ADDRESS),
74 ("SignatureVerifier", SIGNATURE_VERIFIER_ADDRESS),
75 ("AddressRegistry", ADDRESS_REGISTRY_ADDRESS),
76 ("TIP20ChannelReserve", TIP20_CHANNEL_RESERVE_ADDRESS),
77 ("ReceivePolicyGuard", RECEIVE_POLICY_GUARD_ADDRESS),
78 ("StorageCredits", STORAGE_CREDITS_ADDRESS),
79 ("CurrentCommittee", CURRENT_COMMITTEE_ADDRESS),
80];
81
82#[cfg(feature = "monad")]
83const MONAD_PRECOMPILE_LABELS: &[(&str, Address)] = &[
84 ("Staking", monad_revm::staking::STAKING_ADDRESS),
85 ("ReserveBalance", monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS),
86];
87
88#[cfg(feature = "monad")]
89const MONAD_PRECOMPILES: &[(&str, Address)] = &[
90 ("MonadStaking", monad_revm::staking::STAKING_ADDRESS),
91 ("MonadReserveBalance", monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS),
92];
93
94#[cfg(feature = "base")]
95const BASE_PRECOMPILES: &[(&str, Address)] = &[
96 ("B20Factory", B20FactoryStorage::ADDRESS),
97 ("ActivationRegistry", ActivationRegistryStorage::ADDRESS),
98 ("PolicyRegistry", PolicyRegistryStorage::ADDRESS),
99 ("TxContext", TxContextStorage::ADDRESS),
100 ("NonceManager", NonceManagerStorage::ADDRESS),
101];
102
103#[cfg(feature = "base")]
115pub const BASE_CODE_SENTINEL_ADDRESSES: &[Address] =
116 &[ActivationRegistryStorage::ADDRESS, PolicyRegistryStorage::ADDRESS];
117
118const BSC_P256_ADDRESS: Address = address!("0000000000000000000000000000000000000100");
120
121const BSC_MAINNET_CHAIN_ID: u64 = 56;
122const BSC_TESTNET_CHAIN_ID: u64 = 97;
123const BSC_MAINNET_HABER_TIMESTAMP: u64 = 1_718_863_500;
124const BSC_TESTNET_HABER_TIMESTAMP: u64 = 1_716_962_820;
125const BSC_MAINNET_OSAKA_TIMESTAMP: u64 = 1_777_343_400;
126const BSC_TESTNET_OSAKA_TIMESTAMP: u64 = 1_774_319_400;
127
128pub const TEMPO_PRECOMPILE_ADDRESSES: &[Address] = &{
130 let mut addresses = [Address::ZERO; TEMPO_PRECOMPILES.len()];
131 let mut i = 0;
132 while i < addresses.len() {
133 addresses[i] = TEMPO_PRECOMPILES[i].1;
134 i += 1;
135 }
136 addresses
137};
138
139#[derive(
140 Clone,
141 Copy,
142 Debug,
143 Default,
144 PartialEq,
145 Eq,
146 PartialOrd,
147 Ord,
148 Hash,
149 Serialize,
150 Deserialize,
151 clap::ValueEnum,
152)]
153#[serde(rename_all = "lowercase")]
154#[clap(rename_all = "lowercase")]
155pub enum NetworkVariant {
156 #[default]
157 Ethereum,
158 #[cfg(feature = "base")]
159 Base,
160 #[cfg(feature = "optimism")]
161 Optimism,
162 Tempo,
163 #[cfg(feature = "monad")]
164 Monad,
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169pub struct NetworkContractSizeLimits {
170 pub runtime: usize,
172 pub initcode: usize,
174}
175
176impl std::str::FromStr for NetworkVariant {
177 type Err = String;
178
179 fn from_str(s: &str) -> Result<Self, Self::Err> {
180 match s {
181 "ethereum" => Ok(Self::Ethereum),
182 #[cfg(feature = "base")]
183 "base" => Ok(Self::Base),
184 #[cfg(feature = "optimism")]
185 "optimism" => Ok(Self::Optimism),
186 "tempo" => Ok(Self::Tempo),
187 #[cfg(feature = "monad")]
188 "monad" => Ok(Self::Monad),
189 _ => Err(format!("unknown network variant: {s}")),
190 }
191 }
192}
193
194impl NetworkVariant {
195 pub fn from_known_chain_id(chain_id: ChainId) -> Result<Option<Self>, String> {
201 let chain = Chain::from_id(chain_id);
202 if chain.is_tempo() {
203 return Ok(Some(Self::Tempo));
204 }
205 if matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) {
206 return Ok(Some(Self::Ethereum));
207 }
208 #[cfg(feature = "base")]
213 if matches!(chain.named(), Some(NamedChain::Base | NamedChain::BaseSepolia)) {
214 return Ok(Some(Self::Base));
215 }
216 if matches!(chain.named(), Some(NamedChain::Monad | NamedChain::MonadTestnet)) {
217 #[cfg(feature = "monad")]
218 return Ok(Some(Self::Monad));
219 #[cfg(not(feature = "monad"))]
220 return Err("network family `monad` is not enabled in this build".to_string());
221 }
222 if chain.is_optimism() {
223 #[cfg(feature = "optimism")]
224 return Ok(Some(Self::Optimism));
225 #[cfg(not(feature = "optimism"))]
226 return Err("network family `optimism` is not enabled in this build".to_string());
227 }
228 Ok(chain.named().map(|_| Self::Ethereum))
229 }
230
231 pub fn from_node_info_name(network: &str) -> Result<Self, String> {
233 match network {
234 "ethereum" => Ok(Self::Ethereum),
235 #[cfg(feature = "base")]
236 "base" => Ok(Self::Base),
237 #[cfg(not(feature = "base"))]
238 "base" => Err("network family `base` is not enabled in this build".to_string()),
239 #[cfg(feature = "optimism")]
240 "optimism" => Ok(Self::Optimism),
241 #[cfg(not(feature = "optimism"))]
242 "optimism" => Err("network family `optimism` is not enabled in this build".to_string()),
243 "tempo" => Ok(Self::Tempo),
244 #[cfg(feature = "monad")]
245 "monad" => Ok(Self::Monad),
246 #[cfg(not(feature = "monad"))]
247 "monad" => Err("network family `monad` is not enabled in this build".to_string()),
248 network => {
249 Err(format!("unsupported network family `{network}` reported by fork endpoint"))
250 }
251 }
252 }
253
254 pub fn parse_hardfork(self, hardfork: &str) -> Result<FoundryHardfork, String> {
256 format!("{}:{hardfork}", self.name()).parse()
257 }
258
259 pub fn historical_hardfork(self, chain_id: ChainId, timestamp: u64) -> Option<FoundryHardfork> {
264 match self {
265 Self::Ethereum => {
266 EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
267 .map(Into::into)
268 }
269 Self::Tempo => {
270 TempoHardfork::from_chain_and_timestamp(chain_id, timestamp).map(Into::into)
271 }
272 #[cfg(feature = "optimism")]
273 Self::Optimism => {
274 OpHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
275 .map(Into::into)
276 }
277 #[cfg(feature = "monad")]
278 Self::Monad => {
279 MonadHardfork::from_chain_and_timestamp(chain_id, timestamp).map(Into::into)
280 }
281 #[cfg(feature = "base")]
282 Self::Base => {
283 BaseUpgrade::from_chain_and_timestamp(chain_id, timestamp).map(Into::into)
284 }
285 }
286 }
287
288 pub fn hardfork_at(self, chain_id: ChainId, timestamp: u64) -> FoundryHardfork {
293 self.historical_hardfork(chain_id, timestamp).unwrap_or_else(|| match self {
294 Self::Ethereum => EthereumHardfork::default().into(),
295 Self::Tempo => latest_active_tempo_hardfork().into(),
296 #[cfg(feature = "optimism")]
297 Self::Optimism => OpHardfork::default().into(),
298 #[cfg(feature = "monad")]
299 Self::Monad => MonadHardfork::default().into(),
300 #[cfg(feature = "base")]
301 Self::Base => BaseUpgrade::default().into(),
302 })
303 }
304
305 pub const fn is_ethereum(&self) -> bool {
307 matches!(self, Self::Ethereum)
308 }
309
310 #[cfg(feature = "base")]
312 pub const fn is_base(&self) -> bool {
313 matches!(self, Self::Base)
314 }
315
316 pub const fn is_optimism(&self) -> bool {
318 #[cfg(feature = "optimism")]
319 {
320 matches!(self, Self::Optimism)
321 }
322 #[cfg(not(feature = "optimism"))]
323 {
324 false
325 }
326 }
327
328 pub const fn is_tempo(&self) -> bool {
330 matches!(self, Self::Tempo)
331 }
332
333 #[cfg(feature = "monad")]
335 pub const fn is_monad(&self) -> bool {
336 matches!(self, Self::Monad)
337 }
338
339 #[cfg(not(feature = "monad"))]
341 pub const fn is_monad(&self) -> bool {
342 false
343 }
344
345 pub const fn name(&self) -> &'static str {
347 match self {
348 Self::Ethereum => "ethereum",
349 #[cfg(feature = "base")]
350 Self::Base => "base",
351 #[cfg(feature = "optimism")]
352 Self::Optimism => "optimism",
353 Self::Tempo => "tempo",
354 #[cfg(feature = "monad")]
355 Self::Monad => "monad",
356 }
357 }
358
359 pub const fn hardfork_namespace(&self) -> Option<&'static str> {
361 match self {
362 Self::Ethereum => None,
363 #[cfg(feature = "base")]
364 Self::Base => Some("base"),
365 #[cfg(feature = "optimism")]
366 Self::Optimism => Some("optimism"),
367 Self::Tempo => Some("tempo"),
368 #[cfg(feature = "monad")]
369 Self::Monad => Some("monad"),
370 }
371 }
372}
373
374impl std::fmt::Display for NetworkVariant {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 f.write_str(self.name())
377 }
378}
379
380impl From<ChainId> for NetworkVariant {
381 fn from(chain_id: ChainId) -> Self {
382 Self::from_known_chain_id(chain_id).ok().flatten().unwrap_or(Self::Ethereum)
383 }
384}
385
386#[derive(Clone, Debug, Default, Parser, Deserialize, Copy, PartialEq, Eq, Hash)]
387pub struct NetworkConfigs {
388 #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "tempo"])]
390 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
391 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
392 #[serde(default)]
393 pub(crate) network: Option<NetworkVariant>,
394 #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "tempo"])]
396 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
397 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
398 celo: bool,
399 #[cfg(feature = "optimism")]
401 #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])]
402 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
403 #[serde(default)]
406 pub(crate) optimism: bool,
407 #[arg(long, hide = true, conflicts_with_all = ["network", "celo"])]
409 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
410 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
411 #[serde(default)]
414 tempo: bool,
415 #[cfg(feature = "monad")]
417 #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])]
418 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
419 #[serde(default)]
422 monad: bool,
423 #[arg(skip)]
425 #[serde(default)]
426 bypass_prevrandao: bool,
427}
428
429impl Serialize for NetworkConfigs {
433 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
434 use serde::ser::SerializeStruct;
435 let mut s = serializer.serialize_struct("NetworkConfigs", 3)?;
436 s.serialize_field("network", &self.resolved_network())?;
437 s.serialize_field("celo", &self.celo)?;
438 s.serialize_field("bypass_prevrandao", &self.bypass_prevrandao)?;
439 s.end()
440 }
441}
442
443impl NetworkConfigs {
444 pub fn validate(&self) -> Result<(), String> {
449 let mut selectors = Vec::new();
450 if let Some(network) = self.network {
451 selectors.push((network.name(), format!("network = \"{}\"", network.name())));
452 }
453 if self.celo {
454 selectors.push(("celo", "celo = true".to_string()));
455 }
456 #[cfg(feature = "optimism")]
457 if self.optimism {
458 selectors.push(("optimism", "optimism = true".to_string()));
459 }
460 if self.tempo {
461 selectors.push(("tempo", "tempo = true".to_string()));
462 }
463 #[cfg(feature = "monad")]
464 if self.monad {
465 selectors.push(("monad", "monad = true".to_string()));
466 }
467
468 if let Some((family, selector)) = selectors.first()
469 && let Some((_, conflicting)) =
470 selectors.iter().find(|(candidate, _)| candidate != family)
471 {
472 return Err(format!(
473 "network selectors `{selector}` and `{conflicting}` conflict; select only one \
474 network"
475 ));
476 }
477
478 Ok(())
479 }
480
481 pub fn with_ethereum() -> Self {
482 Self { network: Some(NetworkVariant::Ethereum), ..Default::default() }
483 }
484
485 pub fn with_celo() -> Self {
486 Self { celo: true, ..Default::default() }
487 }
488
489 pub fn with_tempo() -> Self {
490 Self { network: Some(NetworkVariant::Tempo), tempo: true, ..Default::default() }
491 }
492
493 #[cfg(feature = "monad")]
494 pub fn with_monad() -> Self {
495 Self { network: Some(NetworkVariant::Monad), monad: true, ..Default::default() }
496 }
497
498 #[cfg(feature = "base")]
499 pub fn with_base() -> Self {
500 Self { network: Some(NetworkVariant::Base), ..Default::default() }
501 }
502
503 pub const fn is_tempo(&self) -> bool {
504 if let Some(network) = self.resolved_network() { network.is_tempo() } else { false }
505 }
506
507 pub const fn is_optimism(&self) -> bool {
511 if let Some(network) = self.resolved_network() { network.is_optimism() } else { false }
512 }
513
514 #[cfg(feature = "monad")]
515 pub const fn is_monad(&self) -> bool {
516 if let Some(network) = self.resolved_network() { network.is_monad() } else { false }
517 }
518
519 #[cfg(not(feature = "monad"))]
520 pub const fn is_monad(&self) -> bool {
521 false
522 }
523
524 #[cfg(feature = "base")]
525 pub const fn is_base(&self) -> bool {
526 matches!(self.resolved_network(), Some(NetworkVariant::Base))
527 }
528
529 pub fn executed_hardfork(&self, hardfork: FoundryHardfork) -> FoundryHardfork {
536 if self.is_tempo() {
537 return TempoHardfork::from(hardfork).into();
538 }
539 #[cfg(feature = "monad")]
540 if self.is_monad() {
541 return MonadHardfork::from(hardfork).into();
542 }
543 #[cfg(feature = "base")]
544 if self.is_base() {
545 return BaseUpgrade::from(hardfork).into();
546 }
547 hardfork
548 }
549
550 pub const fn is_celo(&self) -> bool {
551 self.celo
552 }
553
554 pub const fn resolved_network(&self) -> Option<NetworkVariant> {
556 if let Some(n) = self.network {
557 return Some(n);
558 }
559 #[cfg(feature = "optimism")]
560 if self.optimism {
561 return Some(NetworkVariant::Optimism);
562 }
563 if self.tempo {
564 return Some(NetworkVariant::Tempo);
565 }
566 #[cfg(feature = "monad")]
567 if self.monad {
568 return Some(NetworkVariant::Monad);
569 }
570 None
571 }
572
573 pub const fn has_network_selection(&self) -> bool {
575 self.celo || self.resolved_network().is_some()
576 }
577
578 pub const fn execution_family_name(&self) -> &'static str {
580 self.execution_network().name()
581 }
582
583 pub const fn execution_profile_name(&self) -> &'static str {
585 if self.celo { "celo" } else { self.execution_family_name() }
586 }
587
588 pub const fn execution_network(&self) -> NetworkVariant {
590 if let Some(network) = self.resolved_network() { network } else { NetworkVariant::Ethereum }
591 }
592
593 pub fn has_same_execution_profile(&self, other: &Self) -> bool {
595 self.celo == other.celo
596 && match (self.resolved_network(), other.resolved_network()) {
597 (Some(left), Some(right)) => left == right,
598 (Some(left), None) => left.is_ethereum(),
599 (None, Some(right)) => right.is_ethereum(),
600 (None, None) => true,
601 }
602 }
603
604 pub const fn supports_fork_source(&self, source: &Self) -> bool {
612 match self.execution_network() {
613 #[cfg(feature = "base")]
614 NetworkVariant::Base => source.is_base(),
615 #[cfg(feature = "optimism")]
616 NetworkVariant::Optimism => match source.execution_network() {
617 NetworkVariant::Optimism => true,
618 #[cfg(feature = "base")]
619 NetworkVariant::Base => true,
620 _ => false,
621 },
622 #[cfg(feature = "monad")]
623 NetworkVariant::Monad => source.is_monad(),
624 _ => !source.is_monad(),
625 }
626 }
627
628 pub fn active_network_name(&self) -> Option<&'static str> {
630 self.resolved_network().and_then(|network| network.hardfork_namespace())
631 }
632
633 #[cfg_attr(
638 not(any(feature = "base", feature = "optimism")),
639 allow(clippy::missing_const_for_fn)
640 )]
641 pub fn base_fee_params(
642 &self,
643 #[cfg_attr(not(any(feature = "base", feature = "optimism")), allow(unused_variables))]
644 timestamp: u64,
645 ) -> BaseFeeParams {
646 #[cfg(feature = "base")]
647 if self.is_base() {
648 let canyon_active =
649 BaseUpgrade::from_chain_and_timestamp(NamedChain::Base as u64, timestamp)
650 .is_some_and(|upgrade| upgrade >= BaseUpgrade::Canyon);
651 return if canyon_active {
652 BaseFeeParams::new(250, 6)
653 } else {
654 BaseFeeParams::new(50, 6)
655 };
656 }
657 #[cfg(feature = "optimism")]
658 if self.is_optimism() {
659 return self.op_base_fee_params(timestamp);
660 }
661 BaseFeeParams::ethereum()
662 }
663
664 pub fn next_block_blob_excess_gas(
670 &self,
671 blob_params: BlobParams,
672 parent_excess_blob_gas: u64,
673 parent_blob_gas_used: u64,
674 parent_base_fee: u64,
675 ) -> u64 {
676 #[cfg(feature = "base")]
677 if self.is_base() {
678 return 0;
679 }
680 if self.is_optimism() {
681 return 0;
682 }
683 blob_params.next_block_excess_blob_gas_osaka(
684 parent_excess_blob_gas,
685 parent_blob_gas_used,
686 parent_base_fee,
687 )
688 }
689
690 #[cfg(feature = "monad")]
692 pub fn contract_size_limits(&self) -> Option<NetworkContractSizeLimits> {
693 self.is_monad().then_some(NetworkContractSizeLimits {
694 runtime: monad_revm::MONAD_MAX_CODE_SIZE,
695 initcode: monad_revm::MONAD_MAX_INITCODE_SIZE,
696 })
697 }
698
699 #[cfg(not(feature = "monad"))]
701 pub const fn contract_size_limits(&self) -> Option<NetworkContractSizeLimits> {
702 None
703 }
704
705 pub fn bypass_prevrandao(&self, chain_id: u64) -> bool {
706 if let Ok(
707 Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet | Gnosis | Chiado,
708 ) = NamedChain::try_from(chain_id)
709 {
710 return true;
711 }
712 self.bypass_prevrandao
713 }
714
715 pub fn try_with_chain_id(self, chain_id: u64) -> Result<Self, String> {
721 if self.has_network_selection() {
722 return Ok(self);
723 }
724
725 match NetworkVariant::from_known_chain_id(chain_id).map_err(|error| {
726 format!("cannot infer execution network from chain ID {chain_id}: {error}")
727 })? {
728 Some(network) => Ok(self.with_rpc_identity(network, chain_id)),
729 None => Ok(self),
730 }
731 }
732
733 pub fn with_chain_id(self, chain_id: u64) -> Self {
738 self.try_with_chain_id(chain_id).unwrap_or(self)
739 }
740
741 pub fn with_rpc_network(self, network: NetworkVariant) -> Self {
746 let mut resolved = if network.is_ethereum() { Self::default() } else { network.into() };
747 resolved.bypass_prevrandao = self.bypass_prevrandao;
748 resolved
749 }
750
751 pub fn with_rpc_identity(self, network: NetworkVariant, chain_id: ChainId) -> Self {
757 let mut resolved = self.with_rpc_network(network);
758 if network.is_ethereum()
759 && matches!(
760 Chain::from_id(chain_id).named(),
761 Some(NamedChain::Celo | NamedChain::CeloSepolia)
762 )
763 {
764 resolved.celo = true;
765 }
766 resolved
767 }
768
769 pub fn canonical_execution_profile(self) -> Self {
771 if self.is_celo() {
772 Self::with_celo()
773 } else if let Some(network) = self.resolved_network()
774 && !network.is_ethereum()
775 {
776 network.into()
777 } else {
778 Self::default()
779 }
780 }
781
782 pub fn with_execution_profile(self, profile: Self) -> Self {
784 let mut resolved = if profile.is_celo() {
785 Self::with_celo()
786 } else {
787 profile.resolved_network().map(Into::into).unwrap_or_default()
788 };
789 resolved.bypass_prevrandao = self.bypass_prevrandao;
790 resolved
791 }
792
793 pub fn with_rpc_profile(self, profile: Self) -> Self {
795 self.with_execution_profile(profile.canonical_execution_profile())
796 }
797
798 pub fn from_node_info_profile(profile: &str) -> Result<Self, String> {
800 if profile == "celo" {
801 return Ok(Self::with_celo());
802 }
803 let network = NetworkVariant::from_node_info_name(profile)?;
804 Ok(if network.is_ethereum() { Self::default() } else { network.into() })
805 }
806
807 pub fn from_rpc_identity_profile_with_fallback(
814 chain_id: ChainId,
815 node_info_profile: Option<Option<&str>>,
816 unknown_fallback: Option<Self>,
817 ) -> Result<Option<Self>, String> {
818 let known_profile = || {
819 NetworkVariant::from_known_chain_id(chain_id).map(|network| {
820 network.map(|network| Self::default().with_rpc_identity(network, chain_id))
821 })
822 };
823 let fallback_is_explicit =
824 unknown_fallback.is_some_and(|fallback| fallback.has_network_selection());
825 let fallback = unknown_fallback.map(Self::canonical_execution_profile);
826 if let Some(Some(profile)) = node_info_profile {
827 return Self::from_node_info_profile(profile).map(Some);
828 }
829 if fallback_is_explicit && let Some(fallback) = fallback {
830 return Ok(Some(fallback));
831 }
832 match node_info_profile {
833 Some(None) => Ok(known_profile()?.or(fallback).or(Some(Self::default()))),
834 None => Ok(known_profile()?.or(fallback)),
835 Some(Some(_)) => unreachable!(),
836 }
837 }
838
839 pub fn normalize_for_hardfork(self, hardfork: FoundryHardfork) -> Result<Self, String> {
844 if self.has_network_selection() {
845 let configured = self.execution_network();
846 if configured.hardfork_namespace() != hardfork.namespace() {
847 return Err(format!(
848 "hardfork `{}` conflicts with network config `{}`",
849 String::from(hardfork),
850 self.execution_profile_name(),
851 ));
852 }
853 }
854
855 let network = match hardfork {
856 FoundryHardfork::Ethereum(_) => self,
857 FoundryHardfork::Tempo(_) => Self::with_tempo(),
858 #[cfg(feature = "base")]
859 FoundryHardfork::Base(_) => Self::with_base(),
860 #[cfg(feature = "optimism")]
861 FoundryHardfork::Optimism(_) => Self::with_optimism(),
862 #[cfg(feature = "monad")]
863 FoundryHardfork::Monad(_) => Self::with_monad(),
864 };
865
866 Ok(network)
867 }
868
869 pub fn inject_precompiles(self, precompiles: &mut PrecompilesMap) {
871 if self.is_celo() {
872 precompiles.apply_precompile(&CELO_TRANSFER_ADDRESS, move |_| {
873 Some(celo::transfer::precompile())
874 });
875 }
876 }
877
878 pub fn precompiles(self, hardfork: Option<FoundryHardfork>) -> BTreeMap<String, Address> {
880 let mut precompiles = BTreeMap::new();
881 if self.is_celo() {
882 precompiles
883 .insert(PRECOMPILE_ID_CELO_TRANSFER.name().to_string(), CELO_TRANSFER_ADDRESS);
884 }
885 if self.is_tempo() {
886 let tempo_hardfork = hardfork.and_then(TempoHardfork::from_foundry_hardfork);
887 precompiles.extend(
888 TEMPO_PRECOMPILES
889 .iter()
890 .copied()
891 .filter(|(_, address)| {
892 tempo_hardfork.is_none_or(|hardfork| {
893 is_tempo_precompile_active_at(*address, hardfork)
894 })
895 })
896 .map(|(label, address)| (label.to_string(), address)),
897 );
898 }
899 #[cfg(feature = "monad")]
900 if self.is_monad() {
901 let monad_hardfork = hardfork.and_then(MonadHardfork::from_foundry_hardfork);
902 precompiles.extend(
903 MONAD_PRECOMPILES
904 .iter()
905 .copied()
906 .filter(|(_, address)| {
907 monad_hardfork.is_none_or(|hardfork| {
908 is_monad_precompile_active_at(*address, hardfork)
909 })
910 })
911 .map(|(label, address)| (label.to_string(), address)),
912 );
913 }
914 #[cfg(feature = "base")]
915 if self.is_base() {
916 let base_upgrade = hardfork.and_then(|hardfork| match hardfork {
917 FoundryHardfork::Base(upgrade) => Some(upgrade),
918 _ => None,
919 });
920 precompiles.extend(
921 BASE_PRECOMPILES
922 .iter()
923 .copied()
924 .filter(|(_, address)| {
925 base_upgrade
926 .is_none_or(|upgrade| is_base_precompile_active_at(*address, upgrade))
927 })
928 .map(|(label, address)| (label.to_string(), address)),
929 );
930 }
931 precompiles
932 }
933}
934
935pub fn apply_bsc_p256_precompile(
937 precompiles: &mut PrecompilesMap,
938 chain_id: ChainId,
939 timestamp: u64,
940) {
941 let Some(p256verify) = bsc_p256_precompile(chain_id, timestamp) else { return };
942 precompiles.apply_precompile(&BSC_P256_ADDRESS, move |_| {
943 p256verify.map(|p256verify| {
944 DynPrecompile::new(p256verify.id().clone(), move |input| {
945 p256verify.execute(input.data, input.gas(), input.reservoir)
946 })
947 })
948 });
949}
950
951impl From<NetworkVariant> for NetworkConfigs {
952 fn from(network: NetworkVariant) -> Self {
953 match network {
954 NetworkVariant::Ethereum => Self::with_ethereum(),
955 NetworkVariant::Tempo => {
956 Self { network: Some(network), tempo: true, ..Default::default() }
957 }
958 #[cfg(feature = "monad")]
959 NetworkVariant::Monad => {
960 Self { network: Some(network), monad: true, ..Default::default() }
961 }
962 #[cfg(feature = "base")]
963 NetworkVariant::Base => Self { network: Some(network), ..Default::default() },
964 #[cfg(feature = "optimism")]
965 NetworkVariant::Optimism => {
966 Self { network: Some(network), optimism: true, ..Default::default() }
967 }
968 }
969 }
970}
971
972const fn bsc_p256_precompile(chain_id: ChainId, timestamp: u64) -> Option<Option<RevmPrecompile>> {
975 let (haber_timestamp, osaka_timestamp) = match chain_id {
976 BSC_MAINNET_CHAIN_ID => (BSC_MAINNET_HABER_TIMESTAMP, BSC_MAINNET_OSAKA_TIMESTAMP),
977 BSC_TESTNET_CHAIN_ID => (BSC_TESTNET_HABER_TIMESTAMP, BSC_TESTNET_OSAKA_TIMESTAMP),
978 _ => return None,
979 };
980
981 if timestamp < haber_timestamp {
982 Some(None)
983 } else if timestamp < osaka_timestamp {
984 Some(Some(P256VERIFY))
985 } else {
986 Some(Some(P256VERIFY_OSAKA))
987 }
988}
989
990pub fn resolved_precompile_labels(hardfork: Option<FoundryHardfork>) -> AddressHashMap<String> {
993 match hardfork {
994 Some(FoundryHardfork::Tempo(hardfork)) => TEMPO_PRECOMPILES
995 .iter()
996 .filter(|(_, address)| is_tempo_precompile_active_at(*address, hardfork))
997 .map(|(label, address)| (*address, (*label).to_string()))
998 .collect(),
999 #[cfg(feature = "monad")]
1000 Some(FoundryHardfork::Monad(hardfork)) => MONAD_PRECOMPILE_LABELS
1001 .iter()
1002 .filter(|(_, address)| is_monad_precompile_active_at(*address, hardfork))
1003 .map(|(label, address)| (*address, (*label).to_string()))
1004 .collect(),
1005 #[cfg(feature = "base")]
1006 Some(FoundryHardfork::Base(upgrade)) => BASE_PRECOMPILES
1007 .iter()
1008 .filter(|(_, address)| is_base_precompile_active_at(*address, upgrade))
1009 .map(|(label, address)| (*address, (*label).to_string()))
1010 .collect(),
1011 _ => AddressHashMap::default(),
1012 }
1013}
1014
1015pub fn is_tempo_precompile_active_at(address: Address, hardfork: TempoHardfork) -> bool {
1017 if address == CURRENT_COMMITTEE_ADDRESS {
1018 hardfork.is_t8()
1019 } else if address == TIP20_CHANNEL_RESERVE_ADDRESS {
1020 hardfork.is_t5()
1021 } else if address == RECEIVE_POLICY_GUARD_ADDRESS {
1022 hardfork.is_t6()
1023 } else if address == STORAGE_CREDITS_ADDRESS {
1024 hardfork.is_t7()
1025 } else if address == ADDRESS_REGISTRY_ADDRESS || address == SIGNATURE_VERIFIER_ADDRESS {
1026 hardfork.is_t3()
1027 } else {
1028 true
1029 }
1030}
1031
1032pub fn active_tempo_precompile_addresses(hardfork: TempoHardfork) -> impl Iterator<Item = Address> {
1034 TEMPO_PRECOMPILE_ADDRESSES
1035 .iter()
1036 .copied()
1037 .filter(move |&address| is_tempo_precompile_active_at(address, hardfork))
1038}
1039
1040#[cfg(feature = "monad")]
1042pub fn is_monad_precompile_active_at(address: Address, hardfork: MonadHardfork) -> bool {
1043 address == monad_revm::staking::STAKING_ADDRESS
1044 || (address == monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS
1045 && MonadHardfork::MonadNine.is_enabled_in(hardfork))
1046}
1047
1048#[cfg(feature = "base")]
1050pub fn is_base_precompile_active_at(address: Address, upgrade: BaseUpgrade) -> bool {
1051 if matches!(address, TxContextStorage::ADDRESS | NonceManagerStorage::ADDRESS) {
1052 upgrade >= BaseUpgrade::Cobalt
1053 } else if matches!(
1054 address,
1055 B20FactoryStorage::ADDRESS
1056 | ActivationRegistryStorage::ADDRESS
1057 | PolicyRegistryStorage::ADDRESS
1058 ) {
1059 upgrade >= BaseUpgrade::Beryl
1060 } else {
1061 false
1062 }
1063}
1064
1065#[cfg(feature = "base")]
1067pub fn active_base_precompiles(
1068 upgrade: BaseUpgrade,
1069) -> impl Iterator<Item = (&'static str, Address)> {
1070 BASE_PRECOMPILES
1071 .iter()
1072 .copied()
1073 .filter(move |(_, address)| is_base_precompile_active_at(*address, upgrade))
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use super::*;
1079 use revm::precompile::{
1080 Precompiles,
1081 secp256r1::{P256VERIFY_BASE_GAS_FEE, P256VERIFY_BASE_GAS_FEE_OSAKA},
1082 };
1083
1084 #[test]
1087 fn network_variant_predicates() {
1088 assert!(NetworkVariant::Ethereum.is_ethereum());
1089 assert!(!NetworkVariant::Ethereum.is_optimism());
1090 assert!(!NetworkVariant::Ethereum.is_tempo());
1091 assert!(NetworkVariant::Tempo.is_tempo());
1092 assert!(!NetworkVariant::Tempo.is_ethereum());
1093 assert!(!NetworkVariant::Tempo.is_optimism());
1094
1095 #[cfg(feature = "monad")]
1096 {
1097 assert!(!NetworkVariant::Ethereum.is_monad());
1098 assert!(!NetworkVariant::Tempo.is_monad());
1099 assert!(NetworkVariant::Monad.is_monad());
1100 assert!(!NetworkVariant::Monad.is_ethereum());
1101 assert!(!NetworkVariant::Monad.is_optimism());
1102 assert!(!NetworkVariant::Monad.is_tempo());
1103 }
1104
1105 #[cfg(feature = "optimism")]
1106 {
1107 assert!(NetworkVariant::Optimism.is_optimism());
1108 assert!(!NetworkVariant::Optimism.is_ethereum());
1109 assert!(!NetworkVariant::Optimism.is_tempo());
1110
1111 #[cfg(feature = "monad")]
1112 assert!(!NetworkVariant::Optimism.is_monad());
1113 }
1114
1115 #[cfg(feature = "base")]
1116 {
1117 assert!(NetworkVariant::Base.is_base());
1118 assert!(!NetworkVariant::Base.is_ethereum());
1119 assert!(!NetworkVariant::Base.is_optimism());
1120 assert!(!NetworkVariant::Base.is_tempo());
1121 }
1122 }
1123
1124 #[test]
1125 #[cfg(all(feature = "base", feature = "optimism"))]
1126 fn historical_hardfork_uses_selected_execution_family() {
1127 let timestamp = 1_710_374_401;
1128
1129 assert_eq!(
1130 NetworkVariant::Base.historical_hardfork(NamedChain::Base as u64, timestamp),
1131 Some(BaseUpgrade::Ecotone.into())
1132 );
1133 assert_eq!(
1134 NetworkVariant::Optimism.historical_hardfork(NamedChain::Base as u64, timestamp),
1135 Some(OpHardfork::Ecotone.into())
1136 );
1137 assert_eq!(
1138 NetworkVariant::Ethereum.historical_hardfork(NamedChain::Base as u64, timestamp),
1139 None
1140 );
1141 assert_eq!(
1142 NetworkVariant::Tempo.historical_hardfork(NamedChain::Base as u64, timestamp),
1143 None
1144 );
1145 }
1146
1147 #[test]
1148 #[cfg(feature = "monad")]
1149 fn fork_sources_only_isolate_monad() {
1150 let non_monad = vec![
1151 NetworkConfigs::default(),
1152 NetworkConfigs::with_ethereum(),
1153 NetworkConfigs::with_celo(),
1154 NetworkConfigs::with_tempo(),
1155 ];
1156
1157 for execution in &non_monad {
1158 for source in &non_monad {
1159 assert!(execution.supports_fork_source(source));
1160 }
1161 assert!(!execution.supports_fork_source(&NetworkConfigs::with_monad()));
1162 assert!(!NetworkConfigs::with_monad().supports_fork_source(execution));
1163 }
1164 assert!(NetworkConfigs::with_monad().supports_fork_source(&NetworkConfigs::with_monad()));
1165 }
1166
1167 #[test]
1168 #[cfg(feature = "optimism")]
1169 fn optimism_fork_sources_require_op_stack_state() {
1170 let optimism = NetworkConfigs::with_optimism();
1171 for source in [
1172 NetworkConfigs::default(),
1173 NetworkConfigs::with_ethereum(),
1174 NetworkConfigs::with_celo(),
1175 NetworkConfigs::with_tempo(),
1176 ] {
1177 assert!(!optimism.supports_fork_source(&source));
1178 assert!(source.supports_fork_source(&optimism));
1179 }
1180 assert!(optimism.supports_fork_source(&optimism));
1181 #[cfg(feature = "monad")]
1182 assert!(!optimism.supports_fork_source(&NetworkConfigs::with_monad()));
1183 }
1184
1185 #[test]
1186 #[cfg(feature = "base")]
1187 fn fork_sources_preserve_base_state_source_compatibility() {
1188 #[cfg_attr(not(feature = "optimism"), allow(unused_mut))]
1189 let mut non_base = vec![
1190 NetworkConfigs::default(),
1191 NetworkConfigs::with_ethereum(),
1192 NetworkConfigs::with_celo(),
1193 NetworkConfigs::with_tempo(),
1194 ];
1195 #[cfg(feature = "optimism")]
1196 non_base.push(NetworkConfigs::with_optimism());
1197 for execution in &non_base {
1198 assert!(execution.supports_fork_source(&NetworkConfigs::with_base()));
1199 assert!(!NetworkConfigs::with_base().supports_fork_source(execution));
1200 }
1201 assert!(NetworkConfigs::with_base().supports_fork_source(&NetworkConfigs::with_base()));
1202
1203 #[cfg(feature = "monad")]
1204 {
1205 assert!(
1206 !NetworkConfigs::with_monad().supports_fork_source(&NetworkConfigs::with_base())
1207 );
1208 assert!(
1209 !NetworkConfigs::with_base().supports_fork_source(&NetworkConfigs::with_monad())
1210 );
1211 }
1212 }
1213
1214 #[test]
1215 fn known_chain_identity_does_not_guess_unknown_networks() {
1216 assert_eq!(NetworkVariant::from_known_chain_id(98_765_432).unwrap(), None);
1217 assert_eq!(
1218 NetworkVariant::from_known_chain_id(NamedChain::Mainnet as u64).unwrap(),
1219 Some(NetworkVariant::Ethereum)
1220 );
1221 assert_eq!(NetworkVariant::from(98_765_432), NetworkVariant::Ethereum);
1222 }
1223
1224 #[test]
1225 fn fallible_chain_id_inference_preserves_unknown_chains() {
1226 let networks = NetworkConfigs { bypass_prevrandao: true, ..Default::default() };
1227 assert_eq!(networks.try_with_chain_id(98_765_432).unwrap(), networks);
1228 }
1229
1230 #[test]
1231 #[cfg(not(feature = "monad"))]
1232 fn fallible_chain_id_inference_rejects_disabled_monad() {
1233 for chain_id in [NamedChain::Monad as u64, NamedChain::MonadTestnet as u64] {
1234 let unavailable = "network family `monad` is not enabled in this build";
1235 let expected =
1236 format!("cannot infer execution network from chain ID {chain_id}: {unavailable}");
1237 assert_eq!(
1238 NetworkConfigs::default().try_with_chain_id(chain_id).unwrap_err(),
1239 expected
1240 );
1241 }
1242 }
1243
1244 #[test]
1245 #[cfg(not(feature = "optimism"))]
1246 fn fallible_chain_id_inference_rejects_disabled_optimism() {
1247 let chain_id = NamedChain::Optimism as u64;
1248 assert_eq!(
1249 NetworkConfigs::default().try_with_chain_id(chain_id).unwrap_err(),
1250 format!(
1251 "cannot infer execution network from chain ID {chain_id}: network family \
1252 `optimism` is not enabled in this build"
1253 )
1254 );
1255 }
1256
1257 #[test]
1258 fn explicit_ethereum_overrides_known_chain_inference() {
1259 let ethereum = NetworkConfigs::with_ethereum();
1260 for chain_id in [NamedChain::Monad as u64, NamedChain::Optimism as u64] {
1261 assert_eq!(ethereum.try_with_chain_id(chain_id).unwrap(), ethereum);
1262 }
1263 }
1264
1265 #[test]
1266 fn fallible_chain_id_inference_preserves_orthogonal_configuration() {
1267 let networks = NetworkConfigs { bypass_prevrandao: true, ..Default::default() };
1268 let inferred = networks.try_with_chain_id(NamedChain::Tempo as u64).unwrap();
1269
1270 assert!(inferred.is_tempo());
1271 assert!(inferred.bypass_prevrandao(NamedChain::Mainnet as u64));
1272 }
1273
1274 #[test]
1275 #[cfg(feature = "optimism")]
1276 fn fallible_chain_id_inference_detects_optimism() {
1277 let chain_id = NamedChain::Optimism as u64;
1278 assert_eq!(NetworkVariant::from(chain_id), NetworkVariant::Optimism);
1279 assert!(NetworkConfigs::default().try_with_chain_id(chain_id).unwrap().is_optimism());
1280 }
1281
1282 #[test]
1283 fn rpc_identity_preserves_orthogonal_configuration() {
1284 let base = NetworkConfigs { bypass_prevrandao: true, ..NetworkConfigs::default() };
1285 let ethereum = base.with_rpc_network(NetworkVariant::Ethereum);
1286 assert!(ethereum.bypass_prevrandao(NamedChain::Mainnet as u64));
1287
1288 let ethereum = NetworkConfigs::default().with_rpc_network(NetworkVariant::Ethereum);
1289 assert_eq!(ethereum, NetworkConfigs::default());
1290
1291 assert!(
1292 NetworkConfigs::default()
1293 .with_rpc_identity(NetworkVariant::Ethereum, NamedChain::Celo as u64)
1294 .is_celo()
1295 );
1296 assert!(
1297 !NetworkConfigs::default()
1298 .with_rpc_identity(NetworkVariant::Ethereum, NamedChain::Monad as u64)
1299 .is_monad()
1300 );
1301 }
1302
1303 #[test]
1304 fn rpc_metadata_overrides_known_execution_chain_identity() {
1305 assert_eq!(
1306 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1307 NamedChain::Mainnet as u64,
1308 Some(Some("tempo")),
1309 None,
1310 )
1311 .unwrap(),
1312 Some(NetworkConfigs::with_tempo())
1313 );
1314 assert_eq!(
1315 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1316 NamedChain::Tempo as u64,
1317 Some(None),
1318 None,
1319 )
1320 .unwrap(),
1321 Some(NetworkConfigs::with_tempo())
1322 );
1323 assert_eq!(
1324 NetworkConfigs::from_rpc_identity_profile_with_fallback(98_765_432, None, None)
1325 .unwrap(),
1326 None
1327 );
1328 assert_eq!(
1329 NetworkConfigs::from_rpc_identity_profile_with_fallback(98_765_432, Some(None), None)
1330 .unwrap(),
1331 Some(NetworkConfigs::default())
1332 );
1333 #[cfg(feature = "optimism")]
1334 assert_eq!(
1335 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1336 NamedChain::Optimism as u64,
1337 Some(None),
1338 None,
1339 )
1340 .unwrap(),
1341 Some(NetworkConfigs::from(NetworkVariant::Optimism))
1342 );
1343 }
1344
1345 #[test]
1346 fn rpc_profile_distinguishes_celo_from_ethereum_factory() {
1347 let custom_chain_id = 98_765_432;
1348 assert!(
1349 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1350 custom_chain_id,
1351 Some(Some("celo")),
1352 None,
1353 )
1354 .unwrap()
1355 .unwrap()
1356 .is_celo()
1357 );
1358 assert!(
1359 !NetworkConfigs::from_rpc_identity_profile_with_fallback(
1360 NamedChain::Celo as u64,
1361 Some(Some("ethereum")),
1362 None,
1363 )
1364 .unwrap()
1365 .unwrap()
1366 .is_celo()
1367 );
1368 assert!(
1369 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1370 NamedChain::Celo as u64,
1371 Some(None),
1372 None,
1373 )
1374 .unwrap()
1375 .unwrap()
1376 .is_celo()
1377 );
1378 assert!(
1379 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1380 custom_chain_id,
1381 None,
1382 Some(NetworkConfigs::with_celo()),
1383 )
1384 .unwrap()
1385 .unwrap()
1386 .is_celo()
1387 );
1388 }
1389
1390 #[test]
1391 fn rpc_profile_keeps_moonbeam_on_default_ethereum() {
1392 let profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1393 NamedChain::Moonbeam as u64,
1394 None,
1395 None,
1396 )
1397 .unwrap()
1398 .unwrap();
1399
1400 assert_eq!(profile, NetworkConfigs::default());
1401 assert!(profile.bypass_prevrandao(NamedChain::Moonbeam as u64));
1402 }
1403
1404 #[test]
1405 fn default_fallback_still_infers_known_execution_profile() {
1406 for node_info in [None, Some(None)] {
1407 assert_eq!(
1408 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1409 NamedChain::Tempo as u64,
1410 node_info,
1411 Some(NetworkConfigs::default()),
1412 )
1413 .unwrap(),
1414 Some(NetworkConfigs::with_tempo())
1415 );
1416 }
1417 }
1418
1419 #[test]
1420 fn explicit_ethereum_overrides_known_execution_profile() {
1421 for node_info in [None, Some(None)] {
1422 assert_eq!(
1423 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1424 NamedChain::Tempo as u64,
1425 node_info,
1426 Some(NetworkConfigs::with_ethereum()),
1427 )
1428 .unwrap(),
1429 Some(NetworkConfigs::default())
1430 );
1431 }
1432 }
1433
1434 #[test]
1435 #[cfg(not(feature = "monad"))]
1436 fn explicit_ethereum_overrides_disabled_monad_without_node_info() {
1437 assert_eq!(
1438 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1439 NamedChain::Monad as u64,
1440 None,
1441 Some(NetworkConfigs::with_ethereum()),
1442 )
1443 .unwrap(),
1444 Some(NetworkConfigs::default())
1445 );
1446 }
1447
1448 #[test]
1449 #[cfg(not(feature = "monad"))]
1450 fn explicit_ethereum_overrides_disabled_monad_with_legacy_node_info() {
1451 assert_eq!(
1452 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1453 NamedChain::Monad as u64,
1454 Some(None),
1455 Some(NetworkConfigs::with_ethereum()),
1456 )
1457 .unwrap(),
1458 Some(NetworkConfigs::default())
1459 );
1460 }
1461
1462 #[test]
1463 #[cfg(not(feature = "monad"))]
1464 fn disabled_monad_without_explicit_profile_still_errors() {
1465 for node_info in [None, Some(None)] {
1466 assert_eq!(
1467 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1468 NamedChain::Monad as u64,
1469 node_info,
1470 None,
1471 )
1472 .unwrap_err(),
1473 "network family `monad` is not enabled in this build"
1474 );
1475 }
1476 }
1477
1478 #[test]
1479 fn network_selection_distinguishes_default_and_explicit_ethereum() {
1480 assert!(!NetworkConfigs::default().has_network_selection());
1481 assert!(NetworkConfigs::with_ethereum().has_network_selection());
1482 assert!(NetworkConfigs::with_celo().has_network_selection());
1483 }
1484
1485 #[test]
1486 fn celo_uses_ethereum_factory_with_distinct_precompiles() {
1487 assert!(
1488 !NetworkConfigs::with_celo().has_same_execution_profile(&NetworkConfigs::default())
1489 );
1490 assert!(
1491 NetworkConfigs::with_ethereum().has_same_execution_profile(&NetworkConfigs::default())
1492 );
1493 assert_eq!(NetworkConfigs::with_celo().execution_family_name(), "ethereum");
1494 assert_eq!(NetworkConfigs::with_celo().execution_profile_name(), "celo");
1495 }
1496
1497 #[test]
1498 fn authoritative_execution_profile_preserves_orthogonal_settings() {
1499 let inline = NetworkConfigs { bypass_prevrandao: true, ..NetworkConfigs::with_tempo() };
1500
1501 #[cfg_attr(not(any(feature = "optimism", feature = "monad")), allow(unused_mut))]
1502 let mut profiles = vec![
1503 NetworkConfigs::with_ethereum(),
1504 NetworkConfigs::with_tempo(),
1505 NetworkConfigs::with_celo(),
1506 ];
1507 #[cfg(feature = "optimism")]
1508 profiles.push(NetworkVariant::Optimism.into());
1509 #[cfg(feature = "monad")]
1510 profiles.push(NetworkConfigs::with_monad());
1511
1512 for profile in profiles {
1513 let resolved = inline.with_execution_profile(profile);
1514 assert!(resolved.has_same_execution_profile(&profile));
1515 assert!(resolved.has_network_selection());
1516 assert!(resolved.bypass_prevrandao(NamedChain::Mainnet as u64));
1517 }
1518
1519 let ethereum = inline.with_execution_profile(NetworkConfigs::with_ethereum());
1520 assert_eq!(
1521 ethereum.try_with_chain_id(NamedChain::Tempo as u64).unwrap(),
1522 ethereum,
1523 "an authoritative Ethereum profile must prevent later endpoint inference",
1524 );
1525
1526 let rpc_ethereum = inline.with_rpc_profile(NetworkConfigs::with_ethereum());
1527 assert!(!rpc_ethereum.has_network_selection());
1528 }
1529
1530 #[test]
1531 fn chain_id_inference_preserves_explicit_networks() {
1532 assert!(
1533 NetworkConfigs::default().try_with_chain_id(NamedChain::Celo as u64).unwrap().is_celo()
1534 );
1535 let celo = NetworkConfigs { bypass_prevrandao: true, ..Default::default() }
1536 .try_with_chain_id(NamedChain::Celo as u64)
1537 .unwrap();
1538 assert!(celo.is_celo());
1539 assert!(celo.bypass_prevrandao(NamedChain::Mainnet as u64));
1540
1541 let explicit = [
1542 NetworkConfigs::with_ethereum(),
1543 NetworkConfigs::with_tempo(),
1544 NetworkConfigs::with_celo(),
1545 ];
1546 for networks in explicit {
1547 assert_eq!(networks.try_with_chain_id(NamedChain::Celo as u64).unwrap(), networks);
1548 }
1549
1550 #[cfg(feature = "monad")]
1551 {
1552 let monad = NetworkConfigs::with_monad();
1553 assert_eq!(monad.try_with_chain_id(NamedChain::Celo as u64).unwrap(), monad);
1554 }
1555 }
1556
1557 #[test]
1558 #[cfg(feature = "monad")]
1559 fn rpc_metadata_identifies_custom_monad_networks() {
1560 assert_eq!(NetworkVariant::from_node_info_name("monad").unwrap(), NetworkVariant::Monad);
1561 assert!(NetworkConfigs::default().with_rpc_network(NetworkVariant::Monad).is_monad());
1562 }
1563
1564 #[test]
1565 #[cfg(feature = "monad")]
1566 fn parses_endpoint_hardfork_in_network_namespace() {
1567 assert_eq!(
1568 NetworkVariant::Monad.parse_hardfork("MonadEight").unwrap(),
1569 FoundryHardfork::Monad(MonadHardfork::MonadEight)
1570 );
1571 }
1572
1573 #[test]
1574 fn rpc_metadata_rejects_unknown_network_family() {
1575 assert_eq!(
1576 NetworkVariant::from_node_info_name("unknown").unwrap_err(),
1577 "unsupported network family `unknown` reported by fork endpoint"
1578 );
1579 }
1580
1581 #[test]
1582 fn explicit_ethereum_families_reject_namespaced_hardforks() {
1583 #[cfg_attr(not(feature = "monad"), allow(unused_mut))]
1584 let mut incompatible = vec![FoundryHardfork::Tempo(TempoHardfork::T0)];
1585 #[cfg(feature = "monad")]
1586 incompatible.push(FoundryHardfork::Monad(MonadHardfork::MonadEight));
1587
1588 for networks in [NetworkConfigs::with_ethereum(), NetworkConfigs::with_celo()] {
1589 for hardfork in &incompatible {
1590 assert_eq!(
1591 networks.normalize_for_hardfork(*hardfork).unwrap_err(),
1592 format!(
1593 "hardfork `{}` conflicts with network config `{}`",
1594 String::from(*hardfork),
1595 networks.execution_profile_name()
1596 )
1597 );
1598 }
1599 }
1600
1601 let celo = NetworkConfigs::with_celo();
1602 assert_eq!(
1603 celo.normalize_for_hardfork(FoundryHardfork::Ethereum(
1604 foundry_evm_hardforks::EthereumHardfork::Prague
1605 ))
1606 .unwrap(),
1607 celo
1608 );
1609 }
1610
1611 #[cfg(feature = "base")]
1612 #[test]
1613 fn base_precompile_labels_follow_upgrade_boundaries() {
1614 let config = NetworkConfigs::with_base();
1615
1616 assert!(resolved_precompile_labels(Some(BaseUpgrade::Azul.into())).is_empty());
1617
1618 let beryl = resolved_precompile_labels(Some(BaseUpgrade::Beryl.into()));
1619 assert_eq!(beryl.get(&B20FactoryStorage::ADDRESS), Some(&"B20Factory".to_string()));
1620 assert_eq!(
1621 beryl.get(&ActivationRegistryStorage::ADDRESS),
1622 Some(&"ActivationRegistry".to_string())
1623 );
1624 assert_eq!(beryl.get(&PolicyRegistryStorage::ADDRESS), Some(&"PolicyRegistry".to_string()));
1625 assert!(!beryl.contains_key(&TxContextStorage::ADDRESS));
1626 assert!(!beryl.contains_key(&NonceManagerStorage::ADDRESS));
1627
1628 let cobalt = resolved_precompile_labels(Some(BaseUpgrade::Cobalt.into()));
1629 assert_eq!(cobalt.len(), BASE_PRECOMPILES.len());
1630
1631 assert!(config.precompiles(Some(BaseUpgrade::Azul.into())).is_empty());
1633 let beryl = config.precompiles(Some(BaseUpgrade::Beryl.into()));
1634 assert_eq!(beryl.get("B20Factory"), Some(&B20FactoryStorage::ADDRESS));
1635 assert!(!beryl.contains_key("NonceManager"));
1636 assert_eq!(
1637 config.precompiles(Some(BaseUpgrade::Cobalt.into())).get("NonceManager"),
1638 Some(&NonceManagerStorage::ADDRESS)
1639 );
1640 }
1641
1642 #[test]
1643 fn new_tempo_flag_equivalent_to_legacy() {
1644 let via_new = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1645 let via_old = NetworkConfigs { tempo: true, ..Default::default() };
1646 assert_eq!(via_new.is_tempo(), via_old.is_tempo());
1647 assert_eq!(via_new.active_network_name(), via_old.active_network_name());
1648 assert_eq!(via_new.precompiles(None), via_old.precompiles(None));
1649 }
1650
1651 fn bsc_p256_gas_used(chain_id: ChainId, timestamp: u64) -> Option<u64> {
1652 bsc_p256_precompile(chain_id, timestamp)
1653 .flatten()
1654 .map(|precompile| precompile.execute(&[], u64::MAX, 0).unwrap().gas_used)
1655 }
1656
1657 fn assert_bsc_p256_boundaries(chain_id: ChainId, haber_timestamp: u64, osaka_timestamp: u64) {
1658 assert!(matches!(bsc_p256_precompile(chain_id, haber_timestamp - 1), Some(None)));
1659 assert_eq!(bsc_p256_gas_used(chain_id, haber_timestamp), Some(P256VERIFY_BASE_GAS_FEE));
1660 assert_eq!(bsc_p256_gas_used(chain_id, osaka_timestamp - 1), Some(P256VERIFY_BASE_GAS_FEE));
1661 assert_eq!(
1662 bsc_p256_gas_used(chain_id, osaka_timestamp),
1663 Some(P256VERIFY_BASE_GAS_FEE_OSAKA)
1664 );
1665 }
1666
1667 #[test]
1668 fn selects_bsc_p256_at_mainnet_boundaries() {
1669 assert_bsc_p256_boundaries(
1670 BSC_MAINNET_CHAIN_ID,
1671 BSC_MAINNET_HABER_TIMESTAMP,
1672 BSC_MAINNET_OSAKA_TIMESTAMP,
1673 );
1674 }
1675
1676 #[test]
1677 fn selects_bsc_p256_at_testnet_boundaries() {
1678 assert_bsc_p256_boundaries(
1679 BSC_TESTNET_CHAIN_ID,
1680 BSC_TESTNET_HABER_TIMESTAMP,
1681 BSC_TESTNET_OSAKA_TIMESTAMP,
1682 );
1683 }
1684
1685 #[test]
1686 fn removes_bsc_p256_before_haber() {
1687 let mut precompiles = PrecompilesMap::from_static(Precompiles::osaka());
1688 assert!(precompiles.get(&BSC_P256_ADDRESS).is_some());
1689 apply_bsc_p256_precompile(
1690 &mut precompiles,
1691 BSC_MAINNET_CHAIN_ID,
1692 BSC_MAINNET_HABER_TIMESTAMP - 1,
1693 );
1694 assert!(precompiles.get(&BSC_P256_ADDRESS).is_none());
1695 }
1696
1697 #[test]
1698 fn canonical_tempo_network_reports_precompiles() {
1699 let cfg = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1700
1701 assert_eq!(
1702 cfg.precompiles(None).get("TIP20ChannelReserve"),
1703 Some(&TIP20_CHANNEL_RESERVE_ADDRESS)
1704 );
1705 assert!(
1706 !cfg.precompiles(Some(TempoHardfork::T4.into())).contains_key("TIP20ChannelReserve")
1707 );
1708 assert!(
1709 !cfg.precompiles(Some(TempoHardfork::T4.into())).contains_key("ReceivePolicyGuard")
1710 );
1711 assert!(!cfg.precompiles(Some(TempoHardfork::T2.into())).contains_key("AddressRegistry"));
1712 assert!(!cfg.precompiles(Some(TempoHardfork::T2.into())).contains_key("SignatureVerifier"));
1713 assert_eq!(
1714 cfg.precompiles(Some(TempoHardfork::T3.into())).get("AddressRegistry"),
1715 Some(&ADDRESS_REGISTRY_ADDRESS)
1716 );
1717 assert_eq!(
1718 cfg.precompiles(Some(TempoHardfork::T3.into())).get("SignatureVerifier"),
1719 Some(&SIGNATURE_VERIFIER_ADDRESS)
1720 );
1721 assert_eq!(
1722 resolved_precompile_labels(Some(TempoHardfork::T5.into()))
1723 .get(&TIP20_CHANNEL_RESERVE_ADDRESS),
1724 Some(&"TIP20ChannelReserve".to_string())
1725 );
1726 assert!(
1727 !resolved_precompile_labels(Some(TempoHardfork::T5.into()))
1728 .contains_key(&RECEIVE_POLICY_GUARD_ADDRESS)
1729 );
1730 assert!(
1731 resolved_precompile_labels(Some(TempoHardfork::T6.into()))
1732 .contains_key(&RECEIVE_POLICY_GUARD_ADDRESS)
1733 );
1734 }
1735
1736 #[test]
1737 #[cfg(feature = "monad")]
1738 fn canonical_monad_network_reports_hardfork_gated_precompiles() {
1739 let cfg = NetworkConfigs { network: Some(NetworkVariant::Monad), ..Default::default() };
1740
1741 assert_eq!(
1742 cfg.precompiles(Some(MonadHardfork::MonadEight.into())).get("MonadStaking"),
1743 Some(&monad_revm::staking::STAKING_ADDRESS)
1744 );
1745 assert!(
1746 !cfg.precompiles(Some(MonadHardfork::MonadEight.into()))
1747 .contains_key("MonadReserveBalance")
1748 );
1749 assert_eq!(
1750 cfg.precompiles(Some(MonadHardfork::MonadNine.into())).get("MonadReserveBalance"),
1751 Some(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
1752 );
1753 assert_eq!(
1754 resolved_precompile_labels(Some(MonadHardfork::MonadNine.into()))
1755 .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS),
1756 Some(&"ReserveBalance".to_string())
1757 );
1758 assert!(
1759 !resolved_precompile_labels(Some(MonadHardfork::MonadEight.into()))
1760 .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
1761 );
1762 }
1763
1764 #[test]
1765 fn storage_credits_precompile_activates_at_t7() {
1766 assert!(!is_tempo_precompile_active_at(STORAGE_CREDITS_ADDRESS, TempoHardfork::T6));
1767 assert!(is_tempo_precompile_active_at(STORAGE_CREDITS_ADDRESS, TempoHardfork::T7));
1768 assert!(TEMPO_PRECOMPILE_ADDRESSES.contains(&STORAGE_CREDITS_ADDRESS));
1769
1770 let cfg = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1772 assert!(!cfg.precompiles(Some(TempoHardfork::T6.into())).contains_key("StorageCredits"));
1773 assert!(cfg.precompiles(Some(TempoHardfork::T7.into())).contains_key("StorageCredits"));
1774 }
1775
1776 #[test]
1777 fn current_committee_precompile_activates_at_t8() {
1778 assert!(!is_tempo_precompile_active_at(CURRENT_COMMITTEE_ADDRESS, TempoHardfork::T7));
1779 assert!(is_tempo_precompile_active_at(CURRENT_COMMITTEE_ADDRESS, TempoHardfork::T8));
1780 assert!(TEMPO_PRECOMPILE_ADDRESSES.contains(&CURRENT_COMMITTEE_ADDRESS));
1781
1782 let cfg = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1783 assert!(!cfg.precompiles(Some(TempoHardfork::T7.into())).contains_key("CurrentCommittee"));
1784 assert!(cfg.precompiles(Some(TempoHardfork::T8.into())).contains_key("CurrentCommittee"));
1785 }
1786
1787 #[test]
1790 fn active_network_name_tempo() {
1791 let cfg = NetworkConfigs::with_tempo();
1792 assert_eq!(cfg.active_network_name(), Some("tempo"));
1793 }
1794
1795 #[test]
1796 #[cfg(feature = "monad")]
1797 fn active_network_name_monad() {
1798 let cfg = NetworkConfigs::with_monad();
1799 assert_eq!(cfg.active_network_name(), Some("monad"));
1800 assert!(cfg.is_monad());
1801 }
1802
1803 #[test]
1804 #[cfg(feature = "monad")]
1805 fn contract_size_limits_monad() {
1806 let limits = NetworkConfigs::with_monad().contract_size_limits().unwrap();
1807 assert_eq!(limits.runtime, monad_revm::MONAD_MAX_CODE_SIZE);
1808 assert_eq!(limits.initcode, monad_revm::MONAD_MAX_INITCODE_SIZE);
1809 assert!(NetworkConfigs::default().contract_size_limits().is_none());
1810 }
1811
1812 #[test]
1813 fn active_network_name_default_is_none() {
1814 let cfg = NetworkConfigs::default();
1815 assert_eq!(cfg.active_network_name(), None);
1816 assert!(!cfg.is_optimism());
1817 }
1818
1819 #[test]
1823 #[cfg(all(not(feature = "base"), feature = "optimism"))]
1824 fn chain_id_inference_falls_back_to_optimism_without_base() {
1825 for chain_id in [NamedChain::Base as u64, NamedChain::BaseSepolia as u64] {
1826 let configs = NetworkConfigs::default()
1827 .try_with_chain_id(chain_id)
1828 .unwrap_or_else(|error| panic!("chain ID {chain_id} must still resolve: {error}"));
1829 assert!(configs.is_optimism(), "chain ID {chain_id} must resolve to Optimism");
1830 }
1831 }
1832
1833 #[test]
1834 #[cfg(not(feature = "base"))]
1835 fn node_info_rejects_disabled_base() {
1836 assert_eq!(
1837 NetworkVariant::from_node_info_name("base").unwrap_err(),
1838 "network family `base` is not enabled in this build"
1839 );
1840 }
1841
1842 #[test]
1845 fn serde_roundtrip_tempo() {
1846 let original = NetworkConfigs::with_tempo();
1847 let json = serde_json::to_string(&original).unwrap();
1848 let restored: NetworkConfigs = serde_json::from_str(&json).unwrap();
1849 assert!(restored.is_tempo());
1850 }
1851
1852 #[test]
1853 fn serde_legacy_tempo_bool_deserialized() {
1854 let json = r#"{"tempo": true, "celo": false, "bypass_prevrandao": false}"#;
1856 let cfg: NetworkConfigs = serde_json::from_str(json).unwrap();
1857 assert!(cfg.is_tempo());
1858 }
1859
1860 #[test]
1861 #[cfg(feature = "monad")]
1862 fn serde_roundtrip_monad() {
1863 let original = NetworkConfigs::with_monad();
1864 let json = serde_json::to_string(&original).unwrap();
1865 let restored: NetworkConfigs = serde_json::from_str(&json).unwrap();
1866 assert!(restored.is_monad());
1867 assert!(!restored.is_tempo());
1868 }
1869
1870 #[test]
1871 #[cfg(feature = "monad")]
1872 fn serde_legacy_monad_bool_deserialized() {
1873 let json = r#"{"monad": true, "celo": false, "bypass_prevrandao": false}"#;
1874 let cfg: NetworkConfigs = serde_json::from_str(json).unwrap();
1875 assert!(cfg.is_monad());
1876 }
1877
1878 #[test]
1879 fn serde_serializes_legacy_alias_as_canonical_network() {
1880 #[cfg_attr(not(feature = "monad"), allow(unused_mut))]
1881 let mut cases = vec![(NetworkConfigs { tempo: true, ..Default::default() }, "tempo")];
1882 #[cfg(feature = "monad")]
1883 cases.push((NetworkConfigs { monad: true, ..Default::default() }, "monad"));
1884
1885 for (cfg, expected) in cases {
1886 let json = serde_json::to_value(cfg).unwrap();
1887 assert_eq!(json["network"], serde_json::json!(expected));
1888 assert!(json.get("tempo").is_none(), "legacy `tempo` key should not be serialized");
1889 assert!(json.get("monad").is_none(), "legacy `monad` key should not be serialized");
1890 }
1891 }
1892
1893 #[test]
1894 fn serde_new_network_field_deserialized() {
1895 let json_tempo = r#"{"network": "tempo", "celo": false, "bypass_prevrandao": false}"#;
1896 let cfg_tempo: NetworkConfigs = serde_json::from_str(json_tempo).unwrap();
1897 assert!(cfg_tempo.is_tempo());
1898
1899 #[cfg(feature = "monad")]
1900 {
1901 let json_monad = r#"{"network": "monad", "celo": false, "bypass_prevrandao": false}"#;
1902 let cfg_monad: NetworkConfigs = serde_json::from_str(json_monad).unwrap();
1903 assert!(cfg_monad.is_monad());
1904 }
1905 }
1906
1907 #[test]
1908 fn validates_flattened_network_selectors() {
1909 let valid = [
1910 NetworkConfigs::default(),
1911 NetworkConfigs::with_ethereum(),
1912 NetworkConfigs::with_celo(),
1913 NetworkConfigs::with_tempo(),
1914 NetworkConfigs {
1915 network: Some(NetworkVariant::Tempo),
1916 tempo: true,
1917 ..Default::default()
1918 },
1919 ];
1920 for networks in valid {
1921 networks.validate().unwrap();
1922 }
1923
1924 let conflicts = [
1925 (
1926 NetworkConfigs {
1927 network: Some(NetworkVariant::Tempo),
1928 celo: true,
1929 ..Default::default()
1930 },
1931 "network selectors `network = \"tempo\"` and `celo = true` conflict",
1932 ),
1933 (
1934 NetworkConfigs { celo: true, tempo: true, ..Default::default() },
1935 "network selectors `celo = true` and `tempo = true` conflict",
1936 ),
1937 ];
1938 for (networks, expected) in conflicts {
1939 assert!(networks.validate().unwrap_err().contains(expected));
1940 }
1941 }
1942
1943 #[test]
1944 #[cfg(feature = "monad")]
1945 fn validates_flattened_monad_network_selectors() {
1946 for networks in [
1947 NetworkConfigs::with_monad(),
1948 NetworkConfigs {
1949 network: Some(NetworkVariant::Monad),
1950 monad: true,
1951 ..Default::default()
1952 },
1953 ] {
1954 networks.validate().unwrap();
1955 }
1956
1957 let conflicts = [
1958 (
1959 NetworkConfigs {
1960 network: Some(NetworkVariant::Monad),
1961 celo: true,
1962 ..Default::default()
1963 },
1964 "network selectors `network = \"monad\"` and `celo = true` conflict",
1965 ),
1966 (
1967 NetworkConfigs {
1968 network: Some(NetworkVariant::Monad),
1969 tempo: true,
1970 ..Default::default()
1971 },
1972 "network selectors `network = \"monad\"` and `tempo = true` conflict",
1973 ),
1974 (
1975 NetworkConfigs { celo: true, monad: true, ..Default::default() },
1976 "network selectors `celo = true` and `monad = true` conflict",
1977 ),
1978 (
1979 NetworkConfigs { tempo: true, monad: true, ..Default::default() },
1980 "network selectors `tempo = true` and `monad = true` conflict",
1981 ),
1982 ];
1983 for (networks, expected) in conflicts {
1984 assert!(networks.validate().unwrap_err().contains(expected));
1985 }
1986 }
1987
1988 #[test]
1989 #[cfg(feature = "monad")]
1990 fn chain_id_detects_monad_network() {
1991 assert_eq!(NetworkVariant::from(143), NetworkVariant::Monad);
1992 assert_eq!(NetworkVariant::from(10143), NetworkVariant::Monad);
1993
1994 assert!(NetworkConfigs::default().try_with_chain_id(143).unwrap().is_monad());
1995 }
1996
1997 #[cfg(feature = "base")]
1998 mod base {
1999 use super::*;
2000
2001 #[test]
2002 fn active_network_name_base() {
2003 let cfg = NetworkConfigs::with_base();
2004 assert_eq!(cfg.active_network_name(), Some("base"));
2005 }
2006
2007 #[test]
2008 fn base_fee_params_follow_canyon_boundary() {
2009 const CANYON_TIMESTAMP: u64 = 1_704_992_401;
2010
2011 let config = NetworkConfigs::with_base();
2012 assert_eq!(config.base_fee_params(CANYON_TIMESTAMP - 1), BaseFeeParams::new(50, 6));
2013 assert_eq!(config.base_fee_params(CANYON_TIMESTAMP), BaseFeeParams::new(250, 6));
2014 }
2015
2016 #[test]
2017 fn base_does_not_inherit_ethereum_blob_excess_gas() {
2018 let params = BlobParams::prague();
2019 assert_eq!(
2020 NetworkConfigs::with_base().next_block_blob_excess_gas(
2021 params,
2022 0,
2023 params.target_blob_gas_per_block() + 1,
2024 1,
2025 ),
2026 0
2027 );
2028 }
2029
2030 #[test]
2031 fn serde_roundtrip_base() {
2032 let original = NetworkConfigs::with_base();
2033 let json = serde_json::to_string(&original).unwrap();
2034 let restored: NetworkConfigs = serde_json::from_str(&json).unwrap();
2035 assert!(restored.is_base());
2036 assert!(!restored.is_tempo());
2037 }
2038
2039 #[test]
2040 fn serde_base_field_deserialized() {
2041 let json_base = r#"{"network": "base", "celo": false, "bypass_prevrandao": false}"#;
2042 let cfg_base: NetworkConfigs = serde_json::from_str(json_base).unwrap();
2043 assert!(cfg_base.is_base());
2044 }
2045
2046 #[test]
2047 fn chain_id_detects_base_networks() {
2048 assert_eq!(NetworkVariant::from(8453), NetworkVariant::Base);
2049 assert_eq!(NetworkVariant::from(84532), NetworkVariant::Base);
2050 assert!(NetworkConfigs::default().try_with_chain_id(8453).unwrap().is_base());
2051 assert_eq!(NetworkVariant::from_node_info_name("base").unwrap(), NetworkVariant::Base);
2052 }
2053
2054 #[test]
2055 fn hardfork_infers_base_network() {
2056 assert_eq!(
2057 NetworkConfigs::default()
2058 .normalize_for_hardfork(FoundryHardfork::Base(BaseUpgrade::Beryl))
2059 .unwrap()
2060 .resolved_network(),
2061 Some(NetworkVariant::Base)
2062 );
2063 }
2064 }
2065
2066 #[cfg(feature = "optimism")]
2067 mod optimism {
2068 use super::*;
2069
2070 #[test]
2071 fn new_optimism_flag_equivalent_to_legacy() {
2072 let via_new =
2073 NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() };
2074 let via_old = NetworkConfigs { optimism: true, ..Default::default() };
2075 assert_eq!(via_new.is_optimism(), via_old.is_optimism());
2076 assert_eq!(via_new.is_tempo(), via_old.is_tempo());
2077 assert_eq!(via_new.active_network_name(), via_old.active_network_name());
2078 }
2079
2080 #[test]
2081 fn matching_optimism_selectors_are_valid() {
2082 NetworkConfigs::with_optimism().validate().unwrap();
2083 }
2084
2085 #[test]
2086 fn active_network_name_optimism() {
2087 let cfg = NetworkConfigs::with_optimism();
2088 assert_eq!(cfg.active_network_name(), Some("optimism"));
2089 }
2090
2091 #[test]
2092 fn conflicting_optimism_and_tempo_selectors_are_rejected() {
2093 let cfg = NetworkConfigs {
2094 network: Some(NetworkVariant::Optimism),
2095 tempo: true,
2096 ..Default::default()
2097 };
2098 assert_eq!(
2099 cfg.validate().unwrap_err(),
2100 "network selectors `network = \"optimism\"` and `tempo = true` conflict; select \
2101 only one network"
2102 );
2103 }
2104
2105 #[test]
2106 fn serde_roundtrip_optimism() {
2107 let original = NetworkConfigs::with_optimism();
2108 let json = serde_json::to_string(&original).unwrap();
2109 let restored: NetworkConfigs = serde_json::from_str(&json).unwrap();
2110 assert!(restored.is_optimism());
2111 assert!(!restored.is_tempo());
2112 }
2113
2114 #[test]
2115 fn serde_optimism_field_deserialized() {
2116 let json_optimism =
2117 r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#;
2118 let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap();
2119 assert!(cfg_optimism.is_optimism());
2120 }
2121 }
2122
2123 #[test]
2124 fn executed_hardfork_follows_the_network_family() {
2125 let prague = FoundryHardfork::Ethereum(EthereumHardfork::Prague);
2128 assert_eq!(NetworkConfigs::default().executed_hardfork(prague), prague);
2129 assert_eq!(
2130 NetworkConfigs::with_tempo().executed_hardfork(prague),
2131 FoundryHardfork::Tempo(TempoHardfork::from(prague))
2132 );
2133 }
2134}