edb_common/
spec_id.rs

1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Ethereum mainnet hardfork specification ID mapping
18//!
19//! This module provides utilities to determine the correct SpecId (hardfork)
20//! based on block numbers for Ethereum mainnet.
21
22use revm::primitives::{
23    eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
24    hardfork::SpecId,
25};
26use std::collections::BTreeMap;
27use std::sync::LazyLock;
28
29/// Global BTreeMap for Ethereum mainnet hardfork specifications
30/// The key is the starting block number for each hardfork
31static MAINNET_HARDFORKS: LazyLock<BTreeMap<u64, SpecId>> = LazyLock::new(|| {
32    [
33        (0, SpecId::FRONTIER),
34        (1_150_000, SpecId::HOMESTEAD),
35        (2_463_000, SpecId::TANGERINE),
36        (2_675_000, SpecId::SPURIOUS_DRAGON),
37        (4_370_000, SpecId::BYZANTIUM),
38        // Constantinople was planned but immediately replaced by Petersburg
39        // Both activate at block 7_280_000, but Petersburg takes precedence
40        (7_280_000, SpecId::PETERSBURG),
41        (9_069_000, SpecId::ISTANBUL),
42        (12_244_000, SpecId::BERLIN),
43        (12_965_000, SpecId::LONDON),
44        (13_773_000, SpecId::ARROW_GLACIER),
45        (15_050_000, SpecId::GRAY_GLACIER),
46        (15_537_394, SpecId::MERGE),
47        (17_034_870, SpecId::SHANGHAI),
48        (19_426_589, SpecId::CANCUN),
49        // Pectra (Prague on EL) mainnet activation
50        (22_431_084, SpecId::PRAGUE),
51    ]
52    .into_iter()
53    .collect()
54});
55
56/// Get the SpecId for a given block number on Ethereum mainnet
57///
58/// This function uses a global BTreeMap to efficiently find the correct hardfork
59/// specification for any given block number. It handles the special case
60/// of Constantinople/Petersburg where Petersburg immediately replaced
61/// Constantinople at the same block height.
62pub fn get_mainnet_spec_id(block_number: u64) -> SpecId {
63    // Find the last hardfork that started at or before the given block
64    MAINNET_HARDFORKS
65        .range(..=block_number)
66        .last()
67        .map(|(_, spec_id)| *spec_id)
68        .unwrap_or(SpecId::FRONTIER)
69}
70
71/// Get hardfork information for a specific SpecId
72pub fn get_hardfork_info(spec_id: SpecId) -> (&'static str, u64) {
73    match spec_id {
74        SpecId::FRONTIER => ("Frontier", 0),
75        SpecId::HOMESTEAD => ("Homestead", 1_150_000),
76        SpecId::TANGERINE => ("Tangerine Whistle", 2_463_000),
77        SpecId::SPURIOUS_DRAGON => ("Spurious Dragon", 2_675_000),
78        SpecId::BYZANTIUM => ("Byzantium", 4_370_000),
79        SpecId::CONSTANTINOPLE => ("Constantinople", 7_280_000), // Note: Replaced by Petersburg
80        SpecId::PETERSBURG => ("Petersburg", 7_280_000),
81        SpecId::ISTANBUL => ("Istanbul", 9_069_000),
82        SpecId::BERLIN => ("Berlin", 12_244_000),
83        SpecId::LONDON => ("London", 12_965_000),
84        SpecId::ARROW_GLACIER => ("Arrow Glacier", 13_773_000),
85        SpecId::GRAY_GLACIER => ("Gray Glacier", 15_050_000),
86        SpecId::MERGE => ("The Merge", 15_537_394),
87        SpecId::SHANGHAI => ("Shanghai", 17_034_870),
88        SpecId::CANCUN => ("Cancun", 19_426_589),
89        SpecId::PRAGUE => ("Prague (Pectra EL)", 22_431_084),
90        _ => ("Unknown", 0),
91    }
92}
93
94/// Returns the blob base fee update fraction based on the spec id.
95pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
96    if spec >= SpecId::PRAGUE {
97        BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE
98    } else {
99        BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_mainnet_spec_id() {
109        // Test genesis
110        assert_eq!(get_mainnet_spec_id(0), SpecId::FRONTIER);
111        assert_eq!(get_mainnet_spec_id(1), SpecId::FRONTIER);
112
113        // Test Homestead
114        assert_eq!(get_mainnet_spec_id(1_149_999), SpecId::FRONTIER);
115        assert_eq!(get_mainnet_spec_id(1_150_000), SpecId::HOMESTEAD);
116        assert_eq!(get_mainnet_spec_id(1_150_001), SpecId::HOMESTEAD);
117
118        // Test Constantinople/Petersburg transition
119        assert_eq!(get_mainnet_spec_id(7_279_999), SpecId::BYZANTIUM);
120        assert_eq!(get_mainnet_spec_id(7_280_000), SpecId::PETERSBURG); // Petersburg, not Constantinople
121        assert_eq!(get_mainnet_spec_id(7_280_001), SpecId::PETERSBURG);
122
123        // Test recent hardforks
124        assert_eq!(get_mainnet_spec_id(15_537_394), SpecId::MERGE);
125        assert_eq!(get_mainnet_spec_id(17_034_870), SpecId::SHANGHAI);
126        assert_eq!(get_mainnet_spec_id(19_426_589), SpecId::CANCUN);
127
128        // Test future blocks
129        assert_eq!(get_mainnet_spec_id(20_000_000), SpecId::CANCUN);
130        assert_eq!(get_mainnet_spec_id(u64::MAX), SpecId::CANCUN);
131    }
132}