use std::{fmt, str::FromStr};
use super::chain::{
is_local, is_mainnet, is_source_chain, is_testnet, BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, LOCAL_ANVIL,
LOCAL_ANVIL_DESTINATION, SEPOLIA,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "config", derive(clap::ValueEnum))]
#[serde(rename_all = "lowercase")]
pub enum NetworkMode {
Local,
Testnet,
Mainnet,
}
impl NetworkMode {
pub const fn source_chain_id(&self) -> u64 {
match self {
Self::Local => LOCAL_ANVIL,
Self::Testnet => SEPOLIA,
Self::Mainnet => ETHEREUM_MAINNET,
}
}
pub fn destination_chain_ids(&self) -> Vec<u64> {
match self {
Self::Local => vec![LOCAL_ANVIL_DESTINATION],
Self::Testnet => vec![BASE_SEPOLIA],
Self::Mainnet => vec![BASE_MAINNET],
}
}
pub fn all_chain_ids(&self) -> Vec<u64> {
let mut ids = vec![self.source_chain_id()];
ids.extend(self.destination_chain_ids());
ids
}
pub const fn deployment_env(&self) -> &'static str {
match self {
Self::Local | Self::Testnet => "stagef",
Self::Mainnet => "prod",
}
}
pub const fn from_chain_id(chain_id: u64) -> Option<Self> {
if is_local(chain_id) {
Some(Self::Local)
} else if is_testnet(chain_id) {
Some(Self::Testnet)
} else if is_mainnet(chain_id) {
Some(Self::Mainnet)
} else {
None
}
}
pub const fn is_source(&self, chain_id: u64) -> bool {
self.source_chain_id() == chain_id
}
}
impl fmt::Display for NetworkMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Local => write!(f, "local"),
Self::Testnet => write!(f, "testnet"),
Self::Mainnet => write!(f, "mainnet"),
}
}
}
impl FromStr for NetworkMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"local" => Ok(Self::Local),
"testnet" => Ok(Self::Testnet),
"mainnet" => Ok(Self::Mainnet),
_ => Err(format!(
"invalid network mode '{}': expected 'local', 'testnet', or 'mainnet'",
s
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_chain_ids_are_correct() {
assert_eq!(NetworkMode::Local.source_chain_id(), 31337);
assert_eq!(NetworkMode::Testnet.source_chain_id(), 11155111);
assert_eq!(NetworkMode::Mainnet.source_chain_id(), 1);
}
#[test]
fn destination_chain_ids_are_correct() {
assert_eq!(NetworkMode::Local.destination_chain_ids(), vec![31338]);
assert_eq!(NetworkMode::Testnet.destination_chain_ids(), vec![84532]);
assert_eq!(NetworkMode::Mainnet.destination_chain_ids(), vec![8453]);
}
#[test]
fn all_chain_ids_includes_source_and_destinations() {
let local = NetworkMode::Local.all_chain_ids();
assert_eq!(local, vec![31337, 31338]);
let testnet = NetworkMode::Testnet.all_chain_ids();
assert_eq!(testnet, vec![11155111, 84532]);
let mainnet = NetworkMode::Mainnet.all_chain_ids();
assert_eq!(mainnet, vec![1, 8453]);
}
#[test]
fn from_chain_id_round_trip() {
assert_eq!(NetworkMode::from_chain_id(31337), Some(NetworkMode::Local));
assert_eq!(NetworkMode::from_chain_id(11155111), Some(NetworkMode::Testnet));
assert_eq!(NetworkMode::from_chain_id(1), Some(NetworkMode::Mainnet));
assert_eq!(NetworkMode::from_chain_id(31338), Some(NetworkMode::Local));
assert_eq!(NetworkMode::from_chain_id(84532), Some(NetworkMode::Testnet));
assert_eq!(NetworkMode::from_chain_id(8453), Some(NetworkMode::Mainnet));
assert_eq!(NetworkMode::from_chain_id(11155420), Some(NetworkMode::Testnet));
assert_eq!(NetworkMode::from_chain_id(421614), Some(NetworkMode::Testnet));
assert_eq!(NetworkMode::from_chain_id(10), Some(NetworkMode::Mainnet));
assert_eq!(NetworkMode::from_chain_id(42161), Some(NetworkMode::Mainnet));
assert_eq!(NetworkMode::from_chain_id(999999), None);
}
#[test]
fn deployment_env_mapping() {
assert_eq!(NetworkMode::Local.deployment_env(), "stagef");
assert_eq!(NetworkMode::Testnet.deployment_env(), "stagef");
assert_eq!(NetworkMode::Mainnet.deployment_env(), "prod");
}
#[test]
fn from_str_parsing() {
assert_eq!("local".parse::<NetworkMode>(), Ok(NetworkMode::Local));
assert_eq!("testnet".parse::<NetworkMode>(), Ok(NetworkMode::Testnet));
assert_eq!("mainnet".parse::<NetworkMode>(), Ok(NetworkMode::Mainnet));
assert_eq!("LOCAL".parse::<NetworkMode>(), Ok(NetworkMode::Local));
assert!("invalid".parse::<NetworkMode>().is_err());
}
#[test]
fn display_format() {
assert_eq!(NetworkMode::Local.to_string(), "local");
assert_eq!(NetworkMode::Testnet.to_string(), "testnet");
assert_eq!(NetworkMode::Mainnet.to_string(), "mainnet");
}
#[test]
fn source_chains_are_source() {
for network in [NetworkMode::Local, NetworkMode::Testnet, NetworkMode::Mainnet] {
assert!(
is_source_chain(network.source_chain_id()),
"{} source chain {} should be classified as source",
network,
network.source_chain_id()
);
}
}
}