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 format(
128 "Pre-merge hard forks (block based)",
129 &self.pre_merge,
130 self.with_merge.is_empty(),
131 f,
132 )?;
133
134 if self.with_merge.is_empty() {
135 if !self.post_merge.is_empty() {
136 // need an extra line here in case we don't have a merge block (optimism)
137 writeln!(f)?;
138 }
139 } else {
140 format("Merge hard forks", &self.with_merge, self.post_merge.is_empty(), f)?;
141 }
142
143 if !self.post_merge.is_empty() {
144 format("Post-merge hard forks (timestamp based)", &self.post_merge, true, f)?;
145 }
146
147 Ok(())
148 }
149}
150
151impl DisplayHardforks {
152 /// Creates a new [`DisplayHardforks`] from an iterator of hardforks.
153 pub fn new<'a, I>(hardforks: I) -> Self
154 where
155 I: IntoIterator<Item = (&'a dyn Hardfork, ForkCondition)>,
156 {
157 // Delegate to with_meta by mapping the iterator to include None for metadata
158 Self::with_meta(hardforks.into_iter().map(|(fork, condition)| (fork, condition, None)))
159 }
160
161 /// Creates a new [`DisplayHardforks`] from an iterator of hardforks with optional metadata.
162 pub fn with_meta<'a, I>(hardforks: I) -> Self
163 where
164 I: IntoIterator<Item = (&'a dyn Hardfork, ForkCondition, Option<String>)>,
165 {
166 let mut pre_merge = Vec::new();
167 let mut with_merge = Vec::new();
168 let mut post_merge = Vec::new();
169
170 for (fork, condition, metadata) in hardforks {
171 let mut display_fork = DisplayFork {
172 name: fork.name().to_string(),
173 activated_at: condition,
174 eip: None,
175 metadata,
176 };
177
178 match condition {
179 ForkCondition::Block(_) => {
180 pre_merge.push(display_fork);
181 }
182 ForkCondition::TTD { activation_block_number, total_difficulty, fork_block } => {
183 display_fork.activated_at = ForkCondition::TTD {
184 activation_block_number,
185 fork_block,
186 total_difficulty,
187 };
188 with_merge.push(display_fork);
189 }
190 ForkCondition::Timestamp(_) => {
191 post_merge.push(display_fork);
192 }
193 ForkCondition::Never => {}
194 }
195 }
196
197 Self { pre_merge, with_merge, post_merge }
198 }
199}