Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/chainspec/src/genesis/dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"epochLength": 302400,
"moderatoTime": 0,
"allegrettoTime": 0,
"allegroModeratoTime": 0,
"depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa"
},
"nonce": "0x42",
Expand Down
66 changes: 56 additions & 10 deletions crates/chainspec/src/hardfork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>`)
//! 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<TempoHardfork> for SpecId`
//! 6. Update `From<SpecId> 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<u64>` 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": <timestamp>` in genesis JSON
//!
//! ### In genesis files and generator:
//! 12. Add `"vivaceTime": 0` to `genesis/dev.json`
//! 13. Add `vivace_time: Option<u64>` arg to `xtask/src/genesis_args.rs`
//! 14. Add insertion of `"vivaceTime"` to chain_config.extra_fields
//!
//! ## Current State
//!
Expand All @@ -32,6 +46,8 @@ hardfork!(
Moderato,
/// Allegretto hardfork.
Allegretto,
/// Allegro-Moderato hardfork.
AllegroModerato,
}
);

Expand All @@ -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.
Expand All @@ -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
Expand All @@ -89,6 +118,7 @@ impl From<TempoHardfork> for SpecId {
TempoHardfork::Adagio => Self::OSAKA,
TempoHardfork::Moderato => Self::OSAKA,
TempoHardfork::Allegretto => Self::OSAKA,
TempoHardfork::AllegroModerato => Self::OSAKA,
}
}
}
Expand All @@ -100,7 +130,9 @@ impl From<SpecId> for TempoHardfork {
/// `From<TempoHardfork> 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
Expand Down Expand Up @@ -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]
Expand All @@ -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());
}
}
63 changes: 59 additions & 4 deletions crates/chainspec/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ pub struct TempoGenesisInfo {
#[serde(skip_serializing_if = "Option::is_none")]
allegretto_time: Option<u64>,

/// Timestamp of Allegro-Moderato hardfork activation
#[serde(skip_serializing_if = "Option::is_none")]
allegro_moderato_time: Option<u64>,

/// The epoch length used by consensus.
#[serde(skip_serializing_if = "Option::is_none")]
epoch_length: Option<u64>,
Expand Down Expand Up @@ -131,6 +135,7 @@ impl TempoChainSpec {
adagio_time,
moderato_time,
allegretto_time,
allegro_moderato_time,
..
} = TempoGenesisInfo::extract_from(&genesis);

Expand All @@ -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))));
Expand Down Expand Up @@ -360,6 +366,7 @@ mod tests {
"adagioTime": 1000,
"moderatoTime": 2000,
"allegrettoTime": 3000,
"allegroModeratoTime": 4000,
},
"alloc": {}
});
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -532,7 +572,8 @@ mod tests {
"cancunTime": 0,
"adagioTime": 1000,
"moderatoTime": 2000,
"allegrettoTime": 3000
"allegrettoTime": 3000,
"allegroModeratoTime": 4000
},
"alloc": {}
});
Expand Down Expand Up @@ -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"
);
}
}
10 changes: 10 additions & 0 deletions xtask/src/genesis_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ pub(crate) struct GenesisArgs {
#[arg(long)]
pub allegretto_time: Option<u64>,

/// Allegro-Moderato hardfork activation timestamp
#[arg(long)]
pub allegro_moderato_time: Option<u64>,

#[arg(long, default_value_t = 302_400)]
epoch_length: u64,

Expand Down Expand Up @@ -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
Expand Down
Loading