superchain_primitives/
superchain.rs

1//! Superchain types.
2
3use alloc::{string::String, vec::Vec};
4use alloy_primitives::Address;
5
6use crate::ChainConfig;
7use crate::HardForkConfiguration;
8
9/// A superchain configuration.
10#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
13pub struct Superchain {
14    /// Superchain identifier, without capitalization or display changes.
15    pub name: String,
16    /// Superchain configuration file contents.
17    pub config: SuperchainConfig,
18    /// Chain IDs of chains that are part of this superchain.
19    pub chains: Vec<ChainConfig>,
20}
21
22/// A superchain configuration file format
23#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
26#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
27pub struct SuperchainConfig {
28    /// Superchain name (e.g. "Mainnet")
29    pub name: String,
30    /// Superchain L1 anchor information
31    pub l1: SuperchainL1Info,
32    /// Optional addresses for the superchain-wide default protocol versions contract.
33    pub protocol_versions_addr: Option<Address>,
34    /// Optional address for the superchain-wide default superchain config contract.
35    pub superchain_config_addr: Option<Address>,
36    /// Hardfork Configuration. These values may be overridden by individual chains.
37    #[cfg_attr(feature = "serde", serde(flatten))]
38    pub hardfork_defaults: HardForkConfiguration,
39}
40
41/// Superchain L1 anchor information
42#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
45#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
46pub struct SuperchainL1Info {
47    /// L1 chain ID
48    #[cfg_attr(feature = "serde", serde(rename = "ChainID"))]
49    pub chain_id: u64,
50    /// L1 chain public RPC endpoint
51    #[cfg_attr(feature = "serde", serde(rename = "PublicRPC"))]
52    pub public_rpc: String,
53    /// L1 chain explorer RPC endpoint
54    pub explorer: String,
55}
56
57/// Level of integration with the superchain.
58#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
59#[cfg_attr(
60    feature = "serde",
61    derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
62)]
63#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
64#[repr(u8)]
65pub enum SuperchainLevel {
66    /// Frontier chains are chains with customizations beyond the
67    /// standard OP Stack configuration and are considered "advanced".
68    Frontier = 0,
69    /// Standard chains don't have any customizations beyond the
70    /// standard OP Stack configuration and are considered "vanilla".
71    #[default]
72    Standard = 1,
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use alloy_primitives::address;
79
80    fn ref_config() -> SuperchainConfig {
81        SuperchainConfig {
82            name: "Mainnet".to_string(),
83            l1: SuperchainL1Info {
84                chain_id: 1,
85                public_rpc: "https://ethereum-rpc.publicnode.com".to_string(),
86                explorer: "https://etherscan.io".to_string(),
87            },
88            protocol_versions_addr: Some(address!("8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935")),
89            superchain_config_addr: Some(address!("95703e0982140D16f8ebA6d158FccEde42f04a4C")),
90            hardfork_defaults: HardForkConfiguration::default(),
91        }
92    }
93
94    #[test]
95    fn test_superchain_l1_info_serde() {
96        let l1_str = r#"{
97            "ChainID": 1,
98            "PublicRPC": "https://ethereum-rpc.publicnode.com",
99            "Explorer": "https://etherscan.io"
100          }"#;
101        let l1: SuperchainL1Info = serde_json::from_str(l1_str).unwrap();
102        assert_eq!(
103            l1,
104            SuperchainL1Info {
105                chain_id: 1,
106                public_rpc: "https://ethereum-rpc.publicnode.com".to_string(),
107                explorer: "https://etherscan.io".to_string(),
108            }
109        );
110    }
111
112    #[test]
113    fn test_superchain_config_serde() {
114        let cfg_str = r#"{
115            "Name": "Mainnet",
116            "L1": {
117              "ChainID": 1,
118              "PublicRPC": "https://ethereum-rpc.publicnode.com",
119              "Explorer": "https://etherscan.io"
120            },
121            "ProtocolVersionsAddr": "0x8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935",
122            "SuperchainConfigAddr": "0x95703e0982140D16f8ebA6d158FccEde42f04a4C"
123          }"#;
124        let cfg: SuperchainConfig = serde_json::from_str(cfg_str).unwrap();
125        assert_eq!(cfg, ref_config());
126    }
127
128    #[test]
129    fn test_superchain_serde() {
130        let superchain_str = r#"{
131            "name": "Mainnet",
132            "config": {
133              "Name": "Mainnet",
134              "L1": {
135                "ChainID": 1,
136                "PublicRPC": "https://ethereum-rpc.publicnode.com",
137                "Explorer": "https://etherscan.io"
138              },
139              "ProtocolVersionsAddr": "0x8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935",
140              "SuperchainConfigAddr": "0x95703e0982140D16f8ebA6d158FccEde42f04a4C"
141            },
142            "chains": []
143          }"#;
144        let superchain: Superchain = serde_json::from_str(superchain_str).unwrap();
145        assert_eq!(superchain.name, "Mainnet");
146        assert_eq!(superchain.config, ref_config());
147        assert!(superchain.chains.is_empty());
148    }
149}