1pub use crate::constants::gas::*;
2
3use crate::{
4 bootnodes::{moderato_nodes, presto_nodes},
5 hardfork::{TempoHardfork, TempoHardforks},
6};
7use alloc::{boxed::Box, sync::Arc, vec::Vec};
8use alloy_eips::eip7840::BlobParams;
9use alloy_evm::eth::spec::EthExecutorSpec;
10use alloy_genesis::Genesis;
11use alloy_primitives::{Address, B256, U256};
12use once_cell as _;
13#[cfg(not(feature = "std"))]
14use once_cell::sync::Lazy as LazyLock;
15use reth_chainspec::{
16 BaseFeeParams, Chain, ChainSpec, DepositContract, DisplayHardforks, EthChainSpec,
17 EthereumHardfork, EthereumHardforks, ForkCondition, ForkFilter, ForkId, Hardfork, Hardforks,
18 Head,
19};
20use reth_network_peers::NodeRecord;
21#[cfg(feature = "std")]
22use std::sync::LazyLock;
23use tempo_primitives::TempoHeader;
24
25pub const SYSTEM_TX_COUNT: usize = 1;
27pub const SYSTEM_TX_ADDRESSES: [Address; SYSTEM_TX_COUNT] = [Address::ZERO];
28
29#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
31#[serde(rename_all = "camelCase")]
32pub struct TempoGenesisInfo {
33 #[serde(skip_serializing_if = "Option::is_none")]
35 epoch_length: Option<u64>,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 t0_time: Option<u64>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 t1_time: Option<u64>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 t1a_time: Option<u64>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 t1b_time: Option<u64>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 t1c_time: Option<u64>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 t2_time: Option<u64>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 t3_time: Option<u64>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 t4_time: Option<u64>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 t5_time: Option<u64>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 t6_time: Option<u64>,
66}
67
68impl TempoGenesisInfo {
69 fn extract_from(genesis: &Genesis) -> Self {
71 genesis
72 .config
73 .extra_fields
74 .deserialize_as::<Self>()
75 .unwrap_or_default()
76 }
77
78 pub fn epoch_length(&self) -> Option<u64> {
79 self.epoch_length
80 }
81
82 pub fn fork_time(&self, fork: TempoHardfork) -> Option<u64> {
84 match fork {
85 TempoHardfork::Genesis => Some(0),
86 TempoHardfork::T0 => self.t0_time,
87 TempoHardfork::T1 => self.t1_time,
88 TempoHardfork::T1A => self.t1a_time,
89 TempoHardfork::T1B => self.t1b_time,
90 TempoHardfork::T1C => self.t1c_time,
91 TempoHardfork::T2 => self.t2_time,
92 TempoHardfork::T3 => self.t3_time,
93 TempoHardfork::T4 => self.t4_time,
94 TempoHardfork::T5 => self.t5_time,
95 TempoHardfork::T6 => self.t6_time,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Default)]
102pub struct TempoChainSpecParser;
103
104pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "moderato", "testnet"];
106
107#[cfg(feature = "cli")]
112pub fn chain_value_parser(s: &str) -> eyre::Result<Arc<TempoChainSpec>> {
113 Ok(match s {
114 "mainnet" => PRESTO.clone(),
115 "testnet" | "moderato" => MODERATO.clone(),
116 "dev" => DEV.clone(),
117 _ => TempoChainSpec::from_genesis(reth_cli::chainspec::parse_genesis(s)?).into(),
118 })
119}
120
121#[cfg(feature = "cli")]
122impl reth_cli::chainspec::ChainSpecParser for TempoChainSpecParser {
123 type ChainSpec = TempoChainSpec;
124
125 const SUPPORTED_CHAINS: &'static [&'static str] = SUPPORTED_CHAINS;
126
127 fn parse(s: &str) -> eyre::Result<Arc<Self::ChainSpec>> {
128 chain_value_parser(s)
129 }
130}
131
132pub fn chainspec_from_chain_id(chain_id: u64) -> Option<Arc<TempoChainSpec>> {
136 match chain_id {
137 4217 => Some(PRESTO.clone()),
138 42431 => Some(MODERATO.clone()),
139 _ => None,
140 }
141}
142
143pub static MODERATO: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
144 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/moderato.json"))
145 .expect("`./genesis/moderato.json` must be present and deserializable");
146 TempoChainSpec::from_genesis(genesis)
147 .with_default_follow_url("wss://rpc.moderato.tempo.xyz")
148 .into()
149});
150
151pub static PRESTO: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
152 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/presto.json"))
153 .expect("`./genesis/presto.json` must be present and deserializable");
154 TempoChainSpec::from_genesis(genesis)
155 .with_default_follow_url("wss://rpc.presto.tempo.xyz")
156 .into()
157});
158
159pub static DEV: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
163 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/dev.json"))
164 .expect("`./genesis/dev.json` must be present and deserializable");
165 TempoChainSpec::from_genesis(genesis).into()
166});
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct TempoChainSpec {
171 pub inner: ChainSpec<TempoHeader>,
173 pub info: TempoGenesisInfo,
174 pub default_follow_url: Option<&'static str>,
176}
177
178impl TempoChainSpec {
179 pub fn default_follow_url(&self) -> Option<&'static str> {
181 self.default_follow_url
182 }
183
184 pub fn from_genesis(genesis: Genesis) -> Self {
186 let info = TempoGenesisInfo::extract_from(&genesis);
188
189 let mut base_spec = ChainSpec::from_genesis(genesis);
191
192 let tempo_forks = TempoHardfork::VARIANTS.iter().filter_map(|&fork| {
193 info.fork_time(fork)
194 .map(|time| (fork, ForkCondition::Timestamp(time)))
195 });
196 base_spec.hardforks.extend(tempo_forks);
197
198 Self {
199 inner: base_spec.map_header(|inner| TempoHeader {
200 general_gas_limit: 0,
201 timestamp_millis_part: inner.timestamp % 1000,
202 shared_gas_limit: 0,
203 consensus_context: None,
204 inner,
205 }),
206 info,
207 default_follow_url: None,
208 }
209 }
210
211 pub fn with_default_follow_url(mut self, url: &'static str) -> Self {
213 self.default_follow_url = Some(url);
214 self
215 }
216
217 pub fn moderato() -> Self {
219 MODERATO.as_ref().clone()
220 }
221
222 pub fn mainnet() -> Self {
224 PRESTO.as_ref().clone()
225 }
226}
227
228impl From<ChainSpec> for TempoChainSpec {
231 fn from(spec: ChainSpec) -> Self {
232 Self {
233 inner: spec.map_header(|inner| TempoHeader {
234 general_gas_limit: 0,
235 timestamp_millis_part: inner.timestamp % 1000,
236 shared_gas_limit: 0,
237 consensus_context: None,
238 inner,
239 }),
240 info: TempoGenesisInfo::default(),
241 default_follow_url: None,
242 }
243 }
244}
245
246impl Hardforks for TempoChainSpec {
247 fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
248 self.inner.fork(fork)
249 }
250
251 fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
252 self.inner.forks_iter()
253 }
254
255 fn fork_id(&self, head: &Head) -> ForkId {
256 self.inner.fork_id(head)
257 }
258
259 fn latest_fork_id(&self) -> ForkId {
260 self.inner.latest_fork_id()
261 }
262
263 fn fork_filter(&self, head: Head) -> ForkFilter {
264 self.inner.fork_filter(head)
265 }
266}
267
268impl EthChainSpec for TempoChainSpec {
269 type Header = TempoHeader;
270
271 fn chain(&self) -> Chain {
272 self.inner.chain()
273 }
274
275 fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
276 self.inner.base_fee_params_at_timestamp(timestamp)
277 }
278
279 fn blob_params_at_timestamp(&self, timestamp: u64) -> Option<BlobParams> {
280 self.inner.blob_params_at_timestamp(timestamp)
281 }
282
283 fn deposit_contract(&self) -> Option<&DepositContract> {
284 self.inner.deposit_contract()
285 }
286
287 fn genesis_hash(&self) -> B256 {
288 self.inner.genesis_hash()
289 }
290
291 fn prune_delete_limit(&self) -> usize {
292 self.inner.prune_delete_limit()
293 }
294
295 fn display_hardforks(&self) -> Box<dyn core::fmt::Display> {
296 let tempo_forks = self.inner.hardforks.forks_iter().filter(|(fork, _)| {
298 !EthereumHardfork::VARIANTS
299 .iter()
300 .any(|h| h.name() == (*fork).name())
301 });
302
303 Box::new(DisplayHardforks::new(tempo_forks))
304 }
305
306 fn genesis_header(&self) -> &Self::Header {
307 self.inner.genesis_header()
308 }
309
310 fn genesis(&self) -> &Genesis {
311 self.inner.genesis()
312 }
313
314 fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
315 match self.inner.chain_id() {
316 4217 => Some(presto_nodes()),
317 42431 => Some(moderato_nodes()),
318 _ => self.inner.bootnodes(),
319 }
320 }
321
322 fn final_paris_total_difficulty(&self) -> Option<U256> {
323 self.inner.get_final_paris_total_difficulty()
324 }
325
326 fn next_block_base_fee(&self, _parent: &TempoHeader, target_timestamp: u64) -> Option<u64> {
327 Some(self.tempo_hardfork_at(target_timestamp).base_fee())
328 }
329}
330
331impl EthereumHardforks for TempoChainSpec {
332 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
333 self.inner.ethereum_fork_activation(fork)
334 }
335}
336
337impl EthExecutorSpec for TempoChainSpec {
338 fn deposit_contract_address(&self) -> Option<Address> {
339 self.inner.deposit_contract_address()
340 }
341}
342
343impl TempoHardforks for TempoChainSpec {
344 fn tempo_fork_activation(&self, fork: TempoHardfork) -> ForkCondition {
345 self.fork(fork)
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use crate::hardfork::{TempoHardfork, TempoHardforks};
352 use reth_chainspec::{ForkCondition, Hardforks};
353 use reth_cli::chainspec::ChainSpecParser as _;
354
355 #[test]
356 fn can_load_testnet() {
357 let _ = super::TempoChainSpecParser::parse("testnet")
358 .expect("the testnet chainspec must always be well formed");
359 }
360
361 #[test]
362 fn can_load_dev() {
363 let _ = super::TempoChainSpecParser::parse("dev")
364 .expect("the dev chainspec must always be well formed");
365 }
366
367 #[test]
368 fn test_tempo_chainspec_has_tempo_hardforks() {
369 let chainspec = super::TempoChainSpecParser::parse("mainnet")
370 .expect("the mainnet chainspec must always be well formed");
371
372 let activation = chainspec.tempo_fork_activation(TempoHardfork::Genesis);
374 assert_eq!(activation, ForkCondition::Timestamp(0));
375
376 let activation = chainspec.tempo_fork_activation(TempoHardfork::T0);
378 assert_eq!(activation, ForkCondition::Timestamp(0));
379 }
380
381 #[test]
382 fn test_tempo_chainspec_implements_tempo_hardforks_trait() {
383 let chainspec = super::TempoChainSpecParser::parse("mainnet")
384 .expect("the mainnet chainspec must always be well formed");
385
386 let activation = chainspec.tempo_fork_activation(TempoHardfork::T0);
388 assert_eq!(activation, ForkCondition::Timestamp(0));
389 }
390
391 #[test]
392 fn test_tempo_hardforks_in_inner_hardforks() {
393 let chainspec = super::TempoChainSpecParser::parse("mainnet")
394 .expect("the mainnet chainspec must always be well formed");
395
396 let activation = chainspec.fork(TempoHardfork::T0);
398 assert_eq!(activation, ForkCondition::Timestamp(0));
399
400 let has_genesis = chainspec
402 .forks_iter()
403 .any(|(fork, _)| fork.name() == "Genesis");
404 assert!(has_genesis, "Genesis hardfork should be in inner.hardforks");
405 }
406
407 #[test]
408 fn test_from_genesis_with_hardforks_at_zero() {
409 use alloy_genesis::Genesis;
410
411 let mut config = serde_json::Map::new();
413 config.insert("chainId".into(), 1234.into());
414 for &fork in TempoHardfork::VARIANTS {
415 if fork != TempoHardfork::Genesis {
416 let key = format!("{}Time", fork.name().to_lowercase());
417 config.insert(key, 0.into());
418 }
419 }
420 let json = serde_json::json!({ "config": config, "alloc": {} });
421 let genesis: Genesis = serde_json::from_value(json).unwrap();
422 let chainspec = super::TempoChainSpec::from_genesis(genesis);
423
424 for &fork in TempoHardfork::VARIANTS {
426 assert!(
427 chainspec.tempo_fork_activation(fork).active_at_timestamp(0),
428 "{fork:?} should be active at timestamp 0"
429 );
430 assert!(
431 chainspec
432 .tempo_fork_activation(fork)
433 .active_at_timestamp(1000),
434 "{fork:?} should be active at timestamp 1000"
435 );
436 }
437
438 let latest = *TempoHardfork::VARIANTS.last().unwrap();
440 assert_eq!(chainspec.tempo_hardfork_at(0), latest);
441 assert_eq!(chainspec.tempo_hardfork_at(1000), latest);
442 assert_eq!(chainspec.tempo_hardfork_at(u64::MAX), latest);
443 }
444
445 mod tempo_hardfork_at {
446 use super::*;
447
448 #[test]
449 fn mainnet() {
450 let cs = super::super::TempoChainSpecParser::parse("mainnet")
451 .expect("the mainnet chainspec must always be well formed");
452
453 assert_eq!(cs.tempo_hardfork_at(0), TempoHardfork::T0);
455 assert_eq!(cs.tempo_hardfork_at(1000), TempoHardfork::T0);
456 assert_eq!(cs.tempo_hardfork_at(1770908399), TempoHardfork::T0);
457
458 assert!(cs.is_t1_active_at_timestamp(1770908400));
460 assert!(cs.is_t1a_active_at_timestamp(1770908400));
461 assert_eq!(cs.tempo_hardfork_at(1770908400), TempoHardfork::T1A);
462 assert_eq!(cs.tempo_hardfork_at(1770908401), TempoHardfork::T1A);
463
464 assert!(!cs.is_t1b_active_at_timestamp(1771858799));
466 assert_eq!(cs.tempo_hardfork_at(1771858799), TempoHardfork::T1A);
467
468 assert!(cs.is_t1b_active_at_timestamp(1771858800));
470 assert_eq!(cs.tempo_hardfork_at(1771858800), TempoHardfork::T1B);
471
472 assert!(!cs.is_t1c_active_at_timestamp(1773327599));
474 assert_eq!(cs.tempo_hardfork_at(1773327599), TempoHardfork::T1B);
475
476 assert!(cs.is_t1c_active_at_timestamp(1773327600));
478 assert_eq!(cs.tempo_hardfork_at(1773327600), TempoHardfork::T1C);
479
480 assert!(!cs.is_t2_active_at_timestamp(1774965599));
482 assert_eq!(cs.tempo_hardfork_at(1774965599), TempoHardfork::T1C);
483
484 assert!(cs.is_t2_active_at_timestamp(1774965600));
486 assert_eq!(cs.tempo_hardfork_at(1774965600), TempoHardfork::T2);
487
488 assert!(!cs.is_t3_active_at_timestamp(1777298399));
490 assert_eq!(cs.tempo_hardfork_at(1777298399), TempoHardfork::T2);
491
492 assert!(cs.is_t3_active_at_timestamp(1777298400));
494 assert_eq!(cs.tempo_hardfork_at(1777298400), TempoHardfork::T3);
495
496 assert!(!cs.is_t4_active_at_timestamp(1779112799));
498 assert_eq!(cs.tempo_hardfork_at(1779112799), TempoHardfork::T3);
499
500 assert!(cs.is_t4_active_at_timestamp(1779112800));
502 assert_eq!(cs.tempo_hardfork_at(1779112800), TempoHardfork::T4);
503 assert!(!cs.is_t5_active_at_timestamp(u64::MAX));
504 assert!(!cs.is_t6_active_at_timestamp(u64::MAX));
505 assert_eq!(cs.tempo_hardfork_at(u64::MAX), TempoHardfork::T4);
506 }
507
508 #[test]
509 fn moderato() {
510 let cs = super::super::TempoChainSpecParser::parse("moderato")
511 .expect("the moderato chainspec must always be well formed");
512
513 assert_eq!(cs.tempo_hardfork_at(0), TempoHardfork::Genesis);
515 assert_eq!(cs.tempo_hardfork_at(1770303599), TempoHardfork::Genesis);
516
517 assert_eq!(cs.tempo_hardfork_at(1770303600), TempoHardfork::T1);
519 assert_eq!(cs.tempo_hardfork_at(1770303601), TempoHardfork::T1);
520
521 assert_eq!(cs.tempo_hardfork_at(1771858799), TempoHardfork::T1);
523
524 assert!(cs.is_t1a_active_at_timestamp(1771858800));
526 assert!(cs.is_t1b_active_at_timestamp(1771858800));
527 assert_eq!(cs.tempo_hardfork_at(1771858800), TempoHardfork::T1B);
528
529 assert!(!cs.is_t1c_active_at_timestamp(1773068399));
531 assert_eq!(cs.tempo_hardfork_at(1773068399), TempoHardfork::T1B);
532
533 assert!(cs.is_t1c_active_at_timestamp(1773068400));
535 assert_eq!(cs.tempo_hardfork_at(1773068400), TempoHardfork::T1C);
536
537 assert!(!cs.is_t2_active_at_timestamp(1774537199));
539 assert_eq!(cs.tempo_hardfork_at(1774537199), TempoHardfork::T1C);
540
541 assert!(cs.is_t2_active_at_timestamp(1774537200));
543 assert_eq!(cs.tempo_hardfork_at(1774537200), TempoHardfork::T2);
544
545 assert!(!cs.is_t3_active_at_timestamp(1776779999));
547 assert_eq!(cs.tempo_hardfork_at(1776779999), TempoHardfork::T2);
548
549 assert!(cs.is_t3_active_at_timestamp(1776780000));
551 assert_eq!(cs.tempo_hardfork_at(1776780000), TempoHardfork::T3);
552
553 assert!(!cs.is_t4_active_at_timestamp(1778767199));
555 assert_eq!(cs.tempo_hardfork_at(1778767199), TempoHardfork::T3);
556
557 assert!(cs.is_t4_active_at_timestamp(1778767200));
559 assert_eq!(cs.tempo_hardfork_at(1778767200), TempoHardfork::T4);
560 assert!(!cs.is_t5_active_at_timestamp(u64::MAX));
561 assert!(!cs.is_t6_active_at_timestamp(u64::MAX));
562 assert_eq!(cs.tempo_hardfork_at(u64::MAX), TempoHardfork::T4);
563 }
564
565 #[test]
566 fn testnet() {
567 let cs = super::super::TempoChainSpecParser::parse("testnet")
568 .expect("the testnet chainspec must always be well formed");
569
570 let moderato = super::super::TempoChainSpecParser::parse("moderato")
572 .expect("the moderato chainspec must always be well formed");
573 assert_eq!(cs.inner.chain(), moderato.inner.chain());
574 }
575 }
576
577 #[test]
578 #[allow(clippy::expect_fun_call)]
579 fn chainspec_from_chain_id_roundtrips_supported_chains() {
580 use reth_chainspec::EthChainSpec;
581
582 for &name in super::SUPPORTED_CHAINS {
583 let spec =
584 super::chain_value_parser(name).expect(&format!("failed to parse chain `{name}`"));
585
586 let resolved = super::chainspec_from_chain_id(spec.chain().id())
587 .expect(&format!("failed to parse chain `{name}`"));
588
589 assert_eq!(spec.chain(), resolved.chain(), "chain mismatch for {name}");
590 }
591 }
592}