1use crate::celo::transfer::{
6 CELO_TRANSFER_ADDRESS, CELO_TRANSFER_LABEL, PRECOMPILE_ID_CELO_TRANSFER,
7};
8use alloy_chains::{
9 Chain, NamedChain,
10 NamedChain::{Chiado, Gnosis, Moonbase, Moonbeam, MoonbeamDev, Moonriver, Rsk, RskTestnet},
11};
12use alloy_eips::eip1559::BaseFeeParams;
13use alloy_evm::precompiles::{DynPrecompile, PrecompilesMap};
14use alloy_primitives::{Address, ChainId, address, map::AddressHashMap};
15use clap::Parser;
16#[cfg(feature = "monad")]
17type MonadHardfork = foundry_evm_hardforks::MonadHardfork;
18#[cfg(feature = "optimism")]
19use foundry_evm_hardforks::OpHardfork;
20use foundry_evm_hardforks::{
21 EthereumHardfork, FoundryHardfork, TempoHardfork, latest_active_tempo_hardfork,
22};
23#[cfg(not(feature = "monad"))]
24type MonadHardfork = ();
25use revm::precompile::{
26 Precompile as RevmPrecompile,
27 secp256r1::{P256VERIFY, P256VERIFY_OSAKA},
28};
29use serde::{Deserialize, Serialize};
30use std::collections::BTreeMap;
31use tempo_contracts::precompiles::{
32 ACCOUNT_KEYCHAIN_ADDRESS, ADDRESS_REGISTRY_ADDRESS, CURRENT_COMMITTEE_ADDRESS,
33 NONCE_PRECOMPILE_ADDRESS, RECEIVE_POLICY_GUARD_ADDRESS, SIGNATURE_VERIFIER_ADDRESS,
34 STABLECOIN_DEX_ADDRESS, STORAGE_CREDITS_ADDRESS, TIP_FEE_MANAGER_ADDRESS,
35 TIP20_CHANNEL_RESERVE_ADDRESS, TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS,
36 VALIDATOR_CONFIG_ADDRESS, VALIDATOR_CONFIG_V2_ADDRESS,
37};
38
39pub const MONAD_CHEATCODE_ADDRESS: Address = address!("0xc0FFeeCD43A10e1C2b0De63c6CDCFe5B7d0e0CEA");
41
42#[cfg(feature = "monad")]
43const MONAD_CHEATCODE_ADDRESSES: &[Address] = &[MONAD_CHEATCODE_ADDRESS];
44
45pub mod arbitrum;
46pub mod celo;
47
48#[cfg(feature = "optimism")]
49mod optimism;
50
51const TEMPO_PRECOMPILES: &[(&str, Address)] = &[
52 ("Nonce", NONCE_PRECOMPILE_ADDRESS),
53 ("StablecoinDex", STABLECOIN_DEX_ADDRESS),
54 ("TIP20Factory", TIP20_FACTORY_ADDRESS),
55 ("TIP403Registry", TIP403_REGISTRY_ADDRESS),
56 ("FeeManager", TIP_FEE_MANAGER_ADDRESS),
57 ("ValidatorConfig", VALIDATOR_CONFIG_ADDRESS),
58 ("ValidatorConfigV2", VALIDATOR_CONFIG_V2_ADDRESS),
59 ("AccountKeychain", ACCOUNT_KEYCHAIN_ADDRESS),
60 ("SignatureVerifier", SIGNATURE_VERIFIER_ADDRESS),
61 ("AddressRegistry", ADDRESS_REGISTRY_ADDRESS),
62 ("TIP20ChannelReserve", TIP20_CHANNEL_RESERVE_ADDRESS),
63 ("ReceivePolicyGuard", RECEIVE_POLICY_GUARD_ADDRESS),
64 ("StorageCredits", STORAGE_CREDITS_ADDRESS),
65 ("CurrentCommittee", CURRENT_COMMITTEE_ADDRESS),
66];
67
68#[cfg(feature = "monad")]
69const MONAD_PRECOMPILE_LABELS: &[(&str, Address)] = &[
70 ("Staking", monad_revm::staking::STAKING_ADDRESS),
71 ("ReserveBalance", monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS),
72];
73
74#[cfg(feature = "monad")]
75const MONAD_PRECOMPILES: &[(&str, Address)] = &[
76 ("MonadStaking", monad_revm::staking::STAKING_ADDRESS),
77 ("MonadReserveBalance", monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS),
78];
79
80const BSC_P256_ADDRESS: Address = address!("0000000000000000000000000000000000000100");
82
83const BSC_MAINNET_CHAIN_ID: u64 = 56;
84const BSC_TESTNET_CHAIN_ID: u64 = 97;
85const BSC_MAINNET_HABER_TIMESTAMP: u64 = 1_718_863_500;
86const BSC_TESTNET_HABER_TIMESTAMP: u64 = 1_716_962_820;
87const BSC_MAINNET_OSAKA_TIMESTAMP: u64 = 1_777_343_400;
88const BSC_TESTNET_OSAKA_TIMESTAMP: u64 = 1_774_319_400;
89
90const fn bsc_p256_precompile(chain_id: ChainId, timestamp: u64) -> Option<Option<RevmPrecompile>> {
93 let (haber_timestamp, osaka_timestamp) = match chain_id {
94 BSC_MAINNET_CHAIN_ID => (BSC_MAINNET_HABER_TIMESTAMP, BSC_MAINNET_OSAKA_TIMESTAMP),
95 BSC_TESTNET_CHAIN_ID => (BSC_TESTNET_HABER_TIMESTAMP, BSC_TESTNET_OSAKA_TIMESTAMP),
96 _ => return None,
97 };
98
99 if timestamp < haber_timestamp {
100 Some(None)
101 } else if timestamp < osaka_timestamp {
102 Some(Some(P256VERIFY))
103 } else {
104 Some(Some(P256VERIFY_OSAKA))
105 }
106}
107
108pub const TEMPO_PRECOMPILE_ADDRESSES: &[Address] = &[
110 NONCE_PRECOMPILE_ADDRESS,
111 STABLECOIN_DEX_ADDRESS,
112 TIP20_FACTORY_ADDRESS,
113 TIP403_REGISTRY_ADDRESS,
114 TIP_FEE_MANAGER_ADDRESS,
115 VALIDATOR_CONFIG_ADDRESS,
116 VALIDATOR_CONFIG_V2_ADDRESS,
117 ACCOUNT_KEYCHAIN_ADDRESS,
118 SIGNATURE_VERIFIER_ADDRESS,
119 ADDRESS_REGISTRY_ADDRESS,
120 TIP20_CHANNEL_RESERVE_ADDRESS,
121 RECEIVE_POLICY_GUARD_ADDRESS,
122 STORAGE_CREDITS_ADDRESS,
123 CURRENT_COMMITTEE_ADDRESS,
124];
125
126pub fn is_tempo_precompile_active_at(address: Address, hardfork: TempoHardfork) -> bool {
128 if address == CURRENT_COMMITTEE_ADDRESS {
129 hardfork.is_t8()
130 } else if address == TIP20_CHANNEL_RESERVE_ADDRESS {
131 hardfork.is_t5()
132 } else if address == RECEIVE_POLICY_GUARD_ADDRESS {
133 hardfork.is_t6()
134 } else if address == STORAGE_CREDITS_ADDRESS {
135 hardfork.is_t7()
136 } else if address == ADDRESS_REGISTRY_ADDRESS || address == SIGNATURE_VERIFIER_ADDRESS {
137 hardfork.is_t3()
138 } else {
139 true
140 }
141}
142
143pub fn active_tempo_precompile_addresses(hardfork: TempoHardfork) -> impl Iterator<Item = Address> {
145 TEMPO_PRECOMPILE_ADDRESSES
146 .iter()
147 .copied()
148 .filter(move |&address| is_tempo_precompile_active_at(address, hardfork))
149}
150
151#[cfg(feature = "monad")]
153pub fn is_monad_precompile_active_at(address: Address, hardfork: MonadHardfork) -> bool {
154 address == monad_revm::staking::STAKING_ADDRESS
155 || (address == monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS
156 && MonadHardfork::MonadNine.is_enabled_in(hardfork))
157}
158
159#[derive(
160 Clone,
161 Copy,
162 Debug,
163 Default,
164 PartialEq,
165 Eq,
166 PartialOrd,
167 Ord,
168 Hash,
169 Serialize,
170 Deserialize,
171 clap::ValueEnum,
172)]
173#[serde(rename_all = "lowercase")]
174#[clap(rename_all = "lowercase")]
175pub enum NetworkVariant {
176 #[default]
177 Ethereum,
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 = "optimism")]
201 "optimism" => Ok(Self::Optimism),
202 "tempo" => Ok(Self::Tempo),
203 #[cfg(feature = "monad")]
204 "monad" => Ok(Self::Monad),
205 _ => Err(format!("unknown network variant: {s}")),
206 }
207 }
208}
209
210impl NetworkVariant {
211 pub fn from_known_chain_id(chain_id: ChainId) -> Result<Option<Self>, String> {
217 let chain = Chain::from_id(chain_id);
218 if chain.is_tempo() {
219 return Ok(Some(Self::Tempo));
220 }
221 if matches!(chain.named(), Some(NamedChain::Celo | NamedChain::CeloSepolia)) {
222 return Ok(Some(Self::Ethereum));
223 }
224 if matches!(chain.named(), Some(NamedChain::Monad | NamedChain::MonadTestnet)) {
225 #[cfg(feature = "monad")]
226 return Ok(Some(Self::Monad));
227 #[cfg(not(feature = "monad"))]
228 return Err("network family `monad` is not enabled in this build".to_string());
229 }
230 if chain.is_optimism() {
231 #[cfg(feature = "optimism")]
232 return Ok(Some(Self::Optimism));
233 #[cfg(not(feature = "optimism"))]
234 return Err("network family `optimism` is not enabled in this build".to_string());
235 }
236 Ok(chain.named().map(|_| Self::Ethereum))
237 }
238
239 pub fn from_node_info_name(network: &str) -> Result<Self, String> {
241 match network {
242 "ethereum" => Ok(Self::Ethereum),
243 #[cfg(feature = "optimism")]
244 "optimism" => Ok(Self::Optimism),
245 #[cfg(not(feature = "optimism"))]
246 "optimism" => Err("network family `optimism` is not enabled in this build".to_string()),
247 "tempo" => Ok(Self::Tempo),
248 #[cfg(feature = "monad")]
249 "monad" => Ok(Self::Monad),
250 #[cfg(not(feature = "monad"))]
251 "monad" => Err("network family `monad` is not enabled in this build".to_string()),
252 network => {
253 Err(format!("unsupported network family `{network}` reported by fork endpoint"))
254 }
255 }
256 }
257
258 pub fn from_rpc_identity(
265 chain_id: ChainId,
266 node_info_network: Option<Option<&str>>,
267 ) -> Result<Option<Self>, String> {
268 Self::from_rpc_identity_with_fallback(chain_id, node_info_network, None)
269 }
270
271 pub fn from_rpc_identity_with_fallback(
278 chain_id: ChainId,
279 node_info_network: Option<Option<&str>>,
280 unknown_fallback: Option<Self>,
281 ) -> Result<Option<Self>, String> {
282 match node_info_network {
283 Some(Some(network)) => Self::from_node_info_name(network).map(Some),
284 Some(None) => Ok(Self::from_known_chain_id(chain_id)?
285 .or(unknown_fallback)
286 .or(Some(Self::Ethereum))),
287 None => Ok(Self::from_known_chain_id(chain_id)?.or(unknown_fallback)),
288 }
289 }
290
291 pub fn parse_hardfork(self, hardfork: &str) -> Result<FoundryHardfork, String> {
293 format!("{}:{hardfork}", self.name()).parse()
294 }
295
296 pub fn hardfork_at(self, chain_id: ChainId, timestamp: u64) -> FoundryHardfork {
301 match self {
302 Self::Ethereum => {
303 EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
304 .unwrap_or_default()
305 .into()
306 }
307 Self::Tempo => TempoHardfork::from_chain_and_timestamp(chain_id, timestamp)
308 .unwrap_or_else(latest_active_tempo_hardfork)
309 .into(),
310 #[cfg(feature = "optimism")]
311 Self::Optimism => {
312 OpHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
313 .unwrap_or_default()
314 .into()
315 }
316 #[cfg(feature = "monad")]
317 Self::Monad => MonadHardfork::from_chain_and_timestamp(chain_id, timestamp)
318 .unwrap_or_default()
319 .into(),
320 }
321 }
322
323 pub const fn is_ethereum(&self) -> bool {
325 matches!(self, Self::Ethereum)
326 }
327
328 #[cfg(feature = "optimism")]
330 pub const fn is_optimism(&self) -> bool {
331 matches!(self, Self::Optimism)
332 }
333
334 pub const fn is_tempo(&self) -> bool {
336 matches!(self, Self::Tempo)
337 }
338
339 #[cfg(feature = "monad")]
341 pub const fn is_monad(&self) -> bool {
342 matches!(self, Self::Monad)
343 }
344
345 #[cfg(not(feature = "monad"))]
347 pub const fn is_monad(&self) -> bool {
348 false
349 }
350
351 pub const fn name(&self) -> &'static str {
353 match self {
354 Self::Ethereum => "ethereum",
355 #[cfg(feature = "optimism")]
356 Self::Optimism => "optimism",
357 Self::Tempo => "tempo",
358 #[cfg(feature = "monad")]
359 Self::Monad => "monad",
360 }
361 }
362
363 pub const fn hardfork_namespace(&self) -> Option<&'static str> {
365 match self {
366 Self::Ethereum => None,
367 #[cfg(feature = "optimism")]
368 Self::Optimism => Some("optimism"),
369 Self::Tempo => Some("tempo"),
370 #[cfg(feature = "monad")]
371 Self::Monad => Some("monad"),
372 }
373 }
374}
375
376impl std::fmt::Display for NetworkVariant {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 f.write_str(self.name())
379 }
380}
381
382impl From<ChainId> for NetworkVariant {
383 fn from(chain_id: ChainId) -> Self {
384 Self::from_known_chain_id(chain_id).ok().flatten().unwrap_or(Self::Ethereum)
385 }
386}
387
388#[derive(Clone, Debug, Default, Parser, Deserialize, Copy, PartialEq, Eq, Hash)]
389pub struct NetworkConfigs {
390 #[arg(help_heading = "Networks", long, short, num_args = 1, value_name = "NETWORK", value_enum, conflicts_with_all = ["celo", "tempo"])]
392 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
393 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
394 #[serde(default)]
395 pub(crate) network: Option<NetworkVariant>,
396 #[arg(help_heading = "Networks", long, conflicts_with_all = ["network", "tempo"])]
398 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
399 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
400 celo: bool,
401 #[cfg(feature = "optimism")]
403 #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])]
404 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
405 #[serde(default)]
408 pub(crate) optimism: bool,
409 #[arg(long, hide = true, conflicts_with_all = ["network", "celo"])]
411 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
412 #[cfg_attr(feature = "monad", arg(conflicts_with = "monad"))]
413 #[serde(default)]
416 tempo: bool,
417 #[cfg(feature = "monad")]
419 #[arg(long, hide = true, conflicts_with_all = ["network", "celo", "tempo"])]
420 #[cfg_attr(feature = "optimism", arg(conflicts_with = "optimism"))]
421 #[serde(default)]
424 monad: bool,
425 #[arg(skip)]
427 #[serde(default)]
428 bypass_prevrandao: bool,
429}
430
431impl Serialize for NetworkConfigs {
436 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
437 use serde::ser::SerializeStruct;
438 let mut s = serializer.serialize_struct("NetworkConfigs", 3)?;
439 s.serialize_field("network", &self.resolved_network())?;
440 s.serialize_field("celo", &self.celo)?;
441 s.serialize_field("bypass_prevrandao", &self.bypass_prevrandao)?;
442 s.end()
443 }
444}
445
446impl NetworkConfigs {
447 pub fn validate(&self) -> Result<(), String> {
452 let mut selectors = Vec::new();
453 if let Some(network) = self.network {
454 selectors.push((network.name(), format!("network = \"{}\"", network.name())));
455 }
456 if self.celo {
457 selectors.push(("celo", "celo = true".to_string()));
458 }
459 #[cfg(feature = "optimism")]
460 if self.optimism {
461 selectors.push(("optimism", "optimism = true".to_string()));
462 }
463 if self.tempo {
464 selectors.push(("tempo", "tempo = true".to_string()));
465 }
466 #[cfg(feature = "monad")]
467 if self.monad {
468 selectors.push(("monad", "monad = true".to_string()));
469 }
470
471 if let Some((family, selector)) = selectors.first()
472 && let Some((_, conflicting)) =
473 selectors.iter().find(|(candidate, _)| candidate != family)
474 {
475 return Err(format!(
476 "network selectors `{selector}` and `{conflicting}` conflict; select only one \
477 network"
478 ));
479 }
480
481 Ok(())
482 }
483
484 pub fn with_ethereum() -> Self {
485 Self { network: Some(NetworkVariant::Ethereum), ..Default::default() }
486 }
487
488 pub fn with_celo() -> Self {
489 Self { celo: true, ..Default::default() }
490 }
491
492 pub fn with_tempo() -> Self {
493 Self { network: Some(NetworkVariant::Tempo), tempo: true, ..Default::default() }
494 }
495
496 #[cfg(feature = "monad")]
497 pub fn with_monad() -> Self {
498 Self { network: Some(NetworkVariant::Monad), monad: true, ..Default::default() }
499 }
500
501 pub const fn is_tempo(&self) -> bool {
502 if let Some(network) = self.resolved_network() { network.is_tempo() } else { false }
503 }
504
505 #[cfg(feature = "monad")]
506 pub const fn is_monad(&self) -> bool {
507 if let Some(network) = self.resolved_network() { network.is_monad() } else { false }
508 }
509
510 #[cfg(not(feature = "monad"))]
511 pub const fn is_monad(&self) -> bool {
512 false
513 }
514
515 pub const fn extra_cheatcode_addresses(&self) -> &'static [Address] {
517 #[cfg(feature = "monad")]
518 if self.is_monad() {
519 return MONAD_CHEATCODE_ADDRESSES;
520 }
521 &[]
522 }
523
524 pub const fn is_celo(&self) -> bool {
525 self.celo
526 }
527
528 pub const fn resolved_network(&self) -> Option<NetworkVariant> {
530 if let Some(n) = self.network {
531 return Some(n);
532 }
533 #[cfg(feature = "optimism")]
534 if self.optimism {
535 return Some(NetworkVariant::Optimism);
536 }
537 if self.tempo {
538 return Some(NetworkVariant::Tempo);
539 }
540 #[cfg(feature = "monad")]
541 if self.monad {
542 return Some(NetworkVariant::Monad);
543 }
544 None
545 }
546
547 pub const fn has_network_selection(&self) -> bool {
549 self.celo || self.resolved_network().is_some()
550 }
551
552 pub const fn execution_family_name(&self) -> &'static str {
554 self.execution_network().name()
555 }
556
557 pub const fn execution_profile_name(&self) -> &'static str {
559 if self.celo { "celo" } else { self.execution_family_name() }
560 }
561
562 pub const fn execution_network(&self) -> NetworkVariant {
564 if let Some(network) = self.resolved_network() { network } else { NetworkVariant::Ethereum }
565 }
566
567 pub fn has_same_execution_profile(&self, other: &Self) -> bool {
569 self.celo == other.celo
570 && match (self.resolved_network(), other.resolved_network()) {
571 (Some(left), Some(right)) => left == right,
572 (Some(left), None) => left.is_ethereum(),
573 (None, Some(right)) => right.is_ethereum(),
574 (None, None) => true,
575 }
576 }
577
578 pub const fn supports_fork_source(&self, source: &Self) -> bool {
584 self.is_monad() == source.is_monad()
585 }
586
587 pub fn active_network_name(&self) -> Option<&'static str> {
589 self.resolved_network().and_then(|network| network.hardfork_namespace())
590 }
591
592 #[cfg(feature = "optimism")]
597 pub fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams {
598 if self.is_optimism() {
599 return self.op_base_fee_params(timestamp);
600 }
601 BaseFeeParams::ethereum()
602 }
603
604 #[cfg(not(feature = "optimism"))]
606 pub const fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams {
607 let _ = timestamp;
608 BaseFeeParams::ethereum()
609 }
610
611 #[cfg(feature = "monad")]
613 pub fn contract_size_limits(&self) -> Option<NetworkContractSizeLimits> {
614 self.is_monad().then_some(NetworkContractSizeLimits {
615 runtime: monad_revm::MONAD_MAX_CODE_SIZE,
616 initcode: monad_revm::MONAD_MAX_INITCODE_SIZE,
617 })
618 }
619
620 #[cfg(not(feature = "monad"))]
622 pub const fn contract_size_limits(&self) -> Option<NetworkContractSizeLimits> {
623 None
624 }
625
626 pub fn bypass_prevrandao(&self, chain_id: u64) -> bool {
627 if let Ok(
628 Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet | Gnosis | Chiado,
629 ) = NamedChain::try_from(chain_id)
630 {
631 return true;
632 }
633 self.bypass_prevrandao
634 }
635
636 pub fn try_with_chain_id(self, chain_id: u64) -> Result<Self, String> {
642 if self.has_network_selection() {
643 return Ok(self);
644 }
645
646 match NetworkVariant::from_known_chain_id(chain_id).map_err(|error| {
647 format!("cannot infer execution network from chain ID {chain_id}: {error}")
648 })? {
649 Some(network) => Ok(self.with_rpc_identity(network, chain_id)),
650 None => Ok(self),
651 }
652 }
653
654 pub fn with_chain_id(self, chain_id: u64) -> Self {
659 self.try_with_chain_id(chain_id).unwrap_or(self)
660 }
661
662 pub fn with_rpc_network(self, network: NetworkVariant) -> Self {
667 let mut resolved = if network.is_ethereum() { Self::default() } else { network.into() };
668 resolved.bypass_prevrandao = self.bypass_prevrandao;
669 resolved
670 }
671
672 pub fn with_rpc_identity(self, network: NetworkVariant, chain_id: ChainId) -> Self {
678 let mut resolved = self.with_rpc_network(network);
679 if network.is_ethereum()
680 && matches!(
681 Chain::from_id(chain_id).named(),
682 Some(NamedChain::Celo | NamedChain::CeloSepolia)
683 )
684 {
685 resolved.celo = true;
686 }
687 resolved
688 }
689
690 pub fn canonical_execution_profile(self) -> Self {
692 if self.is_celo() {
693 Self::with_celo()
694 } else if let Some(network) = self.resolved_network()
695 && !network.is_ethereum()
696 {
697 network.into()
698 } else {
699 Self::default()
700 }
701 }
702
703 pub fn with_execution_profile(self, profile: Self) -> Self {
705 let mut resolved = if profile.is_celo() {
706 Self::with_celo()
707 } else {
708 profile.resolved_network().map(Into::into).unwrap_or_default()
709 };
710 resolved.bypass_prevrandao = self.bypass_prevrandao;
711 resolved
712 }
713
714 pub fn with_rpc_profile(self, profile: Self) -> Self {
716 self.with_execution_profile(profile.canonical_execution_profile())
717 }
718
719 pub fn from_node_info_profile(profile: &str) -> Result<Self, String> {
721 if profile == "celo" {
722 return Ok(Self::with_celo());
723 }
724 let network = NetworkVariant::from_node_info_name(profile)?;
725 Ok(if network.is_ethereum() { Self::default() } else { network.into() })
726 }
727
728 pub fn from_rpc_identity_profile_with_fallback(
735 chain_id: ChainId,
736 node_info_profile: Option<Option<&str>>,
737 unknown_fallback: Option<Self>,
738 ) -> Result<Option<Self>, String> {
739 let known_profile = || {
740 NetworkVariant::from_known_chain_id(chain_id).map(|network| {
741 network.map(|network| Self::default().with_rpc_identity(network, chain_id))
742 })
743 };
744 let fallback_is_explicit =
745 unknown_fallback.is_some_and(|fallback| fallback.has_network_selection());
746 let fallback = unknown_fallback.map(Self::canonical_execution_profile);
747 if let Some(Some(profile)) = node_info_profile {
748 return Self::from_node_info_profile(profile).map(Some);
749 }
750 if fallback_is_explicit && let Some(fallback) = fallback {
751 return Ok(Some(fallback));
752 }
753 match node_info_profile {
754 Some(None) => Ok(known_profile()?.or(fallback).or(Some(Self::default()))),
755 None => Ok(known_profile()?.or(fallback)),
756 Some(Some(_)) => unreachable!(),
757 }
758 }
759
760 pub fn normalize_for_hardfork(self, hardfork: FoundryHardfork) -> Result<Self, String> {
765 if self.has_network_selection() {
766 let configured = self.execution_network();
767 if configured.hardfork_namespace() != hardfork.namespace() {
768 return Err(format!(
769 "hardfork `{}` conflicts with network config `{}`",
770 String::from(hardfork),
771 self.execution_profile_name(),
772 ));
773 }
774 }
775
776 let network = match hardfork {
777 FoundryHardfork::Ethereum(_) => self,
778 FoundryHardfork::Tempo(_) => Self::with_tempo(),
779 #[cfg(feature = "optimism")]
780 FoundryHardfork::Optimism(_) => Self::with_optimism(),
781 #[cfg(feature = "monad")]
782 FoundryHardfork::Monad(_) => Self::with_monad(),
783 };
784
785 Ok(network)
786 }
787
788 pub fn inject_precompiles(self, precompiles: &mut PrecompilesMap) {
790 if self.is_celo() {
791 precompiles.apply_precompile(&CELO_TRANSFER_ADDRESS, move |_| {
792 Some(celo::transfer::precompile())
793 });
794 }
795 }
796
797 pub fn inject_chain_precompiles(
799 self,
800 precompiles: &mut PrecompilesMap,
801 chain_id: ChainId,
802 timestamp: u64,
803 ) {
804 let Some(p256verify) = bsc_p256_precompile(chain_id, timestamp) else { return };
805 precompiles.apply_precompile(&BSC_P256_ADDRESS, move |_| {
806 p256verify.map(|p256verify| {
807 DynPrecompile::new(p256verify.id().clone(), move |input| {
808 p256verify.execute(input.data, input.gas, input.reservoir)
809 })
810 })
811 });
812 }
813
814 pub fn precompiles_label(
816 self,
817 tempo_hardfork: Option<TempoHardfork>,
818 monad_hardfork: Option<MonadHardfork>,
819 ) -> AddressHashMap<String> {
820 #[cfg(not(feature = "monad"))]
821 let _ = monad_hardfork;
822 let mut labels = AddressHashMap::default();
823 if self.is_celo() {
824 labels.insert(CELO_TRANSFER_ADDRESS, CELO_TRANSFER_LABEL.to_string());
825 }
826 if self.is_tempo() {
827 labels.extend(
828 TEMPO_PRECOMPILES
829 .iter()
830 .copied()
831 .filter(|(_, address)| {
832 tempo_hardfork.is_none_or(|hardfork| {
833 is_tempo_precompile_active_at(*address, hardfork)
834 })
835 })
836 .map(|(label, address)| (address, label.to_string())),
837 );
838 }
839 #[cfg(feature = "monad")]
840 if self.is_monad() {
841 labels.extend(
842 MONAD_PRECOMPILE_LABELS
843 .iter()
844 .copied()
845 .filter(|(_, address)| {
846 monad_hardfork.is_none_or(|hardfork| {
847 is_monad_precompile_active_at(*address, hardfork)
848 })
849 })
850 .map(|(label, address)| (address, label.to_string())),
851 );
852 }
853 labels
854 }
855
856 pub fn precompiles(
858 self,
859 tempo_hardfork: Option<TempoHardfork>,
860 monad_hardfork: Option<MonadHardfork>,
861 ) -> BTreeMap<String, Address> {
862 #[cfg(not(feature = "monad"))]
863 let _ = monad_hardfork;
864 let mut precompiles = BTreeMap::new();
865 if self.is_celo() {
866 precompiles
867 .insert(PRECOMPILE_ID_CELO_TRANSFER.name().to_string(), CELO_TRANSFER_ADDRESS);
868 }
869 if self.is_tempo() {
870 precompiles.extend(
871 TEMPO_PRECOMPILES
872 .iter()
873 .copied()
874 .filter(|(_, address)| {
875 tempo_hardfork.is_none_or(|hardfork| {
876 is_tempo_precompile_active_at(*address, hardfork)
877 })
878 })
879 .map(|(label, address)| (label.to_string(), address)),
880 );
881 }
882 #[cfg(feature = "monad")]
883 if self.is_monad() {
884 precompiles.extend(
885 MONAD_PRECOMPILES
886 .iter()
887 .copied()
888 .filter(|(_, address)| {
889 monad_hardfork.is_none_or(|hardfork| {
890 is_monad_precompile_active_at(*address, hardfork)
891 })
892 })
893 .map(|(label, address)| (label.to_string(), address)),
894 );
895 }
896 precompiles
897 }
898}
899
900impl From<NetworkVariant> for NetworkConfigs {
901 fn from(network: NetworkVariant) -> Self {
902 match network {
903 NetworkVariant::Ethereum => Self::with_ethereum(),
904 NetworkVariant::Tempo => {
905 Self { network: Some(network), tempo: true, ..Default::default() }
906 }
907 #[cfg(feature = "monad")]
908 NetworkVariant::Monad => {
909 Self { network: Some(network), monad: true, ..Default::default() }
910 }
911 #[cfg(feature = "optimism")]
912 NetworkVariant::Optimism => {
913 Self { network: Some(network), optimism: true, ..Default::default() }
914 }
915 }
916 }
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922 use revm::precompile::{
923 Precompiles,
924 secp256r1::{P256VERIFY_BASE_GAS_FEE, P256VERIFY_BASE_GAS_FEE_OSAKA},
925 };
926
927 #[test]
930 fn network_variant_predicates() {
931 assert!(NetworkVariant::Ethereum.is_ethereum());
932 assert!(!NetworkVariant::Ethereum.is_tempo());
933 assert!(NetworkVariant::Tempo.is_tempo());
934 assert!(!NetworkVariant::Tempo.is_ethereum());
935
936 #[cfg(feature = "monad")]
937 {
938 assert!(!NetworkVariant::Ethereum.is_monad());
939 assert!(!NetworkVariant::Tempo.is_monad());
940 assert!(NetworkVariant::Monad.is_monad());
941 assert!(!NetworkVariant::Monad.is_ethereum());
942 assert!(!NetworkVariant::Monad.is_tempo());
943 }
944
945 #[cfg(feature = "optimism")]
946 {
947 assert!(NetworkVariant::Optimism.is_optimism());
948 assert!(!NetworkVariant::Optimism.is_ethereum());
949 assert!(!NetworkVariant::Optimism.is_tempo());
950
951 #[cfg(feature = "monad")]
952 assert!(!NetworkVariant::Optimism.is_monad());
953 }
954
955 #[cfg(all(feature = "optimism", feature = "monad"))]
956 assert!(!NetworkVariant::Monad.is_optimism());
957 }
958
959 #[test]
960 #[cfg(feature = "monad")]
961 fn fork_sources_only_isolate_monad() {
962 let mut non_monad = vec![
963 NetworkConfigs::default(),
964 NetworkConfigs::with_ethereum(),
965 NetworkConfigs::with_celo(),
966 NetworkConfigs::with_tempo(),
967 ];
968 #[cfg(feature = "optimism")]
969 non_monad.push(NetworkConfigs::with_optimism());
970
971 for execution in &non_monad {
972 for source in &non_monad {
973 assert!(execution.supports_fork_source(source));
974 }
975 assert!(!execution.supports_fork_source(&NetworkConfigs::with_monad()));
976 assert!(!NetworkConfigs::with_monad().supports_fork_source(execution));
977 }
978 assert!(NetworkConfigs::with_monad().supports_fork_source(&NetworkConfigs::with_monad()));
979 }
980
981 #[test]
982 fn known_chain_identity_does_not_guess_unknown_networks() {
983 assert_eq!(NetworkVariant::from_known_chain_id(98_765_432).unwrap(), None);
984 assert_eq!(
985 NetworkVariant::from_known_chain_id(NamedChain::Mainnet as u64).unwrap(),
986 Some(NetworkVariant::Ethereum)
987 );
988 assert_eq!(NetworkVariant::from(98_765_432), NetworkVariant::Ethereum);
989 }
990
991 #[test]
992 fn fallible_chain_id_inference_preserves_unknown_chains() {
993 let networks = NetworkConfigs { bypass_prevrandao: true, ..Default::default() };
994 assert_eq!(networks.try_with_chain_id(98_765_432).unwrap(), networks);
995 }
996
997 #[test]
998 #[cfg(not(feature = "monad"))]
999 fn fallible_chain_id_inference_rejects_disabled_monad() {
1000 for chain_id in [NamedChain::Monad as u64, NamedChain::MonadTestnet as u64] {
1001 let unavailable = "network family `monad` is not enabled in this build";
1002 let expected =
1003 format!("cannot infer execution network from chain ID {chain_id}: {unavailable}");
1004 assert_eq!(
1005 NetworkConfigs::default().try_with_chain_id(chain_id).unwrap_err(),
1006 expected
1007 );
1008 }
1009 }
1010
1011 #[test]
1012 #[cfg(not(feature = "optimism"))]
1013 fn fallible_chain_id_inference_rejects_disabled_optimism() {
1014 let chain_id = NamedChain::Optimism as u64;
1015 assert_eq!(
1016 NetworkConfigs::default().try_with_chain_id(chain_id).unwrap_err(),
1017 format!(
1018 "cannot infer execution network from chain ID {chain_id}: network family \
1019 `optimism` is not enabled in this build"
1020 )
1021 );
1022 }
1023
1024 #[test]
1025 fn explicit_ethereum_overrides_known_chain_inference() {
1026 let ethereum = NetworkConfigs::with_ethereum();
1027 for chain_id in [NamedChain::Monad as u64, NamedChain::Optimism as u64] {
1028 assert_eq!(ethereum.try_with_chain_id(chain_id).unwrap(), ethereum);
1029 }
1030 }
1031
1032 #[test]
1033 fn fallible_chain_id_inference_preserves_orthogonal_configuration() {
1034 let networks = NetworkConfigs { bypass_prevrandao: true, ..Default::default() };
1035 let inferred = networks.try_with_chain_id(NamedChain::Tempo as u64).unwrap();
1036
1037 assert!(inferred.is_tempo());
1038 assert!(inferred.bypass_prevrandao(NamedChain::Mainnet as u64));
1039 }
1040
1041 #[test]
1042 #[cfg(feature = "optimism")]
1043 fn fallible_chain_id_inference_detects_optimism() {
1044 let chain_id = NamedChain::Optimism as u64;
1045 assert_eq!(NetworkVariant::from(chain_id), NetworkVariant::Optimism);
1046 assert!(NetworkConfigs::default().try_with_chain_id(chain_id).unwrap().is_optimism());
1047 }
1048
1049 #[test]
1050 fn rpc_identity_preserves_orthogonal_configuration() {
1051 let base = NetworkConfigs { bypass_prevrandao: true, ..NetworkConfigs::default() };
1052 let ethereum = base.with_rpc_network(NetworkVariant::Ethereum);
1053 assert!(ethereum.bypass_prevrandao(NamedChain::Mainnet as u64));
1054
1055 let ethereum = NetworkConfigs::default().with_rpc_network(NetworkVariant::Ethereum);
1056 assert_eq!(ethereum, NetworkConfigs::default());
1057
1058 assert!(
1059 NetworkConfigs::default()
1060 .with_rpc_identity(NetworkVariant::Ethereum, NamedChain::Celo as u64)
1061 .is_celo()
1062 );
1063 assert!(
1064 !NetworkConfigs::default()
1065 .with_rpc_identity(NetworkVariant::Ethereum, NamedChain::Monad as u64)
1066 .is_monad()
1067 );
1068 }
1069
1070 #[test]
1071 fn rpc_metadata_overrides_known_execution_chain_identity() {
1072 assert_eq!(
1073 NetworkVariant::from_rpc_identity(NamedChain::Mainnet as u64, Some(Some("tempo")))
1074 .unwrap(),
1075 Some(NetworkVariant::Tempo)
1076 );
1077 assert_eq!(
1078 NetworkVariant::from_rpc_identity(NamedChain::Tempo as u64, Some(None)).unwrap(),
1079 Some(NetworkVariant::Tempo)
1080 );
1081 assert_eq!(NetworkVariant::from_rpc_identity(98_765_432, None).unwrap(), None);
1082 assert_eq!(
1083 NetworkVariant::from_rpc_identity(98_765_432, Some(None)).unwrap(),
1084 Some(NetworkVariant::Ethereum)
1085 );
1086 #[cfg(feature = "optimism")]
1087 assert_eq!(
1088 NetworkVariant::from_rpc_identity(NamedChain::Optimism as u64, Some(None)).unwrap(),
1089 Some(NetworkVariant::Optimism)
1090 );
1091 assert_eq!(
1092 NetworkVariant::from_rpc_identity(NamedChain::Celo as u64, Some(None)).unwrap(),
1093 Some(NetworkVariant::Ethereum)
1094 );
1095 }
1096
1097 #[test]
1098 fn rpc_profile_distinguishes_celo_from_ethereum_factory() {
1099 let custom_chain_id = 98_765_432;
1100 assert!(
1101 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1102 custom_chain_id,
1103 Some(Some("celo")),
1104 None,
1105 )
1106 .unwrap()
1107 .unwrap()
1108 .is_celo()
1109 );
1110 assert!(
1111 !NetworkConfigs::from_rpc_identity_profile_with_fallback(
1112 NamedChain::Celo as u64,
1113 Some(Some("ethereum")),
1114 None,
1115 )
1116 .unwrap()
1117 .unwrap()
1118 .is_celo()
1119 );
1120 assert!(
1121 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1122 NamedChain::Celo as u64,
1123 Some(None),
1124 None,
1125 )
1126 .unwrap()
1127 .unwrap()
1128 .is_celo()
1129 );
1130 assert!(
1131 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1132 custom_chain_id,
1133 None,
1134 Some(NetworkConfigs::with_celo()),
1135 )
1136 .unwrap()
1137 .unwrap()
1138 .is_celo()
1139 );
1140 }
1141
1142 #[test]
1143 fn rpc_profile_keeps_moonbeam_on_default_ethereum() {
1144 let profile = NetworkConfigs::from_rpc_identity_profile_with_fallback(
1145 NamedChain::Moonbeam as u64,
1146 None,
1147 None,
1148 )
1149 .unwrap()
1150 .unwrap();
1151
1152 assert_eq!(profile, NetworkConfigs::default());
1153 assert!(profile.bypass_prevrandao(NamedChain::Moonbeam as u64));
1154 }
1155
1156 #[test]
1157 fn default_fallback_still_infers_known_execution_profile() {
1158 for node_info in [None, Some(None)] {
1159 assert_eq!(
1160 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1161 NamedChain::Tempo as u64,
1162 node_info,
1163 Some(NetworkConfigs::default()),
1164 )
1165 .unwrap(),
1166 Some(NetworkConfigs::with_tempo())
1167 );
1168 }
1169 }
1170
1171 #[test]
1172 fn explicit_ethereum_overrides_known_execution_profile() {
1173 for node_info in [None, Some(None)] {
1174 assert_eq!(
1175 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1176 NamedChain::Tempo as u64,
1177 node_info,
1178 Some(NetworkConfigs::with_ethereum()),
1179 )
1180 .unwrap(),
1181 Some(NetworkConfigs::default())
1182 );
1183 }
1184 }
1185
1186 #[test]
1187 #[cfg(not(feature = "monad"))]
1188 fn explicit_ethereum_overrides_disabled_monad_without_node_info() {
1189 assert_eq!(
1190 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1191 NamedChain::Monad as u64,
1192 None,
1193 Some(NetworkConfigs::with_ethereum()),
1194 )
1195 .unwrap(),
1196 Some(NetworkConfigs::default())
1197 );
1198 }
1199
1200 #[test]
1201 #[cfg(not(feature = "monad"))]
1202 fn explicit_ethereum_overrides_disabled_monad_with_legacy_node_info() {
1203 assert_eq!(
1204 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1205 NamedChain::Monad as u64,
1206 Some(None),
1207 Some(NetworkConfigs::with_ethereum()),
1208 )
1209 .unwrap(),
1210 Some(NetworkConfigs::default())
1211 );
1212 }
1213
1214 #[test]
1215 #[cfg(not(feature = "monad"))]
1216 fn disabled_monad_without_explicit_profile_still_errors() {
1217 for node_info in [None, Some(None)] {
1218 assert_eq!(
1219 NetworkConfigs::from_rpc_identity_profile_with_fallback(
1220 NamedChain::Monad as u64,
1221 node_info,
1222 None,
1223 )
1224 .unwrap_err(),
1225 "network family `monad` is not enabled in this build"
1226 );
1227 }
1228 }
1229
1230 #[test]
1231 #[cfg(feature = "monad")]
1232 fn rpc_identity_fallback_preserves_explicit_custom_networks() {
1233 let custom_chain_id = 98_765_432;
1234 for node_info in [None, Some(None)] {
1235 assert_eq!(
1236 NetworkVariant::from_rpc_identity_with_fallback(
1237 custom_chain_id,
1238 node_info,
1239 Some(NetworkVariant::Monad),
1240 )
1241 .unwrap(),
1242 Some(NetworkVariant::Monad)
1243 );
1244 }
1245
1246 assert_eq!(
1247 NetworkVariant::from_rpc_identity_with_fallback(
1248 NamedChain::Mainnet as u64,
1249 Some(None),
1250 Some(NetworkVariant::Monad),
1251 )
1252 .unwrap(),
1253 Some(NetworkVariant::Ethereum)
1254 );
1255 assert_eq!(
1256 NetworkVariant::from_rpc_identity_with_fallback(
1257 custom_chain_id,
1258 Some(Some("tempo")),
1259 Some(NetworkVariant::Monad),
1260 )
1261 .unwrap(),
1262 Some(NetworkVariant::Tempo)
1263 );
1264 }
1265
1266 #[test]
1267 fn network_selection_distinguishes_default_and_explicit_ethereum() {
1268 assert!(!NetworkConfigs::default().has_network_selection());
1269 assert!(NetworkConfigs::with_ethereum().has_network_selection());
1270 assert!(NetworkConfigs::with_celo().has_network_selection());
1271 }
1272
1273 #[test]
1274 fn celo_uses_ethereum_factory_with_distinct_precompiles() {
1275 assert!(
1276 !NetworkConfigs::with_celo().has_same_execution_profile(&NetworkConfigs::default())
1277 );
1278 assert!(
1279 NetworkConfigs::with_ethereum().has_same_execution_profile(&NetworkConfigs::default())
1280 );
1281 assert_eq!(NetworkConfigs::with_celo().execution_family_name(), "ethereum");
1282 assert_eq!(NetworkConfigs::with_celo().execution_profile_name(), "celo");
1283 }
1284
1285 #[test]
1286 fn authoritative_execution_profile_preserves_orthogonal_settings() {
1287 let inline = NetworkConfigs { bypass_prevrandao: true, ..NetworkConfigs::with_tempo() };
1288
1289 #[cfg_attr(not(any(feature = "optimism", feature = "monad")), allow(unused_mut))]
1290 let mut profiles = vec![
1291 NetworkConfigs::with_ethereum(),
1292 NetworkConfigs::with_tempo(),
1293 NetworkConfigs::with_celo(),
1294 ];
1295 #[cfg(feature = "optimism")]
1296 profiles.push(NetworkVariant::Optimism.into());
1297 #[cfg(feature = "monad")]
1298 profiles.push(NetworkConfigs::with_monad());
1299
1300 for profile in profiles {
1301 let resolved = inline.with_execution_profile(profile);
1302 assert!(resolved.has_same_execution_profile(&profile));
1303 assert!(resolved.has_network_selection());
1304 assert!(resolved.bypass_prevrandao(NamedChain::Mainnet as u64));
1305 }
1306
1307 let ethereum = inline.with_execution_profile(NetworkConfigs::with_ethereum());
1308 assert_eq!(
1309 ethereum.try_with_chain_id(NamedChain::Tempo as u64).unwrap(),
1310 ethereum,
1311 "an authoritative Ethereum profile must prevent later endpoint inference",
1312 );
1313
1314 let rpc_ethereum = inline.with_rpc_profile(NetworkConfigs::with_ethereum());
1315 assert!(!rpc_ethereum.has_network_selection());
1316 }
1317
1318 #[test]
1319 fn chain_id_inference_preserves_explicit_networks() {
1320 assert!(
1321 NetworkConfigs::default().try_with_chain_id(NamedChain::Celo as u64).unwrap().is_celo()
1322 );
1323 let celo = NetworkConfigs { bypass_prevrandao: true, ..Default::default() }
1324 .try_with_chain_id(NamedChain::Celo as u64)
1325 .unwrap();
1326 assert!(celo.is_celo());
1327 assert!(celo.bypass_prevrandao(NamedChain::Mainnet as u64));
1328
1329 let explicit = [
1330 NetworkConfigs::with_ethereum(),
1331 NetworkConfigs::with_tempo(),
1332 NetworkConfigs::with_celo(),
1333 ];
1334 for networks in explicit {
1335 assert_eq!(networks.try_with_chain_id(NamedChain::Celo as u64).unwrap(), networks);
1336 }
1337
1338 #[cfg(feature = "monad")]
1339 {
1340 let monad = NetworkConfigs::with_monad();
1341 assert_eq!(monad.try_with_chain_id(NamedChain::Celo as u64).unwrap(), monad);
1342 }
1343 }
1344
1345 #[test]
1346 #[cfg(feature = "monad")]
1347 fn rpc_metadata_identifies_custom_monad_networks() {
1348 assert_eq!(NetworkVariant::from_node_info_name("monad").unwrap(), NetworkVariant::Monad);
1349 assert!(NetworkConfigs::default().with_rpc_network(NetworkVariant::Monad).is_monad());
1350 }
1351
1352 #[test]
1353 #[cfg(feature = "monad")]
1354 fn parses_endpoint_hardfork_in_network_namespace() {
1355 assert_eq!(
1356 NetworkVariant::Monad.parse_hardfork("MonadEight").unwrap(),
1357 FoundryHardfork::Monad(MonadHardfork::MonadEight)
1358 );
1359 }
1360
1361 #[test]
1362 fn rpc_metadata_rejects_unknown_network_family() {
1363 assert_eq!(
1364 NetworkVariant::from_node_info_name("unknown").unwrap_err(),
1365 "unsupported network family `unknown` reported by fork endpoint"
1366 );
1367 }
1368
1369 #[test]
1370 fn explicit_ethereum_families_reject_namespaced_hardforks() {
1371 #[cfg_attr(not(feature = "monad"), allow(unused_mut))]
1372 let mut incompatible = vec![FoundryHardfork::Tempo(TempoHardfork::T0)];
1373 #[cfg(feature = "monad")]
1374 incompatible.push(FoundryHardfork::Monad(MonadHardfork::MonadEight));
1375
1376 for networks in [NetworkConfigs::with_ethereum(), NetworkConfigs::with_celo()] {
1377 for hardfork in &incompatible {
1378 assert_eq!(
1379 networks.normalize_for_hardfork(*hardfork).unwrap_err(),
1380 format!(
1381 "hardfork `{}` conflicts with network config `{}`",
1382 String::from(*hardfork),
1383 networks.execution_profile_name()
1384 )
1385 );
1386 }
1387 }
1388
1389 let celo = NetworkConfigs::with_celo();
1390 assert_eq!(
1391 celo.normalize_for_hardfork(FoundryHardfork::Ethereum(
1392 foundry_evm_hardforks::EthereumHardfork::Prague
1393 ))
1394 .unwrap(),
1395 celo
1396 );
1397 }
1398
1399 #[test]
1400 fn new_tempo_flag_equivalent_to_legacy() {
1401 let via_new = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1402 let via_old = NetworkConfigs { tempo: true, ..Default::default() };
1403 assert_eq!(via_new.is_tempo(), via_old.is_tempo());
1404 assert_eq!(via_new.active_network_name(), via_old.active_network_name());
1405 assert_eq!(via_new.precompiles(None, None), via_old.precompiles(None, None));
1406 assert_eq!(via_new.precompiles_label(None, None), via_old.precompiles_label(None, None));
1407 }
1408
1409 fn bsc_p256_gas_used(chain_id: ChainId, timestamp: u64) -> Option<u64> {
1410 bsc_p256_precompile(chain_id, timestamp)
1411 .flatten()
1412 .map(|precompile| precompile.execute(&[], u64::MAX, 0).unwrap().gas_used)
1413 }
1414
1415 fn assert_bsc_p256_boundaries(chain_id: ChainId, haber_timestamp: u64, osaka_timestamp: u64) {
1416 assert!(matches!(bsc_p256_precompile(chain_id, haber_timestamp - 1), Some(None)));
1417 assert_eq!(bsc_p256_gas_used(chain_id, haber_timestamp), Some(P256VERIFY_BASE_GAS_FEE));
1418 assert_eq!(bsc_p256_gas_used(chain_id, osaka_timestamp - 1), Some(P256VERIFY_BASE_GAS_FEE));
1419 assert_eq!(
1420 bsc_p256_gas_used(chain_id, osaka_timestamp),
1421 Some(P256VERIFY_BASE_GAS_FEE_OSAKA)
1422 );
1423 }
1424
1425 #[test]
1426 fn selects_bsc_p256_at_mainnet_boundaries() {
1427 assert_bsc_p256_boundaries(
1428 BSC_MAINNET_CHAIN_ID,
1429 BSC_MAINNET_HABER_TIMESTAMP,
1430 BSC_MAINNET_OSAKA_TIMESTAMP,
1431 );
1432 }
1433
1434 #[test]
1435 fn selects_bsc_p256_at_testnet_boundaries() {
1436 assert_bsc_p256_boundaries(
1437 BSC_TESTNET_CHAIN_ID,
1438 BSC_TESTNET_HABER_TIMESTAMP,
1439 BSC_TESTNET_OSAKA_TIMESTAMP,
1440 );
1441 }
1442
1443 #[test]
1444 fn removes_bsc_p256_before_haber() {
1445 let mut precompiles = PrecompilesMap::from_static(Precompiles::osaka());
1446 assert!(precompiles.get(&BSC_P256_ADDRESS).is_some());
1447 NetworkConfigs::default().inject_chain_precompiles(
1448 &mut precompiles,
1449 BSC_MAINNET_CHAIN_ID,
1450 BSC_MAINNET_HABER_TIMESTAMP - 1,
1451 );
1452 assert!(precompiles.get(&BSC_P256_ADDRESS).is_none());
1453 }
1454
1455 #[test]
1456 fn canonical_tempo_network_reports_precompiles() {
1457 let cfg = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1458
1459 assert_eq!(
1460 cfg.precompiles(None, None).get("TIP20ChannelReserve"),
1461 Some(&TIP20_CHANNEL_RESERVE_ADDRESS)
1462 );
1463 assert!(
1464 !cfg.precompiles(Some(TempoHardfork::T4), None).contains_key("TIP20ChannelReserve")
1465 );
1466 assert!(!cfg.precompiles(Some(TempoHardfork::T4), None).contains_key("ReceivePolicyGuard"));
1467 assert!(!cfg.precompiles(Some(TempoHardfork::T2), None).contains_key("AddressRegistry"));
1468 assert!(!cfg.precompiles(Some(TempoHardfork::T2), None).contains_key("SignatureVerifier"));
1469 assert_eq!(
1470 cfg.precompiles(Some(TempoHardfork::T3), None).get("AddressRegistry"),
1471 Some(&ADDRESS_REGISTRY_ADDRESS)
1472 );
1473 assert_eq!(
1474 cfg.precompiles(Some(TempoHardfork::T3), None).get("SignatureVerifier"),
1475 Some(&SIGNATURE_VERIFIER_ADDRESS)
1476 );
1477 assert_eq!(
1478 cfg.precompiles_label(Some(TempoHardfork::T5), None)
1479 .get(&TIP20_CHANNEL_RESERVE_ADDRESS),
1480 Some(&"TIP20ChannelReserve".to_string())
1481 );
1482 assert!(cfg.precompiles_label(None, None).contains_key(&TIP20_CHANNEL_RESERVE_ADDRESS));
1483 assert!(
1484 !cfg.precompiles_label(Some(TempoHardfork::T5), None)
1485 .contains_key(&RECEIVE_POLICY_GUARD_ADDRESS)
1486 );
1487 assert!(
1488 cfg.precompiles_label(Some(TempoHardfork::T6), None)
1489 .contains_key(&RECEIVE_POLICY_GUARD_ADDRESS)
1490 );
1491 }
1492
1493 #[test]
1494 #[cfg(feature = "monad")]
1495 fn canonical_monad_network_reports_hardfork_gated_precompiles() {
1496 let cfg = NetworkConfigs { network: Some(NetworkVariant::Monad), ..Default::default() };
1497
1498 assert_eq!(
1499 cfg.precompiles(None, Some(MonadHardfork::MonadEight)).get("MonadStaking"),
1500 Some(&monad_revm::staking::STAKING_ADDRESS)
1501 );
1502 assert!(
1503 !cfg.precompiles(None, Some(MonadHardfork::MonadEight))
1504 .contains_key("MonadReserveBalance")
1505 );
1506 assert_eq!(
1507 cfg.precompiles(None, Some(MonadHardfork::MonadNine)).get("MonadReserveBalance"),
1508 Some(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
1509 );
1510 assert_eq!(
1511 cfg.precompiles_label(None, Some(MonadHardfork::MonadNine))
1512 .get(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS),
1513 Some(&"ReserveBalance".to_string())
1514 );
1515 assert!(
1516 cfg.precompiles_label(None, None)
1517 .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
1518 );
1519 assert!(
1520 !cfg.precompiles_label(None, Some(MonadHardfork::MonadEight))
1521 .contains_key(&monad_revm::reserve_balance::abi::RESERVE_BALANCE_ADDRESS)
1522 );
1523 }
1524
1525 #[test]
1526 fn storage_credits_precompile_activates_at_t7() {
1527 assert!(!is_tempo_precompile_active_at(STORAGE_CREDITS_ADDRESS, TempoHardfork::T6));
1528 assert!(is_tempo_precompile_active_at(STORAGE_CREDITS_ADDRESS, TempoHardfork::T7));
1529 assert!(TEMPO_PRECOMPILE_ADDRESSES.contains(&STORAGE_CREDITS_ADDRESS));
1530
1531 let cfg = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1533 assert!(!cfg.precompiles(Some(TempoHardfork::T6), None).contains_key("StorageCredits"));
1534 assert!(cfg.precompiles(Some(TempoHardfork::T7), None).contains_key("StorageCredits"));
1535 }
1536
1537 #[test]
1538 fn current_committee_precompile_activates_at_t8() {
1539 assert!(!is_tempo_precompile_active_at(CURRENT_COMMITTEE_ADDRESS, TempoHardfork::T7));
1540 assert!(is_tempo_precompile_active_at(CURRENT_COMMITTEE_ADDRESS, TempoHardfork::T8));
1541 assert!(TEMPO_PRECOMPILE_ADDRESSES.contains(&CURRENT_COMMITTEE_ADDRESS));
1542
1543 let cfg = NetworkConfigs { network: Some(NetworkVariant::Tempo), ..Default::default() };
1544 assert!(!cfg.precompiles(Some(TempoHardfork::T7), None).contains_key("CurrentCommittee"));
1545 assert!(cfg.precompiles(Some(TempoHardfork::T8), None).contains_key("CurrentCommittee"));
1546 }
1547
1548 #[test]
1551 fn active_network_name_tempo() {
1552 let cfg = NetworkConfigs::with_tempo();
1553 assert_eq!(cfg.active_network_name(), Some("tempo"));
1554 }
1555
1556 #[test]
1557 #[cfg(feature = "monad")]
1558 fn active_network_name_monad() {
1559 let cfg = NetworkConfigs::with_monad();
1560 assert_eq!(cfg.active_network_name(), Some("monad"));
1561 assert!(cfg.is_monad());
1562 assert_eq!(cfg.extra_cheatcode_addresses(), &[MONAD_CHEATCODE_ADDRESS]);
1563 }
1564
1565 #[test]
1566 #[cfg(feature = "monad")]
1567 fn contract_size_limits_monad() {
1568 let limits = NetworkConfigs::with_monad().contract_size_limits().unwrap();
1569 assert_eq!(limits.runtime, monad_revm::MONAD_MAX_CODE_SIZE);
1570 assert_eq!(limits.initcode, monad_revm::MONAD_MAX_INITCODE_SIZE);
1571 assert!(NetworkConfigs::default().contract_size_limits().is_none());
1572 }
1573
1574 #[test]
1575 fn active_network_name_default_is_none() {
1576 let cfg = NetworkConfigs::default();
1577 assert_eq!(cfg.active_network_name(), None);
1578 assert!(cfg.extra_cheatcode_addresses().is_empty());
1579 }
1580
1581 #[test]
1584 fn serde_roundtrip_tempo() {
1585 let original = NetworkConfigs::with_tempo();
1586 let json = serde_json::to_string(&original).unwrap();
1587 let restored: NetworkConfigs = serde_json::from_str(&json).unwrap();
1588 assert!(restored.is_tempo());
1589 }
1590
1591 #[test]
1592 fn serde_legacy_tempo_bool_deserialized() {
1593 let json = r#"{"tempo": true, "celo": false, "bypass_prevrandao": false}"#;
1595 let cfg: NetworkConfigs = serde_json::from_str(json).unwrap();
1596 assert!(cfg.is_tempo());
1597 }
1598
1599 #[test]
1600 #[cfg(feature = "monad")]
1601 fn serde_roundtrip_monad() {
1602 let original = NetworkConfigs::with_monad();
1603 let json = serde_json::to_string(&original).unwrap();
1604 let restored: NetworkConfigs = serde_json::from_str(&json).unwrap();
1605 assert!(restored.is_monad());
1606 assert!(!restored.is_tempo());
1607 }
1608
1609 #[test]
1610 #[cfg(feature = "monad")]
1611 fn serde_legacy_monad_bool_deserialized() {
1612 let json = r#"{"monad": true, "celo": false, "bypass_prevrandao": false}"#;
1613 let cfg: NetworkConfigs = serde_json::from_str(json).unwrap();
1614 assert!(cfg.is_monad());
1615 }
1616
1617 #[test]
1618 fn serde_serializes_legacy_alias_as_canonical_network() {
1619 #[cfg_attr(not(feature = "monad"), allow(unused_mut))]
1620 let mut cases = vec![(NetworkConfigs { tempo: true, ..Default::default() }, "tempo")];
1621 #[cfg(feature = "monad")]
1622 cases.push((NetworkConfigs { monad: true, ..Default::default() }, "monad"));
1623
1624 for (cfg, expected) in cases {
1625 let json = serde_json::to_value(cfg).unwrap();
1626 assert_eq!(json["network"], serde_json::json!(expected));
1627 assert!(json.get("tempo").is_none(), "legacy `tempo` key should not be serialized");
1628 assert!(json.get("monad").is_none(), "legacy `monad` key should not be serialized");
1629 }
1630 }
1631
1632 #[test]
1633 fn serde_new_network_field_deserialized() {
1634 let json_tempo = r#"{"network": "tempo", "celo": false, "bypass_prevrandao": false}"#;
1635 let cfg_tempo: NetworkConfigs = serde_json::from_str(json_tempo).unwrap();
1636 assert!(cfg_tempo.is_tempo());
1637
1638 #[cfg(feature = "monad")]
1639 {
1640 let json_monad = r#"{"network": "monad", "celo": false, "bypass_prevrandao": false}"#;
1641 let cfg_monad: NetworkConfigs = serde_json::from_str(json_monad).unwrap();
1642 assert!(cfg_monad.is_monad());
1643 }
1644 }
1645
1646 #[test]
1647 fn validates_flattened_network_selectors() {
1648 let valid = [
1649 NetworkConfigs::default(),
1650 NetworkConfigs::with_ethereum(),
1651 NetworkConfigs::with_celo(),
1652 NetworkConfigs::with_tempo(),
1653 NetworkConfigs {
1654 network: Some(NetworkVariant::Tempo),
1655 tempo: true,
1656 ..Default::default()
1657 },
1658 ];
1659 for networks in valid {
1660 networks.validate().unwrap();
1661 }
1662
1663 let conflicts = [
1664 (
1665 NetworkConfigs {
1666 network: Some(NetworkVariant::Tempo),
1667 celo: true,
1668 ..Default::default()
1669 },
1670 "network selectors `network = \"tempo\"` and `celo = true` conflict",
1671 ),
1672 (
1673 NetworkConfigs { celo: true, tempo: true, ..Default::default() },
1674 "network selectors `celo = true` and `tempo = true` conflict",
1675 ),
1676 ];
1677 for (networks, expected) in conflicts {
1678 assert!(networks.validate().unwrap_err().contains(expected));
1679 }
1680 }
1681
1682 #[test]
1683 #[cfg(feature = "monad")]
1684 fn validates_flattened_monad_network_selectors() {
1685 for networks in [
1686 NetworkConfigs::with_monad(),
1687 NetworkConfigs {
1688 network: Some(NetworkVariant::Monad),
1689 monad: true,
1690 ..Default::default()
1691 },
1692 ] {
1693 networks.validate().unwrap();
1694 }
1695
1696 let conflicts = [
1697 (
1698 NetworkConfigs {
1699 network: Some(NetworkVariant::Monad),
1700 celo: true,
1701 ..Default::default()
1702 },
1703 "network selectors `network = \"monad\"` and `celo = true` conflict",
1704 ),
1705 (
1706 NetworkConfigs {
1707 network: Some(NetworkVariant::Monad),
1708 tempo: true,
1709 ..Default::default()
1710 },
1711 "network selectors `network = \"monad\"` and `tempo = true` conflict",
1712 ),
1713 (
1714 NetworkConfigs { celo: true, monad: true, ..Default::default() },
1715 "network selectors `celo = true` and `monad = true` conflict",
1716 ),
1717 (
1718 NetworkConfigs { tempo: true, monad: true, ..Default::default() },
1719 "network selectors `tempo = true` and `monad = true` conflict",
1720 ),
1721 ];
1722 for (networks, expected) in conflicts {
1723 assert!(networks.validate().unwrap_err().contains(expected));
1724 }
1725 }
1726
1727 #[test]
1728 #[cfg(feature = "monad")]
1729 fn chain_id_detects_monad_network() {
1730 assert_eq!(NetworkVariant::from(143), NetworkVariant::Monad);
1731 assert_eq!(NetworkVariant::from(10143), NetworkVariant::Monad);
1732
1733 assert!(NetworkConfigs::default().try_with_chain_id(143).unwrap().is_monad());
1734 }
1735
1736 #[cfg(feature = "optimism")]
1737 mod optimism {
1738 use super::*;
1739
1740 #[test]
1741 fn new_optimism_flag_equivalent_to_legacy() {
1742 let via_new =
1743 NetworkConfigs { network: Some(NetworkVariant::Optimism), ..Default::default() };
1744 let via_old = NetworkConfigs { optimism: true, ..Default::default() };
1745 assert_eq!(via_new.is_optimism(), via_old.is_optimism());
1746 assert_eq!(via_new.is_tempo(), via_old.is_tempo());
1747 assert_eq!(via_new.active_network_name(), via_old.active_network_name());
1748 }
1749
1750 #[test]
1751 fn matching_optimism_selectors_are_valid() {
1752 NetworkConfigs::with_optimism().validate().unwrap();
1753 }
1754
1755 #[test]
1756 fn active_network_name_optimism() {
1757 let cfg = NetworkConfigs::with_optimism();
1758 assert_eq!(cfg.active_network_name(), Some("optimism"));
1759 }
1760
1761 #[test]
1762 fn conflicting_optimism_and_tempo_selectors_are_rejected() {
1763 let cfg = NetworkConfigs {
1764 network: Some(NetworkVariant::Optimism),
1765 tempo: true,
1766 ..Default::default()
1767 };
1768 assert_eq!(
1769 cfg.validate().unwrap_err(),
1770 "network selectors `network = \"optimism\"` and `tempo = true` conflict; select \
1771 only one network"
1772 );
1773 }
1774
1775 #[test]
1776 fn serde_roundtrip_optimism() {
1777 let original = NetworkConfigs::with_optimism();
1778 let json = serde_json::to_string(&original).unwrap();
1779 let restored: NetworkConfigs = serde_json::from_str(&json).unwrap();
1780 assert!(restored.is_optimism());
1781 assert!(!restored.is_tempo());
1782 }
1783
1784 #[test]
1785 fn serde_optimism_field_deserialized() {
1786 let json_optimism =
1787 r#"{"network": "optimism", "celo": false, "bypass_prevrandao": false}"#;
1788 let cfg_optimism: NetworkConfigs = serde_json::from_str(json_optimism).unwrap();
1789 assert!(cfg_optimism.is_optimism());
1790 }
1791 }
1792}