reth_ethereum_forks/
display.rs

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