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/// ```
89#[derive(Debug)]
90pub struct DisplayHardforks {
91 /// A list of pre-merge (block based) hardforks
92 pre_merge: Vec<DisplayFork>,
93 /// A list of merge (TTD based) hardforks
94 with_merge: Vec<DisplayFork>,
95 /// A list of post-merge (timestamp based) hardforks
96 post_merge: Vec<DisplayFork>,
97}
98
99impl core::fmt::Display for DisplayHardforks {
100 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101 fn format(
102 header: &str,
103 forks: &[DisplayFork],
104 next_is_empty: bool,
105 f: &mut core::fmt::Formatter<'_>,
106 ) -> core::fmt::Result {
107 writeln!(f, "{header}:")?;
108 let mut iter = forks.iter().peekable();
109 while let Some(fork) = iter.next() {
110 write!(f, "- {fork}")?;
111 if !next_is_empty || iter.peek().is_some() {
112 writeln!(f)?;
113 }
114 }
115 Ok(())
116 }
117
118 format(
119 "Pre-merge hard forks (block based)",
120 &self.pre_merge,
121 self.with_merge.is_empty(),
122 f,
123 )?;
124
125 if self.with_merge.is_empty() {
126 if !self.post_merge.is_empty() {
127 // need an extra line here in case we don't have a merge block (optimism)
128 writeln!(f)?;
129 }
130 } else {
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<'a, I>(hardforks: I) -> Self
145 where
146 I: IntoIterator<Item = (&'a dyn Hardfork, ForkCondition)>,
147 {
148 let mut pre_merge = Vec::new();
149 let mut with_merge = Vec::new();
150 let mut post_merge = Vec::new();
151
152 for (fork, condition) in hardforks {
153 let mut display_fork =
154 DisplayFork { name: fork.name().to_string(), activated_at: condition, eip: None };
155
156 match condition {
157 ForkCondition::Block(_) => {
158 pre_merge.push(display_fork);
159 }
160 ForkCondition::TTD { activation_block_number, total_difficulty, fork_block } => {
161 display_fork.activated_at = ForkCondition::TTD {
162 activation_block_number,
163 fork_block,
164 total_difficulty,
165 };
166 with_merge.push(display_fork);
167 }
168 ForkCondition::Timestamp(_) => {
169 post_merge.push(display_fork);
170 }
171 ForkCondition::Never => {}
172 }
173 }
174
175 Self { pre_merge, with_merge, post_merge }
176 }
177}