1use alloy_primitives::{Address, B256, U256, keccak256};
2
3pub const ARBOS_STATE_ADDRESS: Address = {
5 let mut bytes = [0u8; 20];
6 bytes[0] = 0xA4;
7 bytes[1] = 0xB0;
8 bytes[2] = 0x5F;
9 bytes[3] = 0xFF;
10 bytes[4] = 0xFF;
11 bytes[5] = 0xFF;
12 bytes[6] = 0xFF;
13 bytes[7] = 0xFF;
14 bytes[8] = 0xFF;
15 bytes[9] = 0xFF;
16 bytes[10] = 0xFF;
17 bytes[11] = 0xFF;
18 bytes[12] = 0xFF;
19 bytes[13] = 0xFF;
20 bytes[14] = 0xFF;
21 bytes[15] = 0xFF;
22 bytes[16] = 0xFF;
23 bytes[17] = 0xFF;
24 bytes[18] = 0xFF;
25 bytes[19] = 0xFF;
26 Address::new(bytes)
27};
28
29#[derive(Debug, Clone, Default)]
30pub struct ArbHeaderInfo {
31 pub send_root: B256,
32 pub send_count: u64,
33 pub l1_block_number: u64,
34 pub arbos_format_version: u64,
35 pub collect_tips: bool,
36}
37
38impl ArbHeaderInfo {
39 pub fn compute_mix_hash(&self) -> B256 {
40 compute_arbos_mixhash(
41 self.send_count,
42 self.l1_block_number,
43 self.arbos_format_version,
44 self.collect_tips,
45 )
46 }
47}
48
49pub fn compute_arbos_mixhash(
50 send_count: u64,
51 l1_block_number: u64,
52 arbos_version: u64,
53 collect_tips: bool,
54) -> B256 {
55 let mut mix = [0u8; 32];
56 mix[0..8].copy_from_slice(&send_count.to_be_bytes());
57 mix[8..16].copy_from_slice(&l1_block_number.to_be_bytes());
58 mix[16..24].copy_from_slice(&arbos_version.to_be_bytes());
59 if collect_tips && arbos_version != arb_chainspec::arbos_version::ARBOS_VERSION_COLLECT_TIPS_OLD
60 {
61 mix[25] = 1;
62 }
63 B256::from(mix)
64}
65
66pub fn extract_collect_tips_from_mix_hash(mix_hash: B256, arbos_version: u64) -> bool {
67 if arbos_version == arb_chainspec::arbos_version::ARBOS_VERSION_COLLECT_TIPS_OLD {
68 return true;
69 }
70 mix_hash.0[25] & 0x1 == 1
71}
72
73pub fn extract_send_root_from_header_extra(extra: &[u8]) -> B256 {
75 if extra.len() >= 32 {
76 B256::from_slice(&extra[..32])
77 } else {
78 B256::ZERO
79 }
80}
81
82pub fn extract_arbos_version_from_mix_hash(mix_hash: B256) -> u64 {
84 let mut buf = [0u8; 8];
85 buf.copy_from_slice(&mix_hash.0[16..24]);
86 u64::from_be_bytes(buf)
87}
88
89pub fn extract_send_count_from_mix_hash(mix_hash: B256) -> u64 {
91 let mut buf = [0u8; 8];
92 buf.copy_from_slice(&mix_hash.0[0..8]);
93 u64::from_be_bytes(buf)
94}
95
96pub fn extract_l1_block_number_from_mix_hash(mix_hash: B256) -> u64 {
98 let mut buf = [0u8; 8];
99 buf.copy_from_slice(&mix_hash.0[8..16]);
100 u64::from_be_bytes(buf)
101}
102
103fn uint_to_hash_u64_be(k: u64) -> B256 {
105 let mut out = [0u8; 32];
106 out[24..32].copy_from_slice(&k.to_be_bytes());
107 B256::from(out)
108}
109
110fn storage_key_map(storage_key: &[u8], key: B256) -> B256 {
112 let boundary = 31usize;
113 let mut data = Vec::with_capacity(storage_key.len() + boundary);
114 data.extend_from_slice(storage_key);
115 data.extend_from_slice(&key.0[..boundary]);
116 let h = keccak256(&data);
117 let mut mapped = [0u8; 32];
118 mapped[..boundary].copy_from_slice(&h.0[..boundary]);
119 mapped[boundary] = key.0[boundary];
120 B256::from(mapped)
121}
122
123fn subspace(parent: &[u8], id: &[u8]) -> [u8; 32] {
125 let mut data = Vec::with_capacity(parent.len() + id.len());
126 data.extend_from_slice(parent);
127 data.extend_from_slice(id);
128 keccak256(&data).0
129}
130
131fn calc_num_partials(size: u64) -> u64 {
133 if size == 0 {
134 return 0;
135 }
136 64 - size.leading_zeros() as u64
137}
138
139pub fn read_storage_u64_be<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
145 read_slot: &F,
146 addr: Address,
147 slot: B256,
148) -> Result<Option<u64>, E> {
149 let Some(val) = read_slot(addr, slot)? else {
150 return Ok(None);
151 };
152 let bytes: [u8; 32] = val.to_be_bytes::<32>();
153 let mut buf = [0u8; 8];
154 buf.copy_from_slice(&bytes[24..32]);
155 Ok(Some(u64::from_be_bytes(buf)))
156}
157
158pub fn read_storage_hash<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
160 read_slot: &F,
161 addr: Address,
162 slot: B256,
163) -> Result<Option<B256>, E> {
164 let Some(val) = read_slot(addr, slot)? else {
165 return Ok(None);
166 };
167 Ok(Some(B256::from(val.to_be_bytes::<32>())))
168}
169
170pub fn merkle_root_from_partials<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
172 read_slot: &F,
173 addr: Address,
174 send_merkle_storage_key: &[u8],
175 size: u64,
176) -> Result<Option<B256>, E> {
177 if size == 0 {
178 return Ok(Some(B256::ZERO));
179 }
180 let mut hash_so_far: Option<B256> = None;
181 let mut capacity_in_hash: u64 = 0;
182 let mut capacity = 1u64;
183 let num_partials = calc_num_partials(size);
184 for level in 0..num_partials {
185 let key = uint_to_hash_u64_be(2 + level);
186 let slot = storage_key_map(send_merkle_storage_key, key);
187 let partial = read_storage_hash(read_slot, addr, slot)?.unwrap_or(B256::ZERO);
188 if partial != B256::ZERO {
189 if let Some(mut h) = hash_so_far {
190 while capacity_in_hash < capacity {
191 let combined = [h.0.as_slice(), &[0u8; 32]].concat();
192 h = keccak256(&combined);
193 capacity_in_hash *= 2;
194 }
195 let combined = [partial.0.as_slice(), h.0.as_slice()].concat();
196 hash_so_far = Some(keccak256(&combined));
197 capacity_in_hash = 2 * capacity;
198 } else {
199 hash_so_far = Some(partial);
200 capacity_in_hash = capacity;
201 }
202 }
203 capacity = capacity.saturating_mul(2);
204 }
205 Ok(hash_so_far)
206}
207
208pub fn derive_arb_header_info<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
213 read_slot: &F,
214 coinbase: Address,
215) -> Result<Option<ArbHeaderInfo>, E> {
216 let addr = ARBOS_STATE_ADDRESS;
217 let root_storage_key: &[u8] = &[];
218
219 let version_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(0));
220 let Some(arbos_version) = read_storage_u64_be(read_slot, addr, version_slot)? else {
221 return Ok(None);
222 };
223
224 let send_merkle_sub = subspace(root_storage_key, &[5u8]);
225 let blockhashes_sub = subspace(root_storage_key, &[6u8]);
226
227 let send_count_slot = storage_key_map(&send_merkle_sub, uint_to_hash_u64_be(0));
228 let send_count = read_storage_u64_be(read_slot, addr, send_count_slot)?.unwrap_or(0);
229
230 let send_root = merkle_root_from_partials(read_slot, addr, &send_merkle_sub, send_count)?
231 .unwrap_or(B256::ZERO);
232
233 let l1_block_num_slot = storage_key_map(&blockhashes_sub, uint_to_hash_u64_be(0));
234 let l1_block_number = read_storage_u64_be(read_slot, addr, l1_block_num_slot)?.unwrap_or(0);
235
236 let collect_tips_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(11));
239 let collect_tips = read_storage_u64_be(read_slot, addr, collect_tips_slot)?.unwrap_or(0) != 0
240 && coinbase == crate::l1_pricing::BATCH_POSTER_ADDRESS;
241
242 Ok(Some(ArbHeaderInfo {
243 send_root,
244 send_count,
245 l1_block_number,
246 arbos_format_version: arbos_version,
247 collect_tips,
248 }))
249}
250
251pub fn arbos_l1_block_number_slot() -> (Address, B256) {
253 let addr = ARBOS_STATE_ADDRESS;
254 let root_storage_key: &[u8] = &[];
255 let blockhashes_sub = subspace(root_storage_key, &[6u8]);
256 let l1_block_num_slot = storage_key_map(&blockhashes_sub, uint_to_hash_u64_be(0));
257 (addr, l1_block_num_slot)
258}
259
260pub fn read_arbos_version<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
262 read_slot: &F,
263) -> Result<Option<u64>, E> {
264 let addr = ARBOS_STATE_ADDRESS;
265 let root_storage_key: &[u8] = &[];
266 let version_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(0));
267 read_storage_u64_be(read_slot, addr, version_slot)
268}
269
270pub fn read_l2_per_block_gas_limit<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
272 read_slot: &F,
273) -> Result<Option<u64>, E> {
274 let addr = ARBOS_STATE_ADDRESS;
275 let root_storage_key: &[u8] = &[];
276 let l2_pricing_subspace = subspace(root_storage_key, &[1u8]);
277 let per_block_gas_limit_slot = storage_key_map(&l2_pricing_subspace, uint_to_hash_u64_be(1));
278 read_storage_u64_be(read_slot, addr, per_block_gas_limit_slot)
279}
280
281pub fn read_l2_base_fee<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
283 read_slot: &F,
284) -> Result<Option<u64>, E> {
285 let addr = ARBOS_STATE_ADDRESS;
286 let root_storage_key: &[u8] = &[];
287 let l2_pricing_subspace = subspace(root_storage_key, &[1u8]);
288 let price_per_unit_slot = storage_key_map(&l2_pricing_subspace, uint_to_hash_u64_be(2));
289 read_storage_u64_be(read_slot, addr, price_per_unit_slot)
290}