foundry_primitives/network/
header.rs1use alloy_consensus::{BlockHeader, Header};
2use alloy_primitives::{Address, B64, B256, Bloom, Bytes, Sealable, U256};
3use alloy_rlp::{BufMut, Decodable, Encodable, Result};
4use std::ops::Deref;
5use tempo_primitives::TempoHeader;
6
7#[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
13#[serde(untagged)]
14#[allow(clippy::large_enum_variant)]
15pub enum FoundryHeader {
16 Tempo(TempoHeader),
18 Ethereum(Header),
20}
21
22impl Default for FoundryHeader {
23 fn default() -> Self {
24 Self::Ethereum(Header::default())
25 }
26}
27
28impl FoundryHeader {
29 pub const fn new(inner: Header, is_tempo: bool) -> Self {
31 if is_tempo { Self::tempo(inner) } else { Self::Ethereum(inner) }
32 }
33
34 pub const fn tempo(inner: Header) -> Self {
36 Self::Tempo(TempoHeader {
37 general_gas_limit: inner.gas_limit,
38 shared_gas_limit: 0,
39 timestamp_millis_part: 0,
40 inner,
41 consensus_context: None,
42 })
43 }
44
45 pub const fn as_tempo(&self) -> Option<&TempoHeader> {
47 match self {
48 Self::Tempo(header) => Some(header),
49 Self::Ethereum(_) => None,
50 }
51 }
52
53 pub const fn inner(&self) -> &Header {
55 match self {
56 Self::Tempo(header) => &header.inner,
57 Self::Ethereum(header) => header,
58 }
59 }
60
61 const fn inner_mut(&mut self) -> &mut Header {
62 match self {
63 Self::Tempo(header) => &mut header.inner,
64 Self::Ethereum(header) => header,
65 }
66 }
67
68 pub const fn set_transactions_root(&mut self, transactions_root: B256) {
70 self.inner_mut().transactions_root = transactions_root;
71 }
72
73 pub const fn set_ommers_hash(&mut self, ommers_hash: B256) {
75 self.inner_mut().ommers_hash = ommers_hash;
76 }
77
78 pub fn into_inner(self) -> Header {
80 match self {
81 Self::Tempo(header) => header.inner,
82 Self::Ethereum(header) => header,
83 }
84 }
85
86 pub fn hash_slow(&self) -> B256 {
88 match self {
89 Self::Tempo(header) => header.hash_slow(),
90 Self::Ethereum(header) => header.hash_slow(),
91 }
92 }
93}
94
95impl From<Header> for FoundryHeader {
96 fn from(value: Header) -> Self {
97 Self::Ethereum(value)
98 }
99}
100
101impl From<TempoHeader> for FoundryHeader {
102 fn from(value: TempoHeader) -> Self {
103 Self::Tempo(value)
104 }
105}
106
107impl AsRef<Self> for FoundryHeader {
108 fn as_ref(&self) -> &Self {
109 self
110 }
111}
112
113impl Deref for FoundryHeader {
114 type Target = Header;
115
116 fn deref(&self) -> &Self::Target {
117 self.inner()
118 }
119}
120
121impl Encodable for FoundryHeader {
122 fn encode(&self, out: &mut dyn BufMut) {
123 match self {
124 Self::Tempo(header) => header.encode(out),
125 Self::Ethereum(header) => header.encode(out),
126 }
127 }
128
129 fn length(&self) -> usize {
130 match self {
131 Self::Tempo(header) => header.length(),
132 Self::Ethereum(header) => header.length(),
133 }
134 }
135}
136
137impl Decodable for FoundryHeader {
138 fn decode(buf: &mut &[u8]) -> Result<Self> {
139 let mut tempo_buf = *buf;
142 if let Ok(header) = TempoHeader::decode(&mut tempo_buf) {
143 *buf = tempo_buf;
144 return Ok(Self::Tempo(header));
145 }
146
147 Header::decode(buf).map(Self::Ethereum)
148 }
149}
150
151impl Sealable for FoundryHeader {
152 fn hash_slow(&self) -> B256 {
153 Self::hash_slow(self)
154 }
155}
156
157macro_rules! delegate_header_methods {
158 ($($method:ident -> $return_type:ty),+ $(,)?) => {
159 $(
160 fn $method(&self) -> $return_type {
161 self.inner().$method()
162 }
163 )+
164 };
165}
166
167impl BlockHeader for FoundryHeader {
168 delegate_header_methods! {
169 parent_hash -> B256,
170 ommers_hash -> B256,
171 beneficiary -> Address,
172 state_root -> B256,
173 transactions_root -> B256,
174 receipts_root -> B256,
175 withdrawals_root -> Option<B256>,
176 logs_bloom -> Bloom,
177 difficulty -> U256,
178 number -> u64,
179 gas_limit -> u64,
180 gas_used -> u64,
181 timestamp -> u64,
182 mix_hash -> Option<B256>,
183 nonce -> Option<B64>,
184 base_fee_per_gas -> Option<u64>,
185 blob_gas_used -> Option<u64>,
186 excess_blob_gas -> Option<u64>,
187 parent_beacon_block_root -> Option<B256>,
188 requests_hash -> Option<B256>,
189 block_access_list_hash -> Option<B256>,
190 slot_number -> Option<u64>,
191 }
192
193 fn extra_data(&self) -> &Bytes {
194 self.inner().extra_data()
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn rlp_roundtrip_preserves_network_header() {
204 for header in [
205 Header { number: 1, ..Default::default() }.into(),
206 FoundryHeader::tempo(Header { number: 2, gas_limit: 30_000_000, ..Default::default() }),
207 ] {
208 let encoded = alloy_rlp::encode(&header);
209 let decoded = FoundryHeader::decode(&mut encoded.as_ref()).unwrap();
210
211 assert_eq!(decoded, header);
212 assert_eq!(decoded.hash_slow(), header.hash_slow());
213 if let Some(tempo) = header.as_tempo() {
214 assert_eq!(header.hash_slow(), tempo.hash_slow());
215 }
216 }
217 }
218
219 #[test]
220 fn serde_roundtrip_preserves_tempo_fields() {
221 let header =
222 FoundryHeader::tempo(Header { number: 1, gas_limit: 30_000_000, ..Default::default() });
223 let value = serde_json::to_value(&header).unwrap();
224
225 assert_eq!(value["mainBlockGeneralGasLimit"], "0x1c9c380");
226 assert_eq!(serde_json::from_value::<FoundryHeader>(value).unwrap(), header);
227 }
228}