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    /// Optional metadata to display alongside the fork (e.g. blob parameters)
29    metadata: Option<String>,
30}
31
32impl core::fmt::Display for DisplayFork {
33    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34        let name_with_eip = if let Some(eip) = &self.eip {
35            format!("{} ({})", self.name, eip)
36        } else {
37            self.name.clone()
38        };
39
40        match self.activated_at {
41            ForkCondition::Block(at) | ForkCondition::Timestamp(at) => {
42                write!(f, "{name_with_eip:32} @{at}")?;
43                if let Some(metadata) = &self.metadata {
44                    write!(f, "          {metadata}")?;
45                }
46            }
47            ForkCondition::TTD { total_difficulty, .. } => {
48                // All networks that have merged are finalized.
49                write!(
50                    f,
51                    "{name_with_eip:32} @{total_difficulty} (network is known to be merged)",
52                )?;
53                if let Some(metadata) = &self.metadata {
54                    write!(f, "          {metadata}")?;
55                }
56            }
57            ForkCondition::Never => unreachable!(),
58        }
59
60        Ok(())
61    }
62}
63
64// # Examples
65//
66// ```
67// # use reth_chainspec::MAINNET;
68// println!("{}", MAINNET.display_hardforks());
69// ```
70//
71/// A container for pretty-printing a list of hardforks.
72///
73/// An example of the output:
74///
75/// ```text
76/// Pre-merge hard forks (block based):
77// - Frontier                         @0
78// - Homestead                        @1150000
79// - Dao                              @1920000
80// - Tangerine                        @2463000
81// - SpuriousDragon                   @2675000
82// - Byzantium                        @4370000
83// - Constantinople                   @7280000
84// - Petersburg                       @7280000
85// - Istanbul                         @9069000
86// - MuirGlacier                      @9200000
87// - Berlin                           @12244000
88// - London                           @12965000
89// - ArrowGlacier                     @13773000
90// - GrayGlacier                      @15050000
91// Merge hard forks:
92// - Paris                            @58750000000000000000000 (network is known to be merged)
93// Post-merge hard forks (timestamp based):
94// - Shanghai                         @1681338455
95// - Cancun                           @1710338135
96// - Prague                           @1746612311
97/// ```
98#[derive(Debug)]
99pub struct DisplayHardforks {
100    /// A list of pre-merge (block based) hardforks
101    pre_merge: Vec<DisplayFork>,
102    /// A list of merge (TTD based) hardforks
103    with_merge: Vec<DisplayFork>,
104    /// A list of post-merge (timestamp based) hardforks
105    post_merge: Vec<DisplayFork>,
106}
107
108impl core::fmt::Display for DisplayHardforks {
109    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110        fn format(
111            header: &str,
112            forks: &[DisplayFork],
113            next_is_empty: bool,
114            f: &mut core::fmt::Formatter<'_>,
115        ) -> core::fmt::Result {
116            writeln!(f, "{header}:")?;
117            let mut iter = forks.iter().peekable();
118            while let Some(fork) = iter.next() {
119                write!(f, "- {fork}")?;
120                if !next_is_empty || iter.peek().is_some() {
121                    writeln!(f)?;
122                }
123            }
124            Ok(())
125        }
126
127        if !self.pre_merge.is_empty() {
128            format(
129                "Pre-merge hard forks (block based)",
130                &self.pre_merge,
131                self.with_merge.is_empty(),
132                f,
133            )?;
134        }
135
136        if self.with_merge.is_empty() {
137            if !self.pre_merge.is_empty() && !self.post_merge.is_empty() {
138                // need an extra line here in case we don't have a merge block (optimism)
139                writeln!(f)?;
140            }
141        } else {
142            format("Merge hard forks", &self.with_merge, self.post_merge.is_empty(), f)?;
143        }
144
145        if !self.post_merge.is_empty() {
146            format("Post-merge hard forks (timestamp based)", &self.post_merge, true, f)?;
147        }
148
149        Ok(())
150    }
151}
152
153impl DisplayHardforks {
154    /// Creates a new [`DisplayHardforks`] from an iterator of hardforks.
155    pub fn new<'a, I>(hardforks: I) -> Self
156    where
157        I: IntoIterator<Item = (&'a dyn Hardfork, ForkCondition)>,
158    {
159        // Delegate to with_meta by mapping the iterator to include None for metadata
160        Self::with_meta(hardforks.into_iter().map(|(fork, condition)| (fork, condition, None)))
161    }
162
163    /// Creates a new [`DisplayHardforks`] from an iterator of hardforks with optional metadata.
164    pub fn with_meta<'a, I>(hardforks: I) -> Self
165    where
166        I: IntoIterator<Item = (&'a dyn Hardfork, ForkCondition, Option<String>)>,
167    {
168        let mut pre_merge = Vec::new();
169        let mut with_merge = Vec::new();
170        let mut post_merge = Vec::new();
171
172        for (fork, condition, metadata) in hardforks {
173            let mut display_fork = DisplayFork {
174                name: fork.name().to_string(),
175                activated_at: condition,
176                eip: None,
177                metadata,
178            };
179
180            match condition {
181                ForkCondition::Block(_) => {
182                    pre_merge.push(display_fork);
183                }
184                ForkCondition::TTD { activation_block_number, total_difficulty, fork_block } => {
185                    display_fork.activated_at = ForkCondition::TTD {
186                        activation_block_number,
187                        fork_block,
188                        total_difficulty,
189                    };
190                    with_merge.push(display_fork);
191                }
192                ForkCondition::Timestamp(_) => {
193                    post_merge.push(display_fork);
194                }
195                ForkCondition::Never => {}
196            }
197        }
198
199        Self { pre_merge, with_merge, post_merge }
200    }
201}