diff --git a/crates/chainspec/src/genesis/dev.json b/crates/chainspec/src/genesis/dev.json index e49efa3dda..a0aff2942a 100644 --- a/crates/chainspec/src/genesis/dev.json +++ b/crates/chainspec/src/genesis/dev.json @@ -23,6 +23,7 @@ "epochLength": 302400, "moderatoTime": 0, "allegrettoTime": 0, + "allegroModeratoTime": 0, "depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa" }, "nonce": "0x42", diff --git a/crates/chainspec/src/hardfork.rs b/crates/chainspec/src/hardfork.rs index 9ed4e2c19b..d43be45d3e 100644 --- a/crates/chainspec/src/hardfork.rs +++ b/crates/chainspec/src/hardfork.rs @@ -2,15 +2,29 @@ //! //! This module provides the infrastructure for managing hardfork transitions in Tempo. //! -//! ## Usage +//! ## Adding a New Hardfork //! -//! When a new hardfork is needed: -//! 1. Add a new variant to `TempoHardfork` (e.g., `Allegro`, `Vivace`) -//! 2. Add a field to `TempoGenesisInfo` in `spec.rs` (e.g., `allegro_time: Option`) -//! 3. Add the hardfork to the `tempo_hardfork_opts` array in `TempoChainSpec::from_genesis` -//! 4. Add a convenience method to `TempoHardforks` trait (optional, for ergonomics) -//! 5. Update genesis files with the activation timestamp (e.g., `"allegroTime": 1234567890`) -//! 6. Use hardfork checks in the EVM handler and precompiles to gate new features +//! When a new hardfork is needed (e.g., `Vivace`): +//! +//! ### In `hardfork.rs`: +//! 1. Add a new variant to `TempoHardfork` enum +//! 2. Add `is_vivace()` method to `TempoHardfork` impl +//! 3. Add `is_vivace_active_at_timestamp()` to `TempoHardforks` trait +//! 4. Update `tempo_hardfork_at()` to check for the new hardfork first (latest hardfork is checked first) +//! 5. Add `TempoHardfork::Vivace => Self::OSAKA` (or appropriate SpecId) in `From for SpecId` +//! 6. Update `From for TempoHardfork` to check for the new hardfork first +//! 7. Add test `test_is_vivace` and update existing `is_*` tests to include the new variant +//! +//! ### In `spec.rs`: +//! 8. Add `vivace_time: Option` field to `TempoGenesisInfo` +//! 9. Extract `vivace_time` in `TempoChainSpec::from_genesis` +//! 10. Add `(TempoHardfork::Vivace, vivace_time)` to `tempo_forks` vec +//! 11. Update tests to include `"vivaceTime": ` in genesis JSON +//! +//! ### In genesis files and generator: +//! 12. Add `"vivaceTime": 0` to `genesis/dev.json` +//! 13. Add `vivace_time: Option` arg to `xtask/src/genesis_args.rs` +//! 14. Add insertion of `"vivaceTime"` to chain_config.extra_fields //! //! ## Current State //! @@ -32,6 +46,8 @@ hardfork!( Moderato, /// Allegretto hardfork. Allegretto, + /// Allegro-Moderato hardfork. + AllegroModerato, } ); @@ -46,6 +62,11 @@ impl TempoHardfork { pub fn is_allegretto(self) -> bool { self >= Self::Allegretto } + + /// Returns `true` if this hardfork is Allegro-Moderato or later. + pub fn is_allegro_moderato(self) -> bool { + self >= Self::AllegroModerato + } } /// Trait for querying Tempo-specific hardfork activations. @@ -71,9 +92,17 @@ pub trait TempoHardforks: EthereumHardforks { .active_at_timestamp(timestamp) } + /// Convenience method to check if Allegro-Moderato hardfork is active at a given timestamp + fn is_allegro_moderato_active_at_timestamp(&self, timestamp: u64) -> bool { + self.tempo_fork_activation(TempoHardfork::AllegroModerato) + .active_at_timestamp(timestamp) + } + /// Retrieves the latest Tempo hardfork active at a given timestamp. fn tempo_hardfork_at(&self, timestamp: u64) -> TempoHardfork { - if self.is_allegretto_active_at_timestamp(timestamp) { + if self.is_allegro_moderato_active_at_timestamp(timestamp) { + TempoHardfork::AllegroModerato + } else if self.is_allegretto_active_at_timestamp(timestamp) { TempoHardfork::Allegretto } else if self.is_moderato_active_at_timestamp(timestamp) { TempoHardfork::Moderato @@ -89,6 +118,7 @@ impl From for SpecId { TempoHardfork::Adagio => Self::OSAKA, TempoHardfork::Moderato => Self::OSAKA, TempoHardfork::Allegretto => Self::OSAKA, + TempoHardfork::AllegroModerato => Self::OSAKA, } } } @@ -100,7 +130,9 @@ impl From for TempoHardfork { /// `From for SpecId`, because multiple Tempo /// hardforks may share the same underlying EVM spec. fn from(spec: SpecId) -> Self { - if spec.is_enabled_in(SpecId::from(Self::Allegretto)) { + if spec.is_enabled_in(SpecId::from(Self::AllegroModerato)) { + Self::AllegroModerato + } else if spec.is_enabled_in(SpecId::from(Self::Allegretto)) { Self::Allegretto } else if spec.is_enabled_in(SpecId::from(Self::Moderato)) { Self::Moderato @@ -147,6 +179,7 @@ mod tests { assert!(!TempoHardfork::Adagio.is_moderato()); assert!(TempoHardfork::Moderato.is_moderato()); assert!(TempoHardfork::Allegretto.is_moderato()); + assert!(TempoHardfork::AllegroModerato.is_moderato()); } #[test] @@ -155,7 +188,20 @@ mod tests { assert!(!TempoHardfork::Moderato.is_allegretto()); assert!(TempoHardfork::Allegretto.is_allegretto()); + assert!(TempoHardfork::AllegroModerato.is_allegretto()); assert!(TempoHardfork::Allegretto.is_moderato()); } + + #[test] + fn test_is_allegro_moderato() { + assert!(!TempoHardfork::Adagio.is_allegro_moderato()); + assert!(!TempoHardfork::Moderato.is_allegro_moderato()); + assert!(!TempoHardfork::Allegretto.is_allegro_moderato()); + + assert!(TempoHardfork::AllegroModerato.is_allegro_moderato()); + + assert!(TempoHardfork::AllegroModerato.is_allegretto()); + assert!(TempoHardfork::AllegroModerato.is_moderato()); + } } diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 13157e8a78..c16c297518 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -35,6 +35,10 @@ pub struct TempoGenesisInfo { #[serde(skip_serializing_if = "Option::is_none")] allegretto_time: Option, + /// Timestamp of Allegro-Moderato hardfork activation + #[serde(skip_serializing_if = "Option::is_none")] + allegro_moderato_time: Option, + /// The epoch length used by consensus. #[serde(skip_serializing_if = "Option::is_none")] epoch_length: Option, @@ -131,6 +135,7 @@ impl TempoChainSpec { adagio_time, moderato_time, allegretto_time, + allegro_moderato_time, .. } = TempoGenesisInfo::extract_from(&genesis); @@ -141,6 +146,7 @@ impl TempoChainSpec { (TempoHardfork::Adagio, adagio_time), (TempoHardfork::Moderato, moderato_time), (TempoHardfork::Allegretto, allegretto_time), + (TempoHardfork::AllegroModerato, allegro_moderato_time), ] .into_iter() .filter_map(|(fork, time)| time.map(|time| (fork, ForkCondition::Timestamp(time)))); @@ -360,6 +366,7 @@ mod tests { "adagioTime": 1000, "moderatoTime": 2000, "allegrettoTime": 3000, + "allegroModeratoTime": 4000, }, "alloc": {} }); @@ -443,6 +450,39 @@ mod tests { chainspec.is_allegretto_active_at_timestamp(4000), "Allegretto should be active after its activation timestamp" ); + + // Test AllegroModerato activation + let activation = chainspec.fork(TempoHardfork::AllegroModerato); + assert_eq!( + activation, + ForkCondition::Timestamp(4000), + "AllegroModerato should be activated at the parsed timestamp from extra_fields" + ); + + assert!( + !chainspec.is_allegro_moderato_active_at_timestamp(0), + "AllegroModerato should not be active before its activation timestamp" + ); + assert!( + !chainspec.is_allegro_moderato_active_at_timestamp(1000), + "AllegroModerato should not be active at Adagio's activation timestamp" + ); + assert!( + !chainspec.is_allegro_moderato_active_at_timestamp(2000), + "AllegroModerato should not be active at Moderato's activation timestamp" + ); + assert!( + !chainspec.is_allegro_moderato_active_at_timestamp(3000), + "AllegroModerato should not be active at Allegretto's activation timestamp" + ); + assert!( + chainspec.is_allegro_moderato_active_at_timestamp(4000), + "AllegroModerato should be active at its activation timestamp" + ); + assert!( + chainspec.is_allegro_moderato_active_at_timestamp(5000), + "AllegroModerato should be active after its activation timestamp" + ); } #[test] @@ -532,7 +572,8 @@ mod tests { "cancunTime": 0, "adagioTime": 1000, "moderatoTime": 2000, - "allegrettoTime": 3000 + "allegrettoTime": 3000, + "allegroModeratoTime": 4000 }, "alloc": {} }); @@ -584,11 +625,25 @@ mod tests { "Should return Allegretto at its activation time" ); - // After Allegretto + // Between Allegretto and AllegroModerato assert_eq!( - chainspec.tempo_hardfork_at(4000), + chainspec.tempo_hardfork_at(3500), TempoHardfork::Allegretto, - "Should return Allegretto after its activation time" + "Should return Allegretto between Allegretto and AllegroModerato activation" + ); + + // At AllegroModerato time + assert_eq!( + chainspec.tempo_hardfork_at(4000), + TempoHardfork::AllegroModerato, + "Should return AllegroModerato at its activation time" + ); + + // After AllegroModerato + assert_eq!( + chainspec.tempo_hardfork_at(5000), + TempoHardfork::AllegroModerato, + "Should return AllegroModerato after its activation time" ); } } diff --git a/xtask/src/genesis_args.rs b/xtask/src/genesis_args.rs index ff89c464b3..42a398d524 100644 --- a/xtask/src/genesis_args.rs +++ b/xtask/src/genesis_args.rs @@ -87,6 +87,10 @@ pub(crate) struct GenesisArgs { #[arg(long)] pub allegretto_time: Option, + /// Allegro-Moderato hardfork activation timestamp + #[arg(long)] + pub allegro_moderato_time: Option, + #[arg(long, default_value_t = 302_400)] epoch_length: u64, @@ -351,6 +355,12 @@ impl GenesisArgs { serde_json::json!(allegretto_time), ); } + if let Some(allegro_moderato_time) = self.allegro_moderato_time { + chain_config.extra_fields.insert( + "allegroModeratoTime".to_string(), + serde_json::json!(allegro_moderato_time), + ); + } chain_config .extra_fields