arbos/
header.rs

1use alloy_primitives::{keccak256, Address, B256, U256};
2
3/// ArbOS state storage address.
4pub 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/// Holds Arbitrum-specific header metadata.
30#[derive(Debug, Clone, Default)]
31pub struct ArbHeaderInfo {
32    /// Merkle root of sent messages.
33    pub send_root: B256,
34    /// Number of messages sent.
35    pub send_count: u64,
36    /// Corresponding L1 block number.
37    pub l1_block_number: u64,
38    /// ArbOS format version.
39    pub arbos_format_version: u64,
40}
41
42impl ArbHeaderInfo {
43    /// Compute the mix_hash from send_count, l1_block_number, and arbos_version.
44    pub fn compute_mix_hash(&self) -> B256 {
45        compute_arbos_mixhash(
46            self.send_count,
47            self.l1_block_number,
48            self.arbos_format_version,
49        )
50    }
51}
52
53/// Compute the mix hash from the three u64 components.
54///
55/// Layout: ]send_count (8 bytes)\]]l1_block_number (8 bytes)\]]arbos_version (8 bytes)\]]0..0\]
56pub fn compute_arbos_mixhash(send_count: u64, l1_block_number: u64, arbos_version: u64) -> B256 {
57    let mut mix = [0u8; 32];
58    mix[0..8].copy_from_slice(&send_count.to_be_bytes());
59    mix[8..16].copy_from_slice(&l1_block_number.to_be_bytes());
60    mix[16..24].copy_from_slice(&arbos_version.to_be_bytes());
61    B256::from(mix)
62}
63
64/// Extract the send root from the first 32 bytes of header extra_data.
65pub fn extract_send_root_from_header_extra(extra: &[u8]) -> B256 {
66    if extra.len() >= 32 {
67        B256::from_slice(&extra[..32])
68    } else {
69        B256::ZERO
70    }
71}
72
73/// Extract ArbOS version from header mix_hash (bytes 16-23).
74pub fn extract_arbos_version_from_mix_hash(mix_hash: B256) -> u64 {
75    let mut buf = [0u8; 8];
76    buf.copy_from_slice(&mix_hash.0[16..24]);
77    u64::from_be_bytes(buf)
78}
79
80/// Extract send count from header mix_hash (bytes 0-7).
81pub fn extract_send_count_from_mix_hash(mix_hash: B256) -> u64 {
82    let mut buf = [0u8; 8];
83    buf.copy_from_slice(&mix_hash.0[0..8]);
84    u64::from_be_bytes(buf)
85}
86
87/// Extract L1 block number from header mix_hash (bytes 8-15).
88pub fn extract_l1_block_number_from_mix_hash(mix_hash: B256) -> u64 {
89    let mut buf = [0u8; 8];
90    buf.copy_from_slice(&mix_hash.0[8..16]);
91    u64::from_be_bytes(buf)
92}
93
94/// Convert a u64 to a left-padded B256 (big-endian in last 8 bytes).
95fn uint_to_hash_u64_be(k: u64) -> B256 {
96    let mut out = [0u8; 32];
97    out[24..32].copy_from_slice(&k.to_be_bytes());
98    B256::from(out)
99}
100
101/// Map a storage key + sub-key to a derived storage slot.
102fn storage_key_map(storage_key: &[u8], key: B256) -> B256 {
103    let boundary = 31usize;
104    let mut data = Vec::with_capacity(storage_key.len() + boundary);
105    data.extend_from_slice(storage_key);
106    data.extend_from_slice(&key.0[..boundary]);
107    let h = keccak256(&data);
108    let mut mapped = [0u8; 32];
109    mapped[..boundary].copy_from_slice(&h.0[..boundary]);
110    mapped[boundary] = key.0[boundary];
111    B256::from(mapped)
112}
113
114/// Derive a subspace key from parent + id.
115fn subspace(parent: &[u8], id: &[u8]) -> [u8; 32] {
116    let mut data = Vec::with_capacity(parent.len() + id.len());
117    data.extend_from_slice(parent);
118    data.extend_from_slice(id);
119    keccak256(&data).0
120}
121
122/// Calculate the number of partials in the Merkle accumulator.
123fn calc_num_partials(size: u64) -> u64 {
124    if size == 0 {
125        return 0;
126    }
127    64 - size.leading_zeros() as u64
128}
129
130/// Read a u64 from storage at a given slot (big-endian in last 8 bytes).
131pub fn read_storage_u64_be<F: Fn(Address, B256) -> Option<U256>>(
132    read_slot: &F,
133    addr: Address,
134    slot: B256,
135) -> Option<u64> {
136    let val = read_slot(addr, slot)?;
137    let bytes: [u8; 32] = val.to_be_bytes::<32>();
138    let mut buf = [0u8; 8];
139    buf.copy_from_slice(&bytes[24..32]);
140    Some(u64::from_be_bytes(buf))
141}
142
143/// Read a B256 hash from storage at a given slot.
144pub fn read_storage_hash<F: Fn(Address, B256) -> Option<U256>>(
145    read_slot: &F,
146    addr: Address,
147    slot: B256,
148) -> Option<B256> {
149    let val = read_slot(addr, slot)?;
150    let bytes: [u8; 32] = val.to_be_bytes::<32>();
151    Some(B256::from(bytes))
152}
153
154/// Compute the Merkle root from partials stored in state.
155pub fn merkle_root_from_partials<F: Fn(Address, B256) -> Option<U256>>(
156    read_slot: &F,
157    addr: Address,
158    send_merkle_storage_key: &[u8],
159    size: u64,
160) -> Option<B256> {
161    if size == 0 {
162        return Some(B256::ZERO);
163    }
164    let mut hash_so_far: Option<B256> = None;
165    let mut capacity_in_hash: u64 = 0;
166    let mut capacity = 1u64;
167    let num_partials = calc_num_partials(size);
168    for level in 0..num_partials {
169        let key = uint_to_hash_u64_be(2 + level);
170        let slot = storage_key_map(send_merkle_storage_key, key);
171        let partial = read_storage_hash(read_slot, addr, slot).unwrap_or(B256::ZERO);
172        if partial != B256::ZERO {
173            if let Some(mut h) = hash_so_far {
174                while capacity_in_hash < capacity {
175                    let combined = [h.0.as_slice(), &[0u8; 32]].concat();
176                    h = keccak256(&combined);
177                    capacity_in_hash *= 2;
178                }
179                let combined = [partial.0.as_slice(), h.0.as_slice()].concat();
180                hash_so_far = Some(keccak256(&combined));
181                capacity_in_hash = 2 * capacity;
182            } else {
183                hash_so_far = Some(partial);
184                capacity_in_hash = capacity;
185            }
186        }
187        capacity = capacity.saturating_mul(2);
188    }
189    hash_so_far
190}
191
192/// Derive ArbHeaderInfo from storage reads.
193pub fn derive_arb_header_info<F: Fn(Address, B256) -> Option<U256>>(
194    read_slot: &F,
195) -> Option<ArbHeaderInfo> {
196    let addr = ARBOS_STATE_ADDRESS;
197    let root_storage_key: &[u8] = &[];
198
199    let version_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(0));
200    let arbos_version = read_storage_u64_be(read_slot, addr, version_slot)?;
201
202    let send_merkle_sub = subspace(root_storage_key, &[5u8]);
203    let blockhashes_sub = subspace(root_storage_key, &[6u8]);
204
205    let send_count_slot = storage_key_map(&send_merkle_sub, uint_to_hash_u64_be(0));
206    let send_count = read_storage_u64_be(read_slot, addr, send_count_slot).unwrap_or(0);
207
208    let send_root = merkle_root_from_partials(read_slot, addr, &send_merkle_sub, send_count)
209        .unwrap_or(B256::ZERO);
210
211    let l1_block_num_slot = storage_key_map(&blockhashes_sub, uint_to_hash_u64_be(0));
212    let l1_block_number = read_storage_u64_be(read_slot, addr, l1_block_num_slot).unwrap_or(0);
213
214    Some(ArbHeaderInfo {
215        send_root,
216        send_count,
217        l1_block_number,
218        arbos_format_version: arbos_version,
219    })
220}
221
222/// Get the storage address and slot for the ArbOS L1 block number.
223pub fn arbos_l1_block_number_slot() -> (Address, B256) {
224    let addr = ARBOS_STATE_ADDRESS;
225    let root_storage_key: &[u8] = &[];
226    let blockhashes_sub = subspace(root_storage_key, &[6u8]);
227    let l1_block_num_slot = storage_key_map(&blockhashes_sub, uint_to_hash_u64_be(0));
228    (addr, l1_block_num_slot)
229}
230
231/// Read ArbOS version from storage.
232pub fn read_arbos_version<F: Fn(Address, B256) -> Option<U256>>(read_slot: &F) -> Option<u64> {
233    let addr = ARBOS_STATE_ADDRESS;
234    let root_storage_key: &[u8] = &[];
235    let version_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(0));
236    read_storage_u64_be(read_slot, addr, version_slot)
237}
238
239/// Read the L2 per-block gas limit from storage.
240pub fn read_l2_per_block_gas_limit<F: Fn(Address, B256) -> Option<U256>>(
241    read_slot: &F,
242) -> Option<u64> {
243    let addr = ARBOS_STATE_ADDRESS;
244    let root_storage_key: &[u8] = &[];
245    let l2_pricing_subspace = subspace(root_storage_key, &[1u8]);
246    let per_block_gas_limit_slot = storage_key_map(&l2_pricing_subspace, uint_to_hash_u64_be(1));
247    read_storage_u64_be(read_slot, addr, per_block_gas_limit_slot)
248}
249
250/// Read the L2 base fee from storage.
251pub fn read_l2_base_fee<F: Fn(Address, B256) -> Option<U256>>(read_slot: &F) -> Option<u64> {
252    let addr = ARBOS_STATE_ADDRESS;
253    let root_storage_key: &[u8] = &[];
254    let l2_pricing_subspace = subspace(root_storage_key, &[1u8]);
255    let price_per_unit_slot = storage_key_map(&l2_pricing_subspace, uint_to_hash_u64_be(2));
256    read_storage_u64_be(read_slot, addr, price_per_unit_slot)
257}