tempo_chainspec/
hardfork.rs

1//! Tempo-specific hardfork definitions and traits.
2//!
3//! This module provides the infrastructure for managing hardfork transitions in Tempo.
4//!
5//! ## Adding a New Hardfork
6//!
7//! When a new hardfork is needed (e.g., `Vivace`):
8//!
9//! ### In `hardfork.rs`:
10//! 1. Add a new variant to `TempoHardfork` enum
11//! 2. Add `is_vivace()` method to `TempoHardfork` impl
12//! 3. Add `is_vivace_active_at_timestamp()` to `TempoHardforks` trait
13//! 4. Update `tempo_hardfork_at()` to check for the new hardfork first (latest hardfork is checked first)
14//! 5. Add `TempoHardfork::Vivace => Self::OSAKA` (or appropriate SpecId) in `From<TempoHardfork> for SpecId`
15//! 6. Update `From<SpecId> for TempoHardfork` to check for the new hardfork first
16//! 7. Add test `test_is_vivace` and update existing `is_*` tests to include the new variant
17//!
18//! ### In `spec.rs`:
19//! 8. Add `vivace_time: Option<u64>` field to `TempoGenesisInfo`
20//! 9. Extract `vivace_time` in `TempoChainSpec::from_genesis`
21//! 10. Add `(TempoHardfork::Vivace, vivace_time)` to `tempo_forks` vec
22//! 11. Update tests to include `"vivaceTime": <timestamp>` in genesis JSON
23//!
24//! ### In genesis files and generator:
25//! 12. Add `"vivaceTime": 0` to `genesis/dev.json`
26//! 13. Add `vivace_time: Option<u64>` arg to `xtask/src/genesis_args.rs`
27//! 14. Add insertion of `"vivaceTime"` to chain_config.extra_fields
28//!
29//! ## Current State
30//!
31//! The `Adagio` variant is a placeholder representing the pre-hardfork baseline.
32
33use alloy_hardforks::hardfork;
34use reth_chainspec::{EthereumHardforks, ForkCondition};
35use reth_ethereum::evm::revm::primitives::hardfork::SpecId;
36
37hardfork!(
38    /// Tempo-specific hardforks for network upgrades.
39    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40    #[derive(Default)]
41    TempoHardfork {
42        /// Placeholder representing the baseline (pre-hardfork) state.
43        Adagio,
44        /// Testnet hardforks for Andantino. To be removed before mainnet launch.
45        Moderato,
46        /// Allegretto hardfork.
47        #[default]
48        Allegretto,
49        /// Allegro-Moderato hardfork.
50        AllegroModerato,
51    }
52);
53
54impl TempoHardfork {
55    /// Returns `true` if this hardfork is Moderato or later.
56    #[inline]
57    pub fn is_moderato(self) -> bool {
58        self >= Self::Moderato
59    }
60
61    /// Returns `true` if this hardfork is Allegretto or later.
62    pub fn is_allegretto(self) -> bool {
63        self >= Self::Allegretto
64    }
65
66    /// Returns `true` if this hardfork is Allegro-Moderato or later.
67    pub fn is_allegro_moderato(self) -> bool {
68        self >= Self::AllegroModerato
69    }
70}
71
72/// Trait for querying Tempo-specific hardfork activations.
73pub trait TempoHardforks: EthereumHardforks {
74    /// Retrieves activation condition for a Tempo-specific hardfork
75    fn tempo_fork_activation(&self, fork: TempoHardfork) -> ForkCondition;
76
77    /// Convenience method to check if Adagio hardfork is active at a given timestamp
78    fn is_adagio_active_at_timestamp(&self, timestamp: u64) -> bool {
79        self.tempo_fork_activation(TempoHardfork::Adagio)
80            .active_at_timestamp(timestamp)
81    }
82
83    /// Convenience method to check if Andantino hardfork is active at a given timestamp
84    fn is_moderato_active_at_timestamp(&self, timestamp: u64) -> bool {
85        self.tempo_fork_activation(TempoHardfork::Moderato)
86            .active_at_timestamp(timestamp)
87    }
88
89    /// Convenience method to check if Allegretto hardfork is active at a given timestamp
90    fn is_allegretto_active_at_timestamp(&self, timestamp: u64) -> bool {
91        self.tempo_fork_activation(TempoHardfork::Allegretto)
92            .active_at_timestamp(timestamp)
93    }
94
95    /// Convenience method to check if Allegro-Moderato hardfork is active at a given timestamp
96    fn is_allegro_moderato_active_at_timestamp(&self, timestamp: u64) -> bool {
97        self.tempo_fork_activation(TempoHardfork::AllegroModerato)
98            .active_at_timestamp(timestamp)
99    }
100
101    /// Retrieves the latest Tempo hardfork active at a given timestamp.
102    fn tempo_hardfork_at(&self, timestamp: u64) -> TempoHardfork {
103        if self.is_allegro_moderato_active_at_timestamp(timestamp) {
104            TempoHardfork::AllegroModerato
105        } else if self.is_allegretto_active_at_timestamp(timestamp) {
106            TempoHardfork::Allegretto
107        } else if self.is_moderato_active_at_timestamp(timestamp) {
108            TempoHardfork::Moderato
109        } else {
110            TempoHardfork::Adagio
111        }
112    }
113}
114
115impl From<TempoHardfork> for SpecId {
116    fn from(value: TempoHardfork) -> Self {
117        match value {
118            TempoHardfork::Adagio => Self::OSAKA,
119            TempoHardfork::Moderato => Self::OSAKA,
120            TempoHardfork::Allegretto => Self::OSAKA,
121            TempoHardfork::AllegroModerato => Self::OSAKA,
122        }
123    }
124}
125
126impl From<SpecId> for TempoHardfork {
127    /// Maps a [`SpecId`] to the *latest compatible* [`TempoHardfork`].
128    ///
129    /// Note: this is intentionally not a strict inverse of
130    /// `From<TempoHardfork> for SpecId`, because multiple Tempo
131    /// hardforks may share the same underlying EVM spec.
132    fn from(spec: SpecId) -> Self {
133        if spec.is_enabled_in(SpecId::from(Self::AllegroModerato)) {
134            Self::AllegroModerato
135        } else if spec.is_enabled_in(SpecId::from(Self::Allegretto)) {
136            Self::Allegretto
137        } else if spec.is_enabled_in(SpecId::from(Self::Moderato)) {
138            Self::Moderato
139        } else {
140            Self::Adagio
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use reth_chainspec::Hardfork;
149
150    #[test]
151    fn test_adagio_hardfork_name() {
152        let fork = TempoHardfork::Adagio;
153        assert_eq!(fork.name(), "Adagio");
154    }
155
156    #[test]
157    fn test_hardfork_trait_implementation() {
158        let fork = TempoHardfork::Adagio;
159        // Should implement Hardfork trait
160        let _name: &str = Hardfork::name(&fork);
161    }
162
163    #[test]
164    #[cfg(feature = "serde")]
165    fn test_tempo_hardfork_serde() {
166        let fork = TempoHardfork::Adagio;
167
168        // Serialize to JSON
169        let json = serde_json::to_string(&fork).unwrap();
170        assert_eq!(json, "\"Adagio\"");
171
172        // Deserialize from JSON
173        let deserialized: TempoHardfork = serde_json::from_str(&json).unwrap();
174        assert_eq!(deserialized, fork);
175    }
176
177    #[test]
178    fn test_is_moderato() {
179        assert!(!TempoHardfork::Adagio.is_moderato());
180        assert!(TempoHardfork::Moderato.is_moderato());
181        assert!(TempoHardfork::Allegretto.is_moderato());
182        assert!(TempoHardfork::AllegroModerato.is_moderato());
183    }
184
185    #[test]
186    fn test_is_allegretto() {
187        assert!(!TempoHardfork::Adagio.is_allegretto());
188        assert!(!TempoHardfork::Moderato.is_allegretto());
189
190        assert!(TempoHardfork::Allegretto.is_allegretto());
191        assert!(TempoHardfork::AllegroModerato.is_allegretto());
192
193        assert!(TempoHardfork::Allegretto.is_moderato());
194    }
195
196    #[test]
197    fn test_is_allegro_moderato() {
198        assert!(!TempoHardfork::Adagio.is_allegro_moderato());
199        assert!(!TempoHardfork::Moderato.is_allegro_moderato());
200        assert!(!TempoHardfork::Allegretto.is_allegro_moderato());
201
202        assert!(TempoHardfork::AllegroModerato.is_allegro_moderato());
203
204        assert!(TempoHardfork::AllegroModerato.is_allegretto());
205        assert!(TempoHardfork::AllegroModerato.is_moderato());
206    }
207}