Skip to content
65 changes: 64 additions & 1 deletion crates/chainspec/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{ChainSpec, DepositContract};
use crate::{ChainSpec, DepositContract, GravityHardfork};
use alloc::{boxed::Box, vec::Vec};
use alloy_chains::Chain;
use alloy_consensus::Header;
Expand Down Expand Up @@ -64,6 +64,41 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug {
/// Returns the final total difficulty if the Paris hardfork is known.
fn final_paris_total_difficulty(&self) -> Option<U256>;

/// Returns `true` if Alpha hardfork is active at the given block number.
fn is_alpha_active_at_block_number(&self, _block_number: u64) -> bool {
false
}

/// Returns `true` if Alpha hardfork transitions exactly at the given block number.
fn alpha_transitions_at_block(&self, _block_number: u64) -> bool {
false
}

/// Returns `true` if Beta hardfork is active at the given block number.
fn is_beta_active_at_block_number(&self, _block_number: u64) -> bool {
false
}

/// Returns `true` if Beta hardfork transitions exactly at the given block number.
fn beta_transitions_at_block(&self, _block_number: u64) -> bool {
false
}

/// Returns `true` if Gamma hardfork transitions exactly at the given block number.
fn gamma_transitions_at_block(&self, _block_number: u64) -> bool {
false
}

/// Returns `true` if Delta hardfork is active at the given block number.
fn is_delta_active_at_block_number(&self, _block_number: u64) -> bool {
false
}

/// Returns `true` if Delta hardfork transitions exactly at the given block number.
fn delta_transitions_at_block(&self, _block_number: u64) -> bool {
false
}

/// See [`calc_next_block_base_fee`].
fn next_block_base_fee(&self, parent: &Self::Header, target_timestamp: u64) -> Option<u64> {
Some(calc_next_block_base_fee(
Expand Down Expand Up @@ -135,4 +170,32 @@ impl EthChainSpec for ChainSpec {
fn final_paris_total_difficulty(&self) -> Option<U256> {
self.paris_block_and_final_difficulty.map(|(_, final_difficulty)| final_difficulty)
}

fn is_alpha_active_at_block_number(&self, block_number: u64) -> bool {
self.gravity_hardforks.is_fork_active_at_block(GravityHardfork::Alpha, block_number)
}

fn alpha_transitions_at_block(&self, block_number: u64) -> bool {
self.gravity_hardforks.fork(GravityHardfork::Alpha).transitions_at_block(block_number)
}

fn is_beta_active_at_block_number(&self, block_number: u64) -> bool {
self.gravity_hardforks.is_fork_active_at_block(GravityHardfork::Beta, block_number)
}

fn beta_transitions_at_block(&self, block_number: u64) -> bool {
self.gravity_hardforks.fork(GravityHardfork::Beta).transitions_at_block(block_number)
}

fn gamma_transitions_at_block(&self, block_number: u64) -> bool {
self.gravity_hardforks.fork(GravityHardfork::Gamma).transitions_at_block(block_number)
}

fn is_delta_active_at_block_number(&self, block_number: u64) -> bool {
self.gravity_hardforks.is_fork_active_at_block(GravityHardfork::Delta, block_number)
}

fn delta_transitions_at_block(&self, block_number: u64) -> bool {
self.gravity_hardforks.fork(GravityHardfork::Delta).transitions_at_block(block_number)
}
}
17 changes: 17 additions & 0 deletions crates/chainspec/src/gravity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! Gravity-specific hardforks
use reth_ethereum_forks::hardfork;

hardfork!(
/// Gravity hardforks.
GravityHardfork {
/// Alpha hardfork: upgrade Staking/StakePool contracts and disable PoW rewards
Alpha,
/// Beta hardfork: upgrade StakePool contracts with correct FACTORY immutable
Beta,
/// Gamma hardfork: audit fixes, precompile changes, 12 contract bytecode upgrades
Gamma,
/// Delta hardfork: activate Governance contract by setting Ownable._owner
Delta,
}
);
3 changes: 3 additions & 0 deletions crates/chainspec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ mod constants;
pub use constants::*;

mod api;
/// Gravity-specific hardforks module.
mod gravity;
/// The chain info module.
mod info;
/// The chain spec module.
Expand All @@ -26,6 +28,7 @@ pub use alloy_chains::{Chain, ChainKind, NamedChain};
pub use reth_ethereum_forks::*;

pub use api::EthChainSpec;
pub use gravity::GravityHardfork;
pub use info::ChainInfo;
#[cfg(any(test, feature = "test-utils"))]
pub use spec::test_fork_ids;
Expand Down
99 changes: 99 additions & 0 deletions crates/chainspec/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use alloy_evm::eth::spec::EthExecutorSpec;

use crate::{
constants::{MAINNET_DEPOSIT_CONTRACT, MAINNET_PRUNE_DELETE_LIMIT},
gravity::GravityHardfork,
holesky, hoodi, mainnet, sepolia, EthChainSpec,
};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
Expand Down Expand Up @@ -112,6 +113,7 @@ pub static MAINNET: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
(mainnet::MAINNET_BPO1_TIMESTAMP, BlobParams::bpo1()),
(mainnet::MAINNET_BPO2_TIMESTAMP, BlobParams::bpo2()),
]),
gravity_hardforks: ChainHardforks::default(),
};
spec.genesis.config.dao_fork_support = true;
spec.into()
Expand Down Expand Up @@ -144,6 +146,7 @@ pub static SEPOLIA: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
(sepolia::SEPOLIA_BPO1_TIMESTAMP, BlobParams::bpo1()),
(sepolia::SEPOLIA_BPO2_TIMESTAMP, BlobParams::bpo2()),
]),
gravity_hardforks: ChainHardforks::default(),
};
spec.genesis.config.dao_fork_support = true;
spec.into()
Expand Down Expand Up @@ -174,6 +177,7 @@ pub static HOLESKY: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
(holesky::HOLESKY_BPO1_TIMESTAMP, BlobParams::bpo1()),
(holesky::HOLESKY_BPO2_TIMESTAMP, BlobParams::bpo2()),
]),
gravity_hardforks: ChainHardforks::default(),
};
spec.genesis.config.dao_fork_support = true;
spec.into()
Expand Down Expand Up @@ -206,6 +210,7 @@ pub static HOODI: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
(hoodi::HOODI_BPO1_TIMESTAMP, BlobParams::bpo1()),
(hoodi::HOODI_BPO2_TIMESTAMP, BlobParams::bpo2()),
]),
gravity_hardforks: ChainHardforks::default(),
};
spec.genesis.config.dao_fork_support = true;
spec.into()
Expand Down Expand Up @@ -310,6 +315,9 @@ pub struct ChainSpec {

/// The settings passed for blob configurations for specific hardforks.
pub blob_params: BlobScheduleBlobParams,

/// Gravity-specific hardforks
pub gravity_hardforks: ChainHardforks,
}

impl Default for ChainSpec {
Expand All @@ -324,6 +332,7 @@ impl Default for ChainSpec {
base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()),
prune_delete_limit: MAINNET_PRUNE_DELETE_LIMIT,
blob_params: Default::default(),
gravity_hardforks: Default::default(),
}
}
}
Expand Down Expand Up @@ -723,6 +732,29 @@ impl From<Genesis> for ChainSpec {

let hardforks = ChainHardforks::new(ordered_hardforks);

// Gravity-specific hardforks from extra_fields
let mut gravity_hardforks = ChainHardforks::default();
if let Some(block_num) =
genesis.config.extra_fields.get("alphaBlock").and_then(|v| v.as_u64())
{
gravity_hardforks.insert(GravityHardfork::Alpha, ForkCondition::Block(block_num));
}
if let Some(block_num) =
genesis.config.extra_fields.get("betaBlock").and_then(|v| v.as_u64())
{
gravity_hardforks.insert(GravityHardfork::Beta, ForkCondition::Block(block_num));
}
if let Some(block_num) =
genesis.config.extra_fields.get("gammaBlock").and_then(|v| v.as_u64())
{
gravity_hardforks.insert(GravityHardfork::Gamma, ForkCondition::Block(block_num));
}
if let Some(block_num) =
genesis.config.extra_fields.get("deltaBlock").and_then(|v| v.as_u64())
{
gravity_hardforks.insert(GravityHardfork::Delta, ForkCondition::Block(block_num));
}

Self {
chain: genesis.config.chain_id.into(),
genesis_header: SealedHeader::new_unhashed(make_genesis_header(&genesis, &hardforks)),
Expand All @@ -731,6 +763,7 @@ impl From<Genesis> for ChainSpec {
paris_block_and_final_difficulty,
deposit_contract,
blob_params,
gravity_hardforks,
..Default::default()
}
}
Expand Down Expand Up @@ -2647,4 +2680,70 @@ Post-merge hard forks (timestamp based):
};
assert_eq!(hardfork_params, expected);
}

#[test]
fn test_gravity_alpha_hardfork() {
// Test 1: Default ChainSpec should not have Alpha hardfork active
let spec = ChainSpec::default();
assert!(!spec.is_alpha_active_at_block_number(0));
assert!(!spec.is_alpha_active_at_block_number(u64::MAX));

// Test 2: Parse genesis with alphaBlock from extra_fields
let genesis_json = r#"{
"config": {
"chainId": 1625,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"terminalTotalDifficulty": 0,
"shanghaiTime": 0,
"cancunTime": 0,
"alphaBlock": 1000
},
"alloc": {}
}"#;
let genesis: Genesis = serde_json::from_str(genesis_json).unwrap();
let spec = ChainSpec::from(genesis);

// Before activation block: should return false
assert!(!spec.is_alpha_active_at_block_number(999));

// At activation block: should return true
assert!(spec.is_alpha_active_at_block_number(1000));

// After activation block: should return true
assert!(spec.is_alpha_active_at_block_number(1001));
assert!(spec.is_alpha_active_at_block_number(u64::MAX));

// transitions_at_block: only true at the exact block
assert!(!spec.alpha_transitions_at_block(999));
assert!(spec.alpha_transitions_at_block(1000));
assert!(!spec.alpha_transitions_at_block(1001));
}

#[test]
fn test_gravity_alpha_not_configured() {
// Genesis without alphaBlock should not activate the hardfork
let genesis_json = r#"{
"config": {
"chainId": 1625,
"homesteadBlock": 0,
"terminalTotalDifficulty": 0,
"shanghaiTime": 0
},
"alloc": {}
}"#;
let genesis: Genesis = serde_json::from_str(genesis_json).unwrap();
let spec = ChainSpec::from(genesis);

assert!(!spec.is_alpha_active_at_block_number(0));
assert!(!spec.is_alpha_active_at_block_number(u64::MAX));
}
}
Loading