reth_ethereum_forks/
display.rs

1use crate::{hardforks::Hardforks, ForkCondition};
2use alloc::{
3    format,
4    string::{String, ToString},
5    vec::Vec,
6};
7
8/// A container to pretty-print a hardfork.
9///
10/// The fork is formatted depending on its fork condition:
11///
12/// - Block and timestamp based forks are formatted in the same manner (`{name} <({eip})>
13///   @{condition}`)
14/// - TTD based forks are formatted separately as `{name} <({eip})> @{ttd} (network is <not> known
15///   to be merged)`
16///
17/// An optional EIP can be attached to the fork to display as well. This should generally be in the
18/// form of just `EIP-x`, e.g. `EIP-1559`.
19#[derive(Debug)]
20struct DisplayFork {
21    /// The name of the hardfork (e.g. Frontier)
22    name: String,
23    /// The fork condition
24    activated_at: ForkCondition,
25    /// An optional EIP (e.g. `EIP-1559`).
26    eip: Option<String>,
27}
28
29impl core::fmt::Display for DisplayFork {
30    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31        let name_with_eip = if let Some(eip) = &self.eip {
32            format!("{} ({})", self.name, eip)
33        } else {
34            self.name.clone()
35        };
36
37        match self.activated_at {
38            ForkCondition::Block(at) | ForkCondition::Timestamp(at) => {
39                write!(f, "{name_with_eip:32} @{at}")?;
40            }
41            ForkCondition::TTD { fork_block, total_difficulty } => {
42                write!(
43                    f,
44                    "{:32} @{} ({})",
45                    name_with_eip,
46                    total_difficulty,
47                    if fork_block.is_some() {
48                        "network is known to be merged"
49                    } else {
50                        "network is not known to be merged"
51                    }
52                )?;
53            }
54            ForkCondition::Never => unreachable!(),
55        }
56
57        Ok(())
58    }
59}
60
61// # Examples
62//
63// ```
64// # use reth_chainspec::MAINNET;
65// println!("{}", MAINNET.display_hardforks());
66// ```
67//
68/// A container for pretty-printing a list of hardforks.
69///
70/// An example of the output:
71///
72/// ```text
73/// Pre-merge hard forks (block based):
74// - Frontier                         @0
75// - Homestead                        @1150000
76// - Dao                              @1920000
77// - Tangerine                        @2463000
78// - SpuriousDragon                   @2675000
79// - Byzantium                        @4370000
80// - Constantinople                   @7280000
81// - Petersburg                       @7280000
82// - Istanbul                         @9069000
83// - MuirGlacier                      @9200000
84// - Berlin                           @12244000
85// - London                           @12965000
86// - ArrowGlacier                     @13773000
87// - GrayGlacier                      @15050000
88// Merge hard forks:
89// - Paris                            @58750000000000000000000 (network is known to be merged)
90// Post-merge hard forks (timestamp based):
91// - Shanghai                         @1681338455
92// - Cancun                           @1710338135"
93/// ```
94#[derive(Debug)]
95pub struct DisplayHardforks {
96    /// A list of pre-merge (block based) hardforks
97    pre_merge: Vec<DisplayFork>,
98    /// A list of merge (TTD based) hardforks
99    with_merge: Vec<DisplayFork>,
100    /// A list of post-merge (timestamp based) hardforks
101    post_merge: Vec<DisplayFork>,
102}
103
104impl core::fmt::Display for DisplayHardforks {
105    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
106        fn format(
107            header: &str,
108            forks: &[DisplayFork],
109            next_is_empty: bool,
110            f: &mut core::fmt::Formatter<'_>,
111        ) -> core::fmt::Result {
112            writeln!(f, "{header}:")?;
113            let mut iter = forks.iter().peekable();
114            while let Some(fork) = iter.next() {
115                write!(f, "- {fork}")?;
116                if !next_is_empty || iter.peek().is_some() {
117                    writeln!(f)?;
118                }
119            }
120            Ok(())
121        }
122
123        format(
124            "Pre-merge hard forks (block based)",
125            &self.pre_merge,
126            self.with_merge.is_empty(),
127            f,
128        )?;
129
130        if !self.with_merge.is_empty() {
131            format("Merge hard forks", &self.with_merge, self.post_merge.is_empty(), f)?;
132        }
133
134        if !self.post_merge.is_empty() {
135            format("Post-merge hard forks (timestamp based)", &self.post_merge, true, f)?;
136        }
137
138        Ok(())
139    }
140}
141
142impl DisplayHardforks {
143    /// Creates a new [`DisplayHardforks`] from an iterator of hardforks.
144    pub fn new<H: Hardforks>(hardforks: &H, known_paris_block: Option<u64>) -> Self {
145        let mut pre_merge = Vec::new();
146        let mut with_merge = Vec::new();
147        let mut post_merge = Vec::new();
148
149        for (fork, condition) in hardforks.forks_iter() {
150            let mut display_fork =
151                DisplayFork { name: fork.name().to_string(), activated_at: condition, eip: None };
152
153            match condition {
154                ForkCondition::Block(_) => {
155                    pre_merge.push(display_fork);
156                }
157                ForkCondition::TTD { total_difficulty, .. } => {
158                    display_fork.activated_at =
159                        ForkCondition::TTD { fork_block: known_paris_block, total_difficulty };
160                    with_merge.push(display_fork);
161                }
162                ForkCondition::Timestamp(_) => {
163                    post_merge.push(display_fork);
164                }
165                ForkCondition::Never => continue,
166            }
167        }
168
169        Self { pre_merge, with_merge, post_merge }
170    }
171}