#[cfg(not(feature = "std"))]
use alloc as std;
use alloy_hardforks::{hardfork, EthereumHardfork, EthereumHardforks, ForkCondition, Hardfork};
use alloy_op_hardforks::{OpHardfork, OpHardforks};
use alloy_primitives::{BlockNumber, BlockTimestamp, U256};
use auto_impl::auto_impl;
use std::{boxed::Box, vec::Vec};
use crate::MegaSpecId;
hardfork! {
#[derive(serde::Serialize, serde::Deserialize)]
MegaHardfork {
MiniRex,
MiniRex1,
MiniRex2,
Rex,
Rex1,
Rex2,
Rex3,
Rex4,
}
}
impl MegaHardfork {
#[allow(clippy::match_same_arms)]
pub fn spec_id(&self) -> MegaSpecId {
match self {
Self::MiniRex => MegaSpecId::MINI_REX,
Self::MiniRex1 => MegaSpecId::EQUIVALENCE,
Self::MiniRex2 => MegaSpecId::MINI_REX,
Self::Rex => MegaSpecId::REX,
Self::Rex1 => MegaSpecId::REX1,
Self::Rex2 => MegaSpecId::REX2,
Self::Rex3 => MegaSpecId::REX3,
Self::Rex4 => MegaSpecId::REX4,
}
}
}
#[auto_impl(&, Box, Arc)]
pub trait MegaHardforks: OpHardforks {
fn mega_fork_activation(&self, fork: MegaHardfork) -> ForkCondition;
fn first_hardfork_block(
&self,
fork: MegaHardfork,
parent_timestamp: BlockTimestamp,
current_number_and_timestamp: (BlockNumber, BlockTimestamp),
) -> bool {
let (current_number, current_timestamp) = current_number_and_timestamp;
self.mega_fork_activation(fork).active_at_timestamp(current_timestamp) &&
(current_number == 1 ||
!self.mega_fork_activation(fork).active_at_timestamp(parent_timestamp))
}
fn hardfork(&self, timestamp: u64) -> Option<MegaHardfork> {
if self.is_rex_4_active_at_timestamp(timestamp) {
Some(MegaHardfork::Rex4)
} else if self.is_rex_3_active_at_timestamp(timestamp) {
Some(MegaHardfork::Rex3)
} else if self.is_rex_2_active_at_timestamp(timestamp) {
Some(MegaHardfork::Rex2)
} else if self.is_rex_1_active_at_timestamp(timestamp) {
Some(MegaHardfork::Rex1)
} else if self.is_rex_active_at_timestamp(timestamp) {
Some(MegaHardfork::Rex)
} else if self.is_mini_rex_2_active_at_timestamp(timestamp) {
Some(MegaHardfork::MiniRex2)
} else if self.is_mini_rex_1_active_at_timestamp(timestamp) {
Some(MegaHardfork::MiniRex1)
} else if self.is_mini_rex_active_at_timestamp(timestamp) {
Some(MegaHardfork::MiniRex)
} else {
None
}
}
fn spec_id(&self, timestamp: BlockTimestamp) -> MegaSpecId {
if self.is_rex_4_active_at_timestamp(timestamp) {
MegaSpecId::REX4
} else if self.is_rex_3_active_at_timestamp(timestamp) {
MegaSpecId::REX3
} else if self.is_rex_2_active_at_timestamp(timestamp) {
MegaSpecId::REX2
} else if self.is_rex_1_active_at_timestamp(timestamp) {
MegaSpecId::REX1
} else if self.is_rex_active_at_timestamp(timestamp) {
MegaSpecId::REX
} else if self.is_mini_rex_2_active_at_timestamp(timestamp) {
MegaSpecId::MINI_REX
} else if self.is_mini_rex_1_active_at_timestamp(timestamp) {
MegaSpecId::EQUIVALENCE
} else if self.is_mini_rex_active_at_timestamp(timestamp) {
MegaSpecId::MINI_REX
} else {
MegaSpecId::EQUIVALENCE
}
}
fn is_mini_rex_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::MiniRex).active_at_timestamp(timestamp)
}
fn is_mini_rex_1_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::MiniRex1).active_at_timestamp(timestamp)
}
fn is_mini_rex_2_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::MiniRex2).active_at_timestamp(timestamp)
}
fn is_rex_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::Rex).active_at_timestamp(timestamp)
}
fn is_rex_1_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::Rex1).active_at_timestamp(timestamp)
}
fn is_rex_2_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::Rex2).active_at_timestamp(timestamp)
}
fn is_rex_3_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::Rex3).active_at_timestamp(timestamp)
}
fn is_rex_4_active_at_timestamp(&self, timestamp: u64) -> bool {
self.mega_fork_activation(MegaHardfork::Rex4).active_at_timestamp(timestamp)
}
}
#[derive(Debug, Clone)]
pub struct MegaHardforkConfig {
hardforks: Vec<(Box<dyn Hardfork>, ForkCondition)>,
}
impl Default for MegaHardforkConfig {
fn default() -> Self {
Self::new()
}
}
impl<I, H> From<I> for MegaHardforkConfig
where
I: Iterator<Item = (H, ForkCondition)>,
H: Hardfork + 'static,
{
fn from(iter: I) -> Self {
Self { hardforks: iter.map(|(h, c)| (Box::new(h) as Box<dyn Hardfork>, c)).collect() }
}
}
impl MegaHardforkConfig {
pub fn new() -> Self {
Self {
hardforks: vec![
(EthereumHardfork::Frontier.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Dao.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)),
(EthereumHardfork::London.boxed(), ForkCondition::Block(0)),
(
EthereumHardfork::Paris.boxed(),
ForkCondition::TTD {
activation_block_number: 0,
fork_block: None,
total_difficulty: U256::ZERO,
},
),
(OpHardfork::Bedrock.boxed(), ForkCondition::Block(0)),
(OpHardfork::Regolith.boxed(), ForkCondition::Timestamp(0)),
(EthereumHardfork::Shanghai.boxed(), ForkCondition::Timestamp(0)),
(OpHardfork::Canyon.boxed(), ForkCondition::Timestamp(0)),
(EthereumHardfork::Cancun.boxed(), ForkCondition::Timestamp(0)),
(OpHardfork::Ecotone.boxed(), ForkCondition::Timestamp(0)),
(OpHardfork::Fjord.boxed(), ForkCondition::Timestamp(0)),
(OpHardfork::Granite.boxed(), ForkCondition::Timestamp(0)),
(OpHardfork::Holocene.boxed(), ForkCondition::Timestamp(0)),
(EthereumHardfork::Prague.boxed(), ForkCondition::Timestamp(0)),
(OpHardfork::Isthmus.boxed(), ForkCondition::Timestamp(0)),
],
}
}
pub fn with_all_activated(mut self) -> Self {
self.insert(MegaHardfork::MiniRex, ForkCondition::Timestamp(0));
self.insert(MegaHardfork::MiniRex1, ForkCondition::Timestamp(0));
self.insert(MegaHardfork::MiniRex2, ForkCondition::Timestamp(0));
self.insert(MegaHardfork::Rex, ForkCondition::Timestamp(0));
self.insert(MegaHardfork::Rex1, ForkCondition::Timestamp(0));
self.insert(MegaHardfork::Rex2, ForkCondition::Timestamp(0));
self.insert(MegaHardfork::Rex3, ForkCondition::Timestamp(0));
self.insert(MegaHardfork::Rex4, ForkCondition::Timestamp(0));
self
}
pub fn without(mut self, hardfork: MegaHardfork) -> Self {
self.hardforks.retain(|(h, _)| h.name() != hardfork.name());
self
}
pub fn with(mut self, hardfork: impl Hardfork, condition: ForkCondition) -> Self {
self.insert(hardfork, condition);
self
}
pub fn insert(&mut self, hardfork: impl Hardfork, condition: ForkCondition) {
let index = self.hardforks.iter().position(|(h, _)| h.name() == hardfork.name());
if let Some(index) = index {
self.hardforks[index] = (Box::new(hardfork), condition);
} else {
self.hardforks.push((Box::new(hardfork), condition));
}
}
pub fn get(&self, hardfork: impl Hardfork) -> Option<&ForkCondition> {
self.hardforks
.iter()
.find(|(h, _)| h.name() == hardfork.name())
.map(|(_, condition)| condition)
}
}
impl EthereumHardforks for MegaHardforkConfig {
fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
match self.get(fork) {
Some(condition) => *condition,
None => ForkCondition::Never,
}
}
}
impl OpHardforks for MegaHardforkConfig {
fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition {
match self.get(fork) {
Some(condition) => *condition,
None => ForkCondition::Never,
}
}
}
impl MegaHardforks for MegaHardforkConfig {
fn mega_fork_activation(&self, fork: MegaHardfork) -> ForkCondition {
match self.get(fork) {
Some(condition) => *condition,
None => ForkCondition::Never,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mega_hardfork_spec_ids_match_expected_specs() {
let cases = [
(MegaHardfork::MiniRex, MegaSpecId::MINI_REX),
(MegaHardfork::MiniRex1, MegaSpecId::EQUIVALENCE),
(MegaHardfork::MiniRex2, MegaSpecId::MINI_REX),
(MegaHardfork::Rex, MegaSpecId::REX),
(MegaHardfork::Rex1, MegaSpecId::REX1),
(MegaHardfork::Rex2, MegaSpecId::REX2),
(MegaHardfork::Rex3, MegaSpecId::REX3),
(MegaHardfork::Rex4, MegaSpecId::REX4),
];
for (hardfork, expected_spec) in cases {
assert_eq!(hardfork.spec_id(), expected_spec);
}
}
#[test]
fn test_default_config_contains_upstream_forks_and_no_mega_forks() {
let config = MegaHardforkConfig::default();
assert_eq!(
config.ethereum_fork_activation(EthereumHardfork::Frontier),
ForkCondition::Block(0)
);
assert_eq!(
config.ethereum_fork_activation(EthereumHardfork::Prague),
ForkCondition::Timestamp(0)
);
assert_eq!(config.op_fork_activation(OpHardfork::Isthmus), ForkCondition::Timestamp(0));
assert_eq!(config.mega_fork_activation(MegaHardfork::MiniRex), ForkCondition::Never);
}
#[test]
fn test_config_builder_helpers_override_and_remove_hardforks() {
let mut config = MegaHardforkConfig::new()
.with(MegaHardfork::MiniRex, ForkCondition::Timestamp(10))
.with(MegaHardfork::Rex4, ForkCondition::Timestamp(80));
assert_eq!(config.get(MegaHardfork::MiniRex), Some(&ForkCondition::Timestamp(10)));
assert_eq!(config.get(MegaHardfork::Rex4), Some(&ForkCondition::Timestamp(80)));
config.insert(MegaHardfork::MiniRex, ForkCondition::Timestamp(20));
assert_eq!(config.get(MegaHardfork::MiniRex), Some(&ForkCondition::Timestamp(20)));
let config = config.without(MegaHardfork::MiniRex);
assert_eq!(config.get(MegaHardfork::MiniRex), None);
let from_iter = MegaHardforkConfig::from(
[
(MegaHardfork::MiniRex, ForkCondition::Timestamp(1)),
(MegaHardfork::Rex2, ForkCondition::Timestamp(2)),
]
.into_iter(),
);
assert_eq!(from_iter.get(MegaHardfork::MiniRex), Some(&ForkCondition::Timestamp(1)));
assert_eq!(from_iter.get(MegaHardfork::Rex2), Some(&ForkCondition::Timestamp(2)));
}
#[test]
fn test_with_all_activated_enables_all_mega_hardforks() {
let config = MegaHardforkConfig::default().with_all_activated();
for hardfork in [
MegaHardfork::MiniRex,
MegaHardfork::MiniRex1,
MegaHardfork::MiniRex2,
MegaHardfork::Rex,
MegaHardfork::Rex1,
MegaHardfork::Rex2,
MegaHardfork::Rex3,
MegaHardfork::Rex4,
] {
assert_eq!(config.mega_fork_activation(hardfork), ForkCondition::Timestamp(0));
}
}
#[test]
fn test_hardfork_and_spec_id_follow_latest_active_timestamp() {
let config = MegaHardforkConfig::default()
.with(MegaHardfork::MiniRex, ForkCondition::Timestamp(10))
.with(MegaHardfork::MiniRex1, ForkCondition::Timestamp(20))
.with(MegaHardfork::MiniRex2, ForkCondition::Timestamp(30))
.with(MegaHardfork::Rex, ForkCondition::Timestamp(40))
.with(MegaHardfork::Rex1, ForkCondition::Timestamp(50))
.with(MegaHardfork::Rex2, ForkCondition::Timestamp(60))
.with(MegaHardfork::Rex3, ForkCondition::Timestamp(70))
.with(MegaHardfork::Rex4, ForkCondition::Timestamp(80));
let expected = [
(0, None, MegaSpecId::EQUIVALENCE),
(15, Some(MegaHardfork::MiniRex), MegaSpecId::MINI_REX),
(25, Some(MegaHardfork::MiniRex1), MegaSpecId::EQUIVALENCE),
(35, Some(MegaHardfork::MiniRex2), MegaSpecId::MINI_REX),
(45, Some(MegaHardfork::Rex), MegaSpecId::REX),
(55, Some(MegaHardfork::Rex1), MegaSpecId::REX1),
(65, Some(MegaHardfork::Rex2), MegaSpecId::REX2),
(75, Some(MegaHardfork::Rex3), MegaSpecId::REX3),
(85, Some(MegaHardfork::Rex4), MegaSpecId::REX4),
];
for (timestamp, expected_hardfork, expected_spec) in expected {
assert_eq!(config.hardfork(timestamp), expected_hardfork);
assert_eq!(config.spec_id(timestamp), expected_spec);
}
}
#[test]
fn test_first_hardfork_block_handles_genesis_and_parent_activation_boundaries() {
let config =
MegaHardforkConfig::default().with(MegaHardfork::Rex2, ForkCondition::Timestamp(100));
assert!(config.first_hardfork_block(MegaHardfork::Rex2, 99, (1, 100)));
assert!(config.first_hardfork_block(MegaHardfork::Rex2, 99, (2, 100)));
assert!(!config.first_hardfork_block(MegaHardfork::Rex2, 100, (3, 101)));
assert!(!config.first_hardfork_block(MegaHardfork::Rex2, 99, (2, 99)));
}
#[test]
fn test_spec_id_with_gaps_in_hardfork_configuration() {
let config = MegaHardforkConfig::default()
.with(MegaHardfork::MiniRex, ForkCondition::Timestamp(10))
.with(MegaHardfork::Rex4, ForkCondition::Timestamp(20));
assert_eq!(config.spec_id(5), MegaSpecId::EQUIVALENCE);
assert_eq!(config.spec_id(15), MegaSpecId::MINI_REX);
assert_eq!(config.spec_id(25), MegaSpecId::REX4);
assert_eq!(config.hardfork(15), Some(MegaHardfork::MiniRex));
assert_eq!(config.hardfork(25), Some(MegaHardfork::Rex4));
}
#[test]
fn test_latest_hardfork_wins_when_multiple_activate_at_same_timestamp() {
let config = MegaHardforkConfig::default()
.with(MegaHardfork::MiniRex, ForkCondition::Timestamp(10))
.with(MegaHardfork::Rex2, ForkCondition::Timestamp(10))
.with(MegaHardfork::Rex4, ForkCondition::Timestamp(10));
assert_eq!(config.hardfork(9), None);
assert_eq!(config.hardfork(10), Some(MegaHardfork::Rex4));
assert_eq!(config.spec_id(10), MegaSpecId::REX4);
}
}