reth_optimism_chainspec/superchain/
configs.rs

1use crate::superchain::chain_metadata::{to_genesis_chain_config, ChainMetadata};
2use alloc::{
3    format,
4    string::{String, ToString},
5    vec::Vec,
6};
7use alloy_genesis::Genesis;
8use miniz_oxide::inflate::decompress_to_vec_zlib_with_limit;
9use tar_no_std::{CorruptDataError, TarArchiveRef};
10
11/// A genesis file can be up to 100MiB. This is a reasonable limit for the genesis file size.
12const MAX_GENESIS_SIZE: usize = 100 * 1024 * 1024; // 100MiB
13
14/// The tar file contains the chain configs and genesis files for all chains.
15const SUPER_CHAIN_CONFIGS_TAR_BYTES: &[u8] = include_bytes!("../../res/superchain-configs.tar");
16
17#[derive(Debug, thiserror::Error)]
18pub(crate) enum SuperchainConfigError {
19    #[error("Error reading archive due to corrupt data: {0}")]
20    CorruptDataError(CorruptDataError),
21    #[error("Error converting bytes to UTF-8 String: {0}")]
22    FromUtf8Error(#[from] alloc::string::FromUtf8Error),
23    #[error("Error reading file: {0}")]
24    Utf8Error(#[from] core::str::Utf8Error),
25    #[error("Error deserializing JSON: {0}")]
26    JsonError(#[from] serde_json::Error),
27    #[error("File {0} not found in archive")]
28    FileNotFound(String),
29    #[error("Error decompressing file: {0}")]
30    DecompressError(String),
31}
32
33/// Reads the [`Genesis`] from the superchain config tar file for a superchain.
34/// For example, `read_genesis_from_superchain_config("unichain", "mainnet")`.
35pub(crate) fn read_superchain_genesis(
36    name: &str,
37    environment: &str,
38) -> Result<Genesis, SuperchainConfigError> {
39    // Open the archive.
40    let archive = TarArchiveRef::new(SUPER_CHAIN_CONFIGS_TAR_BYTES)
41        .map_err(SuperchainConfigError::CorruptDataError)?;
42    // Read and decompress the genesis file.
43    let compressed_genesis_file =
44        read_file(&archive, &format!("genesis/{environment}/{name}.json.zz"))?;
45    let genesis_file =
46        decompress_to_vec_zlib_with_limit(&compressed_genesis_file, MAX_GENESIS_SIZE)
47            .map_err(|e| SuperchainConfigError::DecompressError(format!("{e}")))?;
48
49    // Load the genesis file.
50    let mut genesis: Genesis = serde_json::from_slice(&genesis_file)?;
51
52    // The "config" field is stripped (see fetch_superchain_config.sh) from the genesis file
53    // because it is not always populated. For that reason, we read the config from the chain
54    // metadata file. See: https://github.com/ethereum-optimism/superchain-registry/issues/901
55    genesis.config =
56        to_genesis_chain_config(&read_superchain_metadata(name, environment, &archive)?);
57
58    Ok(genesis)
59}
60
61/// Reads the [`ChainMetadata`] from the superchain config tar file for a superchain.
62/// For example, `read_superchain_config("unichain", "mainnet")`.
63fn read_superchain_metadata(
64    name: &str,
65    environment: &str,
66    archive: &TarArchiveRef<'_>,
67) -> Result<ChainMetadata, SuperchainConfigError> {
68    let config_file = read_file(archive, &format!("configs/{environment}/{name}.json"))?;
69    let config_content = String::from_utf8(config_file)?;
70    let chain_config: ChainMetadata = serde_json::from_str(&config_content)?;
71    Ok(chain_config)
72}
73
74/// Reads a file from the tar archive. The file path is relative to the root of the tar archive.
75fn read_file(
76    archive: &TarArchiveRef<'_>,
77    file_path: &str,
78) -> Result<Vec<u8>, SuperchainConfigError> {
79    for entry in archive.entries() {
80        if entry.filename().as_str()? == file_path {
81            return Ok(entry.data().to_vec())
82        }
83    }
84    Err(SuperchainConfigError::FileNotFound(file_path.to_string()))
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::{generated_chain_value_parser, superchain::Superchain, SUPPORTED_CHAINS};
91    use alloy_chains::NamedChain;
92    use alloy_op_hardforks::{
93        OpHardfork, BASE_MAINNET_CANYON_TIMESTAMP, BASE_MAINNET_ECOTONE_TIMESTAMP,
94        BASE_MAINNET_ISTHMUS_TIMESTAMP, BASE_MAINNET_JOVIAN_TIMESTAMP,
95        BASE_SEPOLIA_CANYON_TIMESTAMP, BASE_SEPOLIA_ECOTONE_TIMESTAMP,
96        BASE_SEPOLIA_ISTHMUS_TIMESTAMP, BASE_SEPOLIA_JOVIAN_TIMESTAMP, OP_MAINNET_CANYON_TIMESTAMP,
97        OP_MAINNET_ECOTONE_TIMESTAMP, OP_MAINNET_ISTHMUS_TIMESTAMP, OP_MAINNET_JOVIAN_TIMESTAMP,
98        OP_SEPOLIA_CANYON_TIMESTAMP, OP_SEPOLIA_ECOTONE_TIMESTAMP, OP_SEPOLIA_ISTHMUS_TIMESTAMP,
99        OP_SEPOLIA_JOVIAN_TIMESTAMP,
100    };
101    use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER;
102    use tar_no_std::TarArchiveRef;
103
104    #[test]
105    fn test_read_superchain_genesis() {
106        let genesis = read_superchain_genesis("unichain", "mainnet").unwrap();
107        assert_eq!(genesis.config.chain_id, 130);
108        assert_eq!(genesis.timestamp, 1730748359);
109        assert!(genesis.alloc.contains_key(&ADDRESS_L2_TO_L1_MESSAGE_PASSER));
110    }
111
112    #[test]
113    fn test_read_superchain_genesis_with_workaround() {
114        let genesis = read_superchain_genesis("funki", "mainnet").unwrap();
115        assert_eq!(genesis.config.chain_id, 33979);
116        assert_eq!(genesis.timestamp, 1721211095);
117        assert!(genesis.alloc.contains_key(&ADDRESS_L2_TO_L1_MESSAGE_PASSER));
118    }
119
120    #[test]
121    fn test_read_superchain_metadata() {
122        let archive = TarArchiveRef::new(SUPER_CHAIN_CONFIGS_TAR_BYTES).unwrap();
123        let chain_config = read_superchain_metadata("funki", "mainnet", &archive).unwrap();
124        assert_eq!(chain_config.chain_id, 33979);
125    }
126
127    #[test]
128    fn test_read_all_genesis_files() {
129        let archive = TarArchiveRef::new(SUPER_CHAIN_CONFIGS_TAR_BYTES).unwrap();
130        // Check that all genesis files can be read without errors.
131        for entry in archive.entries() {
132            let filename = entry
133                .filename()
134                .as_str()
135                .unwrap()
136                .split('/')
137                .map(|s| s.to_string())
138                .collect::<Vec<String>>();
139            if filename.first().unwrap().ne(&"genesis") {
140                continue
141            }
142            read_superchain_metadata(
143                &filename.get(2).unwrap().replace(".json.zz", ""),
144                filename.get(1).unwrap(),
145                &archive,
146            )
147            .unwrap();
148        }
149    }
150
151    #[test]
152    fn test_genesis_exists_for_all_available_chains() {
153        for &chain in Superchain::ALL {
154            let genesis = read_superchain_genesis(chain.name(), chain.environment());
155            assert!(
156                genesis.is_ok(),
157                "Genesis not found for chain: {}-{}",
158                chain.name(),
159                chain.environment()
160            );
161        }
162    }
163
164    #[test]
165    fn test_hardfork_timestamps() {
166        for &chain in SUPPORTED_CHAINS {
167            let metadata = generated_chain_value_parser(chain).unwrap();
168
169            match metadata.chain().named() {
170                Some(NamedChain::Optimism) => {
171                    assert_eq!(
172                        metadata.hardforks.get(OpHardfork::Jovian).unwrap().as_timestamp().unwrap(),
173                        OP_MAINNET_JOVIAN_TIMESTAMP
174                    );
175
176                    assert_eq!(
177                        metadata
178                            .hardforks
179                            .get(OpHardfork::Isthmus)
180                            .unwrap()
181                            .as_timestamp()
182                            .unwrap(),
183                        OP_MAINNET_ISTHMUS_TIMESTAMP
184                    );
185
186                    assert_eq!(
187                        metadata.hardforks.get(OpHardfork::Canyon).unwrap().as_timestamp().unwrap(),
188                        OP_MAINNET_CANYON_TIMESTAMP
189                    );
190
191                    assert_eq!(
192                        metadata
193                            .hardforks
194                            .get(OpHardfork::Ecotone)
195                            .unwrap()
196                            .as_timestamp()
197                            .unwrap(),
198                        OP_MAINNET_ECOTONE_TIMESTAMP
199                    );
200                }
201                Some(NamedChain::OptimismSepolia) => {
202                    assert_eq!(
203                        metadata.hardforks.get(OpHardfork::Jovian).unwrap().as_timestamp().unwrap(),
204                        OP_SEPOLIA_JOVIAN_TIMESTAMP
205                    );
206
207                    assert_eq!(
208                        metadata
209                            .hardforks
210                            .get(OpHardfork::Isthmus)
211                            .unwrap()
212                            .as_timestamp()
213                            .unwrap(),
214                        OP_SEPOLIA_ISTHMUS_TIMESTAMP
215                    );
216
217                    assert_eq!(
218                        metadata.hardforks.get(OpHardfork::Canyon).unwrap().as_timestamp().unwrap(),
219                        OP_SEPOLIA_CANYON_TIMESTAMP
220                    );
221
222                    assert_eq!(
223                        metadata
224                            .hardforks
225                            .get(OpHardfork::Ecotone)
226                            .unwrap()
227                            .as_timestamp()
228                            .unwrap(),
229                        OP_SEPOLIA_ECOTONE_TIMESTAMP
230                    );
231                }
232                Some(NamedChain::Base) => {
233                    assert_eq!(
234                        metadata.hardforks.get(OpHardfork::Jovian).unwrap().as_timestamp().unwrap(),
235                        BASE_MAINNET_JOVIAN_TIMESTAMP
236                    );
237
238                    assert_eq!(
239                        metadata
240                            .hardforks
241                            .get(OpHardfork::Isthmus)
242                            .unwrap()
243                            .as_timestamp()
244                            .unwrap(),
245                        BASE_MAINNET_ISTHMUS_TIMESTAMP
246                    );
247
248                    assert_eq!(
249                        metadata.hardforks.get(OpHardfork::Canyon).unwrap().as_timestamp().unwrap(),
250                        BASE_MAINNET_CANYON_TIMESTAMP
251                    );
252
253                    assert_eq!(
254                        metadata
255                            .hardforks
256                            .get(OpHardfork::Ecotone)
257                            .unwrap()
258                            .as_timestamp()
259                            .unwrap(),
260                        BASE_MAINNET_ECOTONE_TIMESTAMP
261                    );
262                }
263                Some(NamedChain::BaseSepolia) => {
264                    assert_eq!(
265                        metadata.hardforks.get(OpHardfork::Jovian).unwrap().as_timestamp().unwrap(),
266                        BASE_SEPOLIA_JOVIAN_TIMESTAMP
267                    );
268
269                    assert_eq!(
270                        metadata
271                            .hardforks
272                            .get(OpHardfork::Isthmus)
273                            .unwrap()
274                            .as_timestamp()
275                            .unwrap(),
276                        BASE_SEPOLIA_ISTHMUS_TIMESTAMP
277                    );
278
279                    assert_eq!(
280                        metadata.hardforks.get(OpHardfork::Canyon).unwrap().as_timestamp().unwrap(),
281                        BASE_SEPOLIA_CANYON_TIMESTAMP
282                    );
283
284                    assert_eq!(
285                        metadata
286                            .hardforks
287                            .get(OpHardfork::Ecotone)
288                            .unwrap()
289                            .as_timestamp()
290                            .unwrap(),
291                        BASE_SEPOLIA_ECOTONE_TIMESTAMP
292                    );
293                }
294                _ => {}
295            }
296        }
297    }
298}