1pub use crate::constants::gas::*;
2
3use crate::{
4 bootnodes::{moderato_nodes, presto_nodes},
5 hardfork::{TempoHardfork, TempoHardforks},
6 network_identity::NetworkIdentity,
7};
8use alloc::{boxed::Box, sync::Arc, vec::Vec};
9use alloy_eips::eip7840::BlobParams;
10use alloy_evm::eth::spec::EthExecutorSpec;
11use alloy_genesis::Genesis;
12use alloy_primitives::{Address, B256, U256};
13use once_cell as _;
14#[cfg(not(feature = "std"))]
15use once_cell::sync::Lazy as LazyLock;
16use reth_chainspec::{
17 BaseFeeParams, Chain, ChainSpec, DepositContract, DisplayHardforks, EthChainSpec,
18 EthereumHardfork, EthereumHardforks, ForkCondition, ForkFilter, ForkId, Hardfork, Hardforks,
19 Head,
20};
21use reth_network_peers::NodeRecord;
22#[cfg(feature = "std")]
23use std::sync::LazyLock;
24use tempo_primitives::TempoHeader;
25
26pub const SYSTEM_TX_COUNT: usize = 1;
28pub const SYSTEM_TX_ADDRESSES: [Address; SYSTEM_TX_COUNT] = [Address::ZERO];
29
30#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
32#[serde(rename_all = "camelCase")]
33pub struct TempoGenesisInfo {
34 #[serde(skip_serializing_if = "Option::is_none")]
36 epoch_length: Option<u64>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 t0_time: Option<u64>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 t1_time: Option<u64>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 t1a_time: Option<u64>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 t1b_time: Option<u64>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 t1c_time: Option<u64>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 t2_time: Option<u64>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 t3_time: Option<u64>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 t4_time: Option<u64>,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 t5_time: Option<u64>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 t6_time: Option<u64>,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 t7_time: Option<u64>,
70 #[serde(skip_serializing_if = "Option::is_none")]
72 t8_time: Option<u64>,
73}
74
75impl TempoGenesisInfo {
76 fn extract_from(genesis: &Genesis) -> Self {
78 genesis
79 .config
80 .extra_fields
81 .deserialize_as::<Self>()
82 .unwrap_or_default()
83 }
84
85 pub fn epoch_length(&self) -> Option<u64> {
86 self.epoch_length
87 }
88
89 pub fn fork_time(&self, fork: TempoHardfork) -> Option<u64> {
91 match fork {
92 TempoHardfork::Genesis => Some(0),
93 TempoHardfork::T0 => self.t0_time,
94 TempoHardfork::T1 => self.t1_time,
95 TempoHardfork::T1A => self.t1a_time,
96 TempoHardfork::T1B => self.t1b_time,
97 TempoHardfork::T1C => self.t1c_time,
98 TempoHardfork::T2 => self.t2_time,
99 TempoHardfork::T3 => self.t3_time,
100 TempoHardfork::T4 => self.t4_time,
101 TempoHardfork::T5 => self.t5_time,
102 TempoHardfork::T6 => self.t6_time,
103 TempoHardfork::T7 => self.t7_time,
104 TempoHardfork::T8 => self.t8_time,
105 }
106 }
107}
108
109#[derive(Debug, Clone, Default)]
111pub struct TempoChainSpecParser;
112
113pub const SUPPORTED_CHAINS: &[&str] = &["mainnet", "moderato", "testnet"];
115
116#[cfg(feature = "cli")]
121pub fn chain_value_parser(s: &str) -> eyre::Result<Arc<TempoChainSpec>> {
122 Ok(match s {
123 "mainnet" => PRESTO.clone(),
124 "testnet" | "moderato" => MODERATO.clone(),
125 "dev" => DEV.clone(),
126 _ => TempoChainSpec::from_genesis(reth_cli::chainspec::parse_genesis(s)?).into(),
127 })
128}
129
130#[cfg(feature = "cli")]
131impl reth_cli::chainspec::ChainSpecParser for TempoChainSpecParser {
132 type ChainSpec = TempoChainSpec;
133
134 const SUPPORTED_CHAINS: &'static [&'static str] = SUPPORTED_CHAINS;
135
136 fn parse(s: &str) -> eyre::Result<Arc<Self::ChainSpec>> {
137 chain_value_parser(s)
138 }
139}
140
141pub fn chainspec_from_chain_id(chain_id: u64) -> Option<Arc<TempoChainSpec>> {
145 match chain_id {
146 4217 => Some(PRESTO.clone()),
147 42431 => Some(MODERATO.clone()),
148 _ => None,
149 }
150}
151
152pub static MODERATO: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
153 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/moderato.json"))
154 .expect("`./genesis/moderato.json` must be present and deserializable");
155
156 TempoChainSpec::from_genesis(genesis)
157 .with_network_identity(NetworkIdentity::testnet())
158 .with_default_follow_url("wss://rpc.moderato.tempo.xyz")
159 .into()
160});
161
162pub static PRESTO: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
163 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/presto.json"))
164 .expect("`./genesis/presto.json` must be present and deserializable");
165
166 TempoChainSpec::from_genesis(genesis)
167 .with_network_identity(NetworkIdentity::mainnet())
168 .with_default_follow_url("wss://rpc.presto.tempo.xyz")
169 .into()
170});
171
172pub static DEV: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
176 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/dev.json"))
177 .expect("`./genesis/dev.json` must be present and deserializable");
178
179 TempoChainSpec::from_genesis(genesis).into()
180});
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct TempoChainSpec {
185 pub inner: ChainSpec<TempoHeader>,
187 pub info: TempoGenesisInfo,
188 pub network_identity: Option<NetworkIdentity>,
190 pub default_follow_url: Option<&'static str>,
192}
193
194impl TempoChainSpec {
195 pub fn default_follow_url(&self) -> Option<&'static str> {
197 self.default_follow_url
198 }
199
200 pub fn from_genesis(genesis: Genesis) -> Self {
202 let info = TempoGenesisInfo::extract_from(&genesis);
204
205 let mut base_spec = ChainSpec::from_genesis(genesis);
207
208 let tempo_forks = TempoHardfork::VARIANTS.iter().filter_map(|&fork| {
209 info.fork_time(fork)
210 .map(|time| (fork, ForkCondition::Timestamp(time)))
211 });
212
213 base_spec.hardforks.extend(tempo_forks);
214
215 let inner = base_spec.map_header(|inner| TempoHeader {
216 general_gas_limit: 0,
217 timestamp_millis_part: inner.timestamp % 1000,
218 shared_gas_limit: 0,
219 consensus_context: None,
220 inner,
221 });
222
223 let network_identity =
226 NetworkIdentity::from_extra_data(inner.genesis_header().inner.extra_data.as_ref()).ok();
227
228 Self {
229 inner,
230 info,
231 network_identity,
232 default_follow_url: None,
233 }
234 }
235
236 pub fn with_network_identity(mut self, identity: NetworkIdentity) -> Self {
238 self.network_identity = Some(identity);
239 self
240 }
241
242 pub fn with_default_follow_url(mut self, url: &'static str) -> Self {
244 self.default_follow_url = Some(url);
245 self
246 }
247
248 pub fn moderato() -> Self {
250 MODERATO.as_ref().clone()
251 }
252
253 pub fn mainnet() -> Self {
255 PRESTO.as_ref().clone()
256 }
257}
258
259impl From<ChainSpec> for TempoChainSpec {
262 fn from(spec: ChainSpec) -> Self {
263 let inner = spec.map_header(|inner| TempoHeader {
264 general_gas_limit: 0,
265 timestamp_millis_part: inner.timestamp % 1000,
266 shared_gas_limit: 0,
267 consensus_context: None,
268 inner,
269 });
270
271 let network_identity =
272 NetworkIdentity::from_extra_data(inner.genesis_header().inner.extra_data.as_ref()).ok();
273
274 Self {
275 inner,
276 info: TempoGenesisInfo::default(),
277 network_identity,
278 default_follow_url: None,
279 }
280 }
281}
282
283impl Hardforks for TempoChainSpec {
284 fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
285 self.inner.fork(fork)
286 }
287
288 fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
289 self.inner.forks_iter()
290 }
291
292 fn fork_id(&self, head: &Head) -> ForkId {
293 self.inner.fork_id(head)
294 }
295
296 fn latest_fork_id(&self) -> ForkId {
297 self.inner.latest_fork_id()
298 }
299
300 fn fork_filter(&self, head: Head) -> ForkFilter {
301 self.inner.fork_filter(head)
302 }
303}
304
305impl EthChainSpec for TempoChainSpec {
306 type Header = TempoHeader;
307
308 fn chain(&self) -> Chain {
309 self.inner.chain()
310 }
311
312 fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
313 self.inner.base_fee_params_at_timestamp(timestamp)
314 }
315
316 fn blob_params_at_timestamp(&self, timestamp: u64) -> Option<BlobParams> {
317 self.inner.blob_params_at_timestamp(timestamp)
318 }
319
320 fn deposit_contract(&self) -> Option<&DepositContract> {
321 self.inner.deposit_contract()
322 }
323
324 fn genesis_hash(&self) -> B256 {
325 self.inner.genesis_hash()
326 }
327
328 fn prune_delete_limit(&self) -> usize {
329 self.inner.prune_delete_limit()
330 }
331
332 fn display_hardforks(&self) -> Box<dyn core::fmt::Display> {
333 let tempo_forks = self.inner.hardforks.forks_iter().filter(|(fork, _)| {
335 !EthereumHardfork::VARIANTS
336 .iter()
337 .any(|h| h.name() == (*fork).name())
338 });
339
340 Box::new(DisplayHardforks::new(tempo_forks))
341 }
342
343 fn genesis_header(&self) -> &Self::Header {
344 self.inner.genesis_header()
345 }
346
347 fn genesis(&self) -> &Genesis {
348 self.inner.genesis()
349 }
350
351 fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
352 match self.inner.chain_id() {
353 4217 => Some(presto_nodes()),
354 42431 => Some(moderato_nodes()),
355 _ => self.inner.bootnodes(),
356 }
357 }
358
359 fn final_paris_total_difficulty(&self) -> Option<U256> {
360 self.inner.get_final_paris_total_difficulty()
361 }
362
363 fn next_block_base_fee(&self, parent: &TempoHeader, target_timestamp: u64) -> Option<u64> {
364 let target_fork = self.tempo_hardfork_at(target_timestamp);
365
366 if target_fork.is_t7() {
367 let parent_base_fee = parent
368 .inner
369 .base_fee_per_gas
370 .expect("tempo blocks are expected to have a base fee");
371 Some(tempo_t7_next_block_base_fee(
372 parent_base_fee,
373 parent.inner.gas_used,
374 ))
375 } else if target_fork.is_t1() {
376 Some(TEMPO_T1_BASE_FEE)
377 } else {
378 Some(TEMPO_T0_BASE_FEE)
379 }
380 }
381}
382
383impl EthereumHardforks for TempoChainSpec {
384 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
385 self.inner.ethereum_fork_activation(fork)
386 }
387}
388
389impl EthExecutorSpec for TempoChainSpec {
390 fn deposit_contract_address(&self) -> Option<Address> {
391 self.inner.deposit_contract_address()
392 }
393}
394
395impl TempoHardforks for TempoChainSpec {
396 fn tempo_fork_activation(&self, fork: TempoHardfork) -> ForkCondition {
397 self.fork(fork)
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use crate::{
404 hardfork::{TempoHardfork, TempoHardforks},
405 spec::{TEMPO_T1_BASE_FEE, TEMPO_T7_BASE_FEE_CAP, TEMPO_T7_BASE_FEE_FLOOR},
406 };
407 use alloy_primitives::hex;
408 use commonware_codec::Encode as _;
409 use reth_chainspec::EthChainSpec;
410 #[cfg(feature = "cli")]
411 use reth_chainspec::{ForkCondition, Hardforks};
412 #[cfg(feature = "cli")]
413 use reth_cli::chainspec::ChainSpecParser as _;
414 use tempo_primitives::Header;
415
416 #[test]
417 #[cfg(feature = "cli")]
418 fn can_load_testnet() {
419 let _ = super::TempoChainSpecParser::parse("testnet")
420 .expect("the testnet chainspec must always be well formed");
421 }
422
423 #[test]
424 #[cfg(feature = "cli")]
425 fn can_load_dev() {
426 let _ = super::TempoChainSpecParser::parse("dev")
427 .expect("the dev chainspec must always be well formed");
428 }
429
430 #[test]
431 #[cfg(feature = "cli")]
432 fn test_tempo_chainspec_has_tempo_hardforks() {
433 let chainspec = super::TempoChainSpecParser::parse("mainnet")
434 .expect("the mainnet chainspec must always be well formed");
435
436 let activation = chainspec.tempo_fork_activation(TempoHardfork::Genesis);
438 assert_eq!(activation, ForkCondition::Timestamp(0));
439
440 let activation = chainspec.tempo_fork_activation(TempoHardfork::T0);
442 assert_eq!(activation, ForkCondition::Timestamp(0));
443 }
444
445 #[test]
446 #[cfg(feature = "cli")]
447 fn test_tempo_chainspec_implements_tempo_hardforks_trait() {
448 let chainspec = super::TempoChainSpecParser::parse("mainnet")
449 .expect("the mainnet chainspec must always be well formed");
450
451 let activation = chainspec.tempo_fork_activation(TempoHardfork::T0);
453 assert_eq!(activation, ForkCondition::Timestamp(0));
454 }
455
456 #[test]
457 fn network_identity_defaults_to_genesis_extra_data() {
458 let genesis: alloy_genesis::Genesis =
459 serde_json::from_str(include_str!("./genesis/presto.json"))
460 .expect("the mainnet genesis must always be well formed");
461
462 let chainspec = super::TempoChainSpec::from_genesis(genesis);
463 let identity = chainspec
464 .network_identity
465 .expect("presto genesis contains a DKG outcome");
466
467 assert_eq!(identity.from_epoch, 0);
468 assert_eq!(
469 identity.identity.encode().as_ref(),
470 &hex!(
471 "0xa217bb85001d4dcf8e5c50136f77af88cb2cab1857279b91c6240f41cca95c4f"
472 "43f6dcab3e0dfb87dafb3ecbeb6251e90a5df2e6c47432482821cd8b84665ee4"
473 "642589d2d9628a92b03e2bbfb00e006d038cd98def76d2a41b7c228c05f5a193"
474 )
475 );
476 }
477
478 #[test]
479 #[cfg(feature = "cli")]
480 fn named_network_identities_use_compiled_identities() {
481 let moderato = super::TempoChainSpecParser::parse("testnet")
482 .expect("the moderato chainspec must always be well formed");
483 assert_eq!(
484 moderato.network_identity,
485 Some(super::NetworkIdentity::testnet())
486 );
487
488 assert_eq!(
489 moderato.network_identity,
490 Some(super::NetworkIdentity::testnet())
491 );
492
493 let presto = super::TempoChainSpecParser::parse("mainnet")
494 .expect("the mainnet chainspec must always be well formed");
495 assert_eq!(
496 presto.network_identity,
497 Some(super::NetworkIdentity::mainnet())
498 );
499 }
500
501 #[test]
502 fn network_identity_is_absent_without_genesis_dkg_outcome() {
503 let genesis: alloy_genesis::Genesis = serde_json::from_value(serde_json::json!({
504 "config": { "chainId": 1234 },
505 "alloc": {}
506 }))
507 .unwrap();
508
509 let chainspec = super::TempoChainSpec::from_genesis(genesis);
510 assert!(chainspec.network_identity.is_none());
511 }
512
513 #[test]
514 #[cfg(feature = "cli")]
515 fn test_tempo_hardforks_in_inner_hardforks() {
516 let chainspec = super::TempoChainSpecParser::parse("mainnet")
517 .expect("the mainnet chainspec must always be well formed");
518
519 let activation = chainspec.fork(TempoHardfork::T0);
521 assert_eq!(activation, ForkCondition::Timestamp(0));
522
523 let has_genesis = chainspec
525 .forks_iter()
526 .any(|(fork, _)| fork.name() == "Genesis");
527 assert!(has_genesis, "Genesis hardfork should be in inner.hardforks");
528 }
529
530 #[test]
531 fn test_from_genesis_with_hardforks_at_zero() {
532 use alloy_genesis::Genesis;
533
534 let mut config = serde_json::Map::new();
536 config.insert("chainId".into(), 1234.into());
537 for &fork in TempoHardfork::VARIANTS {
538 if fork != TempoHardfork::Genesis {
539 let key = format!("{}Time", fork.name().to_lowercase());
540 config.insert(key, 0.into());
541 }
542 }
543 let json = serde_json::json!({ "config": config, "alloc": {} });
544 let genesis: Genesis = serde_json::from_value(json).unwrap();
545 let chainspec = super::TempoChainSpec::from_genesis(genesis);
546
547 for &fork in TempoHardfork::VARIANTS {
549 assert!(
550 chainspec.tempo_fork_activation(fork).active_at_timestamp(0),
551 "{fork:?} should be active at timestamp 0"
552 );
553 assert!(
554 chainspec
555 .tempo_fork_activation(fork)
556 .active_at_timestamp(1000),
557 "{fork:?} should be active at timestamp 1000"
558 );
559 }
560
561 let latest = *TempoHardfork::VARIANTS.last().unwrap();
563 assert_eq!(chainspec.tempo_hardfork_at(0), latest);
564 assert_eq!(chainspec.tempo_hardfork_at(1000), latest);
565 assert_eq!(chainspec.tempo_hardfork_at(u64::MAX), latest);
566 }
567
568 fn header(timestamp: u64, base_fee: u64, gas_used: u64) -> super::TempoHeader {
569 super::TempoHeader {
570 inner: Header {
571 timestamp,
572 base_fee_per_gas: Some(base_fee),
573 gas_used,
574 ..Default::default()
575 },
576 ..Default::default()
577 }
578 }
579
580 fn chainspec_with_t7_at(t7_time: u64) -> super::TempoChainSpec {
581 let genesis = serde_json::json!({
582 "config": {
583 "chainId": 99999,
584 "homesteadBlock": 0,
585 "daoForkSupport": false,
586 "eip150Block": 0,
587 "eip155Block": 0,
588 "eip158Block": 0,
589 "byzantiumBlock": 0,
590 "constantinopleBlock": 0,
591 "petersburgBlock": 0,
592 "istanbulBlock": 0,
593 "berlinBlock": 0,
594 "londonBlock": 0,
595 "mergeNetsplitBlock": 0,
596 "shanghaiTime": 0,
597 "cancunTime": 0,
598 "pragueTime": 0,
599 "osakaTime": 0,
600 "terminalTotalDifficulty": 0,
601 "terminalTotalDifficultyPassed": true,
602 "t0Time": 0,
603 "t1Time": 0,
604 "t7Time": t7_time
605 },
606 "nonce": "0x42",
607 "timestamp": "0x0",
608 "extraData": "0x",
609 "gasLimit": "0x1dcd6500",
610 "difficulty": "0x0",
611 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
612 "coinbase": "0x0000000000000000000000000000000000000000",
613 "alloc": {}
614 });
615 let genesis: alloy_genesis::Genesis = serde_json::from_value(genesis).unwrap();
616 super::TempoChainSpec::from_genesis(genesis)
617 }
618
619 #[test]
620 fn next_block_base_fee_fixed_before_t7() {
621 let chainspec = chainspec_with_t7_at(10);
622 let parent = header(8, TEMPO_T1_BASE_FEE / 2, 0);
623
624 assert_eq!(
625 chainspec.next_block_base_fee(&parent, 9),
626 Some(TEMPO_T1_BASE_FEE)
627 );
628 }
629
630 #[test]
631 fn next_block_base_fee_seeds_cap_on_t7_activation() {
632 let chainspec = chainspec_with_t7_at(10);
633 let parent = header(9, TEMPO_T1_BASE_FEE, 0);
634
635 assert_eq!(
636 chainspec.next_block_base_fee(&parent, 10),
637 Some(TEMPO_T7_BASE_FEE_CAP)
638 );
639 }
640
641 #[test]
642 fn next_block_base_fee_adjusts_after_t7_activation() {
643 let chainspec = chainspec_with_t7_at(10);
644 let parent = header(10, TEMPO_T7_BASE_FEE_CAP, 0);
645
646 assert_eq!(
647 chainspec.next_block_base_fee(&parent, 11),
648 Some(TEMPO_T7_BASE_FEE_CAP * 7 / 8)
649 );
650 }
651
652 #[test]
653 fn next_block_base_fee_uses_parent_gas_used_after_t7_activation() {
654 let chainspec = chainspec_with_t7_at(10);
655 let parent = header(10, TEMPO_T7_BASE_FEE_FLOOR, 30_000_000);
656
657 assert_eq!(
658 chainspec.next_block_base_fee(&parent, 11),
659 Some(750_000_000)
660 );
661 }
662
663 #[cfg(feature = "cli")]
664 mod tempo_hardfork_at {
665 use super::*;
666
667 #[test]
668 fn mainnet() {
669 let cs = super::super::TempoChainSpecParser::parse("mainnet")
670 .expect("the mainnet chainspec must always be well formed");
671
672 assert_eq!(cs.tempo_hardfork_at(0), TempoHardfork::T0);
674 assert_eq!(cs.tempo_hardfork_at(1000), TempoHardfork::T0);
675 assert_eq!(cs.tempo_hardfork_at(1770908399), TempoHardfork::T0);
676
677 assert!(cs.is_t1_active_at_timestamp(1770908400));
679 assert!(cs.is_t1a_active_at_timestamp(1770908400));
680 assert_eq!(cs.tempo_hardfork_at(1770908400), TempoHardfork::T1A);
681 assert_eq!(cs.tempo_hardfork_at(1770908401), TempoHardfork::T1A);
682
683 assert!(!cs.is_t1b_active_at_timestamp(1771858799));
685 assert_eq!(cs.tempo_hardfork_at(1771858799), TempoHardfork::T1A);
686
687 assert!(cs.is_t1b_active_at_timestamp(1771858800));
689 assert_eq!(cs.tempo_hardfork_at(1771858800), TempoHardfork::T1B);
690
691 assert!(!cs.is_t1c_active_at_timestamp(1773327599));
693 assert_eq!(cs.tempo_hardfork_at(1773327599), TempoHardfork::T1B);
694
695 assert!(cs.is_t1c_active_at_timestamp(1773327600));
697 assert_eq!(cs.tempo_hardfork_at(1773327600), TempoHardfork::T1C);
698
699 assert!(!cs.is_t2_active_at_timestamp(1774965599));
701 assert_eq!(cs.tempo_hardfork_at(1774965599), TempoHardfork::T1C);
702
703 assert!(cs.is_t2_active_at_timestamp(1774965600));
705 assert_eq!(cs.tempo_hardfork_at(1774965600), TempoHardfork::T2);
706
707 assert!(!cs.is_t3_active_at_timestamp(1777298399));
709 assert_eq!(cs.tempo_hardfork_at(1777298399), TempoHardfork::T2);
710
711 assert!(cs.is_t3_active_at_timestamp(1777298400));
713 assert_eq!(cs.tempo_hardfork_at(1777298400), TempoHardfork::T3);
714
715 assert!(!cs.is_t4_active_at_timestamp(1779112799));
717 assert_eq!(cs.tempo_hardfork_at(1779112799), TempoHardfork::T3);
718
719 assert!(cs.is_t4_active_at_timestamp(1779112800));
721 assert_eq!(cs.tempo_hardfork_at(1779112800), TempoHardfork::T4);
722
723 assert!(!cs.is_t5_active_at_timestamp(1781013599));
725 assert_eq!(cs.tempo_hardfork_at(1781013599), TempoHardfork::T4);
726
727 assert!(cs.is_t5_active_at_timestamp(1781013600));
729 assert_eq!(cs.tempo_hardfork_at(1781013600), TempoHardfork::T5);
730 assert!(!cs.is_t6_active_at_timestamp(1781013600));
731
732 assert!(!cs.is_t6_active_at_timestamp(1782223199));
734 assert_eq!(cs.tempo_hardfork_at(1782223199), TempoHardfork::T5);
735
736 assert!(cs.is_t6_active_at_timestamp(1782223200));
738 assert_eq!(cs.tempo_hardfork_at(1782223200), TempoHardfork::T6);
739 assert_eq!(cs.tempo_hardfork_at(u64::MAX), TempoHardfork::T6);
740 }
741
742 #[test]
743 fn moderato() {
744 let cs = super::super::TempoChainSpecParser::parse("moderato")
745 .expect("the moderato chainspec must always be well formed");
746
747 assert_eq!(cs.tempo_hardfork_at(0), TempoHardfork::Genesis);
749 assert_eq!(cs.tempo_hardfork_at(1770303599), TempoHardfork::Genesis);
750
751 assert_eq!(cs.tempo_hardfork_at(1770303600), TempoHardfork::T1);
753 assert_eq!(cs.tempo_hardfork_at(1770303601), TempoHardfork::T1);
754
755 assert_eq!(cs.tempo_hardfork_at(1771858799), TempoHardfork::T1);
757
758 assert!(cs.is_t1a_active_at_timestamp(1771858800));
760 assert!(cs.is_t1b_active_at_timestamp(1771858800));
761 assert_eq!(cs.tempo_hardfork_at(1771858800), TempoHardfork::T1B);
762
763 assert!(!cs.is_t1c_active_at_timestamp(1773068399));
765 assert_eq!(cs.tempo_hardfork_at(1773068399), TempoHardfork::T1B);
766
767 assert!(cs.is_t1c_active_at_timestamp(1773068400));
769 assert_eq!(cs.tempo_hardfork_at(1773068400), TempoHardfork::T1C);
770
771 assert!(!cs.is_t2_active_at_timestamp(1774537199));
773 assert_eq!(cs.tempo_hardfork_at(1774537199), TempoHardfork::T1C);
774
775 assert!(cs.is_t2_active_at_timestamp(1774537200));
777 assert_eq!(cs.tempo_hardfork_at(1774537200), TempoHardfork::T2);
778
779 assert!(!cs.is_t3_active_at_timestamp(1776779999));
781 assert_eq!(cs.tempo_hardfork_at(1776779999), TempoHardfork::T2);
782
783 assert!(cs.is_t3_active_at_timestamp(1776780000));
785 assert_eq!(cs.tempo_hardfork_at(1776780000), TempoHardfork::T3);
786
787 assert!(!cs.is_t4_active_at_timestamp(1778767199));
789 assert_eq!(cs.tempo_hardfork_at(1778767199), TempoHardfork::T3);
790
791 assert!(cs.is_t4_active_at_timestamp(1778767200));
793 assert_eq!(cs.tempo_hardfork_at(1778767200), TempoHardfork::T4);
794
795 assert!(!cs.is_t5_active_at_timestamp(1780495199));
797 assert_eq!(cs.tempo_hardfork_at(1780495199), TempoHardfork::T4);
798
799 assert!(cs.is_t5_active_at_timestamp(1780495200));
801 assert_eq!(cs.tempo_hardfork_at(1780495200), TempoHardfork::T5);
802 assert!(!cs.is_t6_active_at_timestamp(1780495200));
803
804 assert!(!cs.is_t6_active_at_timestamp(1781791199));
806 assert_eq!(cs.tempo_hardfork_at(1781791199), TempoHardfork::T5);
807
808 assert!(cs.is_t6_active_at_timestamp(1781791200));
810 assert_eq!(cs.tempo_hardfork_at(1781791200), TempoHardfork::T6);
811 assert_eq!(cs.tempo_hardfork_at(u64::MAX), TempoHardfork::T6);
812 }
813
814 #[test]
815 fn testnet() {
816 let cs = super::super::TempoChainSpecParser::parse("testnet")
817 .expect("the testnet chainspec must always be well formed");
818
819 let moderato = super::super::TempoChainSpecParser::parse("moderato")
821 .expect("the moderato chainspec must always be well formed");
822 assert_eq!(cs.inner.chain(), moderato.inner.chain());
823 }
824 }
825
826 #[test]
827 #[cfg(feature = "cli")]
828 #[allow(clippy::expect_fun_call)]
829 fn chainspec_from_chain_id_roundtrips_supported_chains() {
830 use reth_chainspec::EthChainSpec;
831
832 for &name in super::SUPPORTED_CHAINS {
833 let spec =
834 super::chain_value_parser(name).expect(&format!("failed to parse chain `{name}`"));
835
836 let resolved = super::chainspec_from_chain_id(spec.chain().id())
837 .expect(&format!("failed to parse chain `{name}`"));
838
839 assert_eq!(spec.chain(), resolved.chain(), "chain mismatch for {name}");
840 }
841 }
842}