reth_optimism_chainspec/superchain/
configs.rs1use 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
11const MAX_GENESIS_SIZE: usize = 100 * 1024 * 1024; const 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
33pub(crate) fn read_superchain_genesis(
36 name: &str,
37 environment: &str,
38) -> Result<Genesis, SuperchainConfigError> {
39 let archive = TarArchiveRef::new(SUPER_CHAIN_CONFIGS_TAR_BYTES)
41 .map_err(SuperchainConfigError::CorruptDataError)?;
42 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 let mut genesis: Genesis = serde_json::from_slice(&genesis_file)?;
51
52 genesis.config =
56 to_genesis_chain_config(&read_superchain_metadata(name, environment, &archive)?);
57
58 Ok(genesis)
59}
60
61fn 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
74fn 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 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}