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