arb_precompiles/
arbaddresstable.rs

1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, Bytes, U256};
3use alloy_sol_types::SolInterface;
4use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
5
6use crate::{
7    interfaces::IArbAddressTable,
8    storage_slot::{
9        derive_subspace_key, map_slot, map_slot_b256, ADDRESS_TABLE_SUBSPACE, ARBOS_STATE_ADDRESS,
10        ROOT_STORAGE_KEY,
11    },
12};
13
14/// ArbAddressTable precompile address (0x66).
15pub const ARBADDRESSTABLE_ADDRESS: Address = Address::new([
16    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
17    0x00, 0x00, 0x00, 0x66,
18]);
19
20const SLOAD_GAS: u64 = 800;
21const SSTORE_GAS: u64 = 20_000;
22const COPY_GAS: u64 = 3;
23
24pub fn create_arbaddresstable_precompile() -> DynPrecompile {
25    DynPrecompile::new_stateful(PrecompileId::custom("arbaddresstable"), handler)
26}
27
28fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
29    let gas_limit = input.gas;
30    crate::init_precompile_gas(input.data.len());
31
32    let call = match IArbAddressTable::ArbAddressTableCalls::abi_decode(input.data) {
33        Ok(c) => c,
34        Err(_) => return crate::burn_all_revert(gas_limit),
35    };
36
37    use IArbAddressTable::ArbAddressTableCalls as Calls;
38    let result = match call {
39        Calls::size(_) => handle_size(&mut input),
40        Calls::addressExists(c) => handle_address_exists(&mut input, c.addr),
41        Calls::lookup(c) => handle_lookup(&mut input, c.addr),
42        Calls::lookupIndex(c) => handle_lookup_index(&mut input, c.index),
43        Calls::register(c) => handle_register(&mut input, c.addr),
44        Calls::compress(c) => handle_compress(&mut input, c.addr),
45        Calls::decompress(c) => handle_decompress(&mut input, &c.buf, c.offset),
46    };
47    crate::gas_check(gas_limit, result)
48}
49
50// ── helpers ──────────────────────────────────────────────────────────
51
52fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
53    input
54        .internals_mut()
55        .load_account(ARBOS_STATE_ADDRESS)
56        .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
57    Ok(())
58}
59
60fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
61    let val = input
62        .internals_mut()
63        .sload(ARBOS_STATE_ADDRESS, slot)
64        .map_err(|_| PrecompileError::other("sload failed"))?;
65    Ok(val.data)
66}
67
68fn sstore_field(
69    input: &mut PrecompileInput<'_>,
70    slot: U256,
71    value: U256,
72) -> Result<(), PrecompileError> {
73    input
74        .internals_mut()
75        .sstore(ARBOS_STATE_ADDRESS, slot, value)
76        .map_err(|_| PrecompileError::other("sstore failed"))?;
77    Ok(())
78}
79
80/// AddressTable numItems is stored at offset 0 in the table's subspace storage.
81fn handle_size(input: &mut PrecompileInput<'_>) -> PrecompileResult {
82    let gas_limit = input.gas;
83    load_arbos(input)?;
84
85    let table_key = derive_subspace_key(ROOT_STORAGE_KEY, ADDRESS_TABLE_SUBSPACE);
86    let size_slot = map_slot(table_key.as_slice(), 0);
87    let size = sload_field(input, size_slot)?;
88
89    Ok(PrecompileOutput::new(
90        (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
91        size.to_be_bytes::<32>().to_vec().into(),
92    ))
93}
94
95fn handle_address_exists(input: &mut PrecompileInput<'_>, addr: Address) -> PrecompileResult {
96    let gas_limit = input.gas;
97    load_arbos(input)?;
98
99    // byAddress = OpenSubStorage([]byte{}) — sub-storage with empty key.
100    let table_key = derive_subspace_key(ROOT_STORAGE_KEY, ADDRESS_TABLE_SUBSPACE);
101    let by_address_key = derive_subspace_key(table_key.as_slice(), &[]);
102
103    let addr_as_b256 = alloy_primitives::B256::left_padding_from(addr.as_slice());
104    let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
105
106    let value = sload_field(input, member_slot)?;
107    let exists = if value != U256::ZERO {
108        U256::from(1u64)
109    } else {
110        U256::ZERO
111    };
112
113    // OAS(1) + byAddress.Get(1) + argsCost(3) + resultCost(3).
114    Ok(PrecompileOutput::new(
115        (2 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
116        exists.to_be_bytes::<32>().to_vec().into(),
117    ))
118}
119
120fn handle_lookup(input: &mut PrecompileInput<'_>, addr: Address) -> PrecompileResult {
121    let gas_limit = input.gas;
122    load_arbos(input)?;
123
124    let table_key = derive_subspace_key(ROOT_STORAGE_KEY, ADDRESS_TABLE_SUBSPACE);
125    let by_address_key = derive_subspace_key(table_key.as_slice(), &[]);
126
127    let addr_as_b256 = alloy_primitives::B256::left_padding_from(addr.as_slice());
128    let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
129
130    let value = sload_field(input, member_slot)?;
131    if value == U256::ZERO {
132        return Err(PrecompileError::other(
133            "address does not exist in AddressTable",
134        ));
135    }
136
137    // Stored value is the 1-based index, so subtract 1.
138    let index = value.wrapping_sub(U256::from(1u64));
139    // OAS(1) + byAddress.Get(1) + argsCost(3) + resultCost(3).
140    Ok(PrecompileOutput::new(
141        (2 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
142        index.to_be_bytes::<32>().to_vec().into(),
143    ))
144}
145
146/// Reverse entries are stored at offset (index + 1) in the table's backing storage.
147fn handle_lookup_index(input: &mut PrecompileInput<'_>, index_u256: U256) -> PrecompileResult {
148    let gas_limit = input.gas;
149    let index: u64 = index_u256
150        .try_into()
151        .map_err(|_| PrecompileError::other("index too large"))?;
152    load_arbos(input)?;
153
154    let table_key = derive_subspace_key(ROOT_STORAGE_KEY, ADDRESS_TABLE_SUBSPACE);
155    // Reverse lookup is at offset (index + 1) — 1-indexed.
156    let entry_slot = map_slot(table_key.as_slice(), index + 1);
157    let value = sload_field(input, entry_slot)?;
158
159    if value == U256::ZERO {
160        return Err(PrecompileError::other(
161            "index does not exist in AddressTable",
162        ));
163    }
164
165    // OAS(1) + numItems(1) + backing(1) + argsCost(3) + resultCost(3).
166    Ok(PrecompileOutput::new(
167        (3 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
168        value.to_be_bytes::<32>().to_vec().into(),
169    ))
170}
171
172/// If already registered, returns the existing index; otherwise registers and
173/// returns the new 0-based index.
174fn handle_register(input: &mut PrecompileInput<'_>, addr: Address) -> PrecompileResult {
175    let gas_limit = input.gas;
176    load_arbos(input)?;
177
178    let table_key = derive_subspace_key(ROOT_STORAGE_KEY, ADDRESS_TABLE_SUBSPACE);
179    let by_address_key = derive_subspace_key(table_key.as_slice(), &[]);
180    let addr_as_b256 = alloy_primitives::B256::left_padding_from(addr.as_slice());
181
182    // Check if address already exists in byAddress mapping.
183    let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
184    let existing = sload_field(input, member_slot)?;
185
186    if existing != U256::ZERO {
187        // Already registered — return 0-based index.
188        let index = existing.wrapping_sub(U256::from(1u64));
189        // OAS(1) + byAddress.Get(1) + argsCost(3) + resultCost(3).
190        return Ok(PrecompileOutput::new(
191            (2 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
192            index.to_be_bytes::<32>().to_vec().into(),
193        ));
194    }
195
196    // Not yet registered — add it.
197    // Read numItems and increment it.
198    let num_items_slot = map_slot(table_key.as_slice(), 0);
199    let num_items = sload_field(input, num_items_slot)?;
200    let num_items_u64: u64 = num_items
201        .try_into()
202        .map_err(|_| PrecompileError::other("invalid numItems"))?;
203    let new_num_items = num_items_u64 + 1;
204    sstore_field(input, num_items_slot, U256::from(new_num_items))?;
205
206    // Store reverse mapping: backingStorage[newNumItems] = addr_hash.
207    let reverse_slot = map_slot(table_key.as_slice(), new_num_items);
208    sstore_field(input, reverse_slot, U256::from_be_bytes(addr_as_b256.0))?;
209
210    // Store byAddress mapping: byAddress[addr_hash] = newNumItems (1-based).
211    sstore_field(input, member_slot, U256::from(new_num_items))?;
212
213    // Return 0-based index.
214    let index = new_num_items - 1;
215
216    // OAS(1) + byAddress.Get(1) + numItems.Get(1) + 3 sstores + argsCost(3) + resultCost(3).
217    let gas_used = 3 * SLOAD_GAS + 3 * SSTORE_GAS + 2 * COPY_GAS;
218
219    Ok(PrecompileOutput::new(
220        gas_used.min(gas_limit),
221        U256::from(index).to_be_bytes::<32>().to_vec().into(),
222    ))
223}
224
225fn handle_compress(input: &mut PrecompileInput<'_>, addr: Address) -> PrecompileResult {
226    let gas_limit = input.gas;
227    load_arbos(input)?;
228
229    let table_key = derive_subspace_key(ROOT_STORAGE_KEY, ADDRESS_TABLE_SUBSPACE);
230    let by_address_key = derive_subspace_key(table_key.as_slice(), &[]);
231    let addr_as_b256 = alloy_primitives::B256::left_padding_from(addr.as_slice());
232    let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
233    let value = sload_field(input, member_slot)?;
234
235    let rlp_bytes = if value != U256::ZERO {
236        // Address exists — RLP-encode the 0-based index.
237        let index = value.wrapping_sub(U256::from(1u64)).to::<u64>();
238        rlp_encode_u64(index)
239    } else {
240        // Not in table — RLP-encode the raw 20-byte address.
241        rlp_encode_bytes(addr.as_slice())
242    };
243
244    // ABI-encode as `bytes`: offset (32) + length (32) + padded data.
245    let mut output = Vec::with_capacity(96);
246    output.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
247    output.extend_from_slice(&U256::from(rlp_bytes.len() as u64).to_be_bytes::<32>());
248    output.extend_from_slice(&rlp_bytes);
249    // Pad to 32-byte boundary.
250    let pad = (32 - rlp_bytes.len() % 32) % 32;
251    output.extend(std::iter::repeat_n(0u8, pad));
252
253    // OAS(1) + byAddress.Get(1) + argsCost(3) + resultCost(3 words for bytes encoding).
254    let result_words = (output.len() as u64).div_ceil(32);
255    Ok(PrecompileOutput::new(
256        (2 * SLOAD_GAS + COPY_GAS + result_words * COPY_GAS).min(gas_limit),
257        output.into(),
258    ))
259}
260
261/// If the RLP payload is 20 bytes it's a raw address; otherwise it's a u64
262/// index looked up in the table. Returns ABI-encoded (address, uint256 bytesRead).
263fn handle_decompress(
264    input: &mut PrecompileInput<'_>,
265    buf: &Bytes,
266    offset: U256,
267) -> PrecompileResult {
268    let gas_limit = input.gas;
269    let data_len = input.data.len();
270    let ioffset: usize = offset
271        .try_into()
272        .map_err(|_| PrecompileError::other("offset too large"))?;
273
274    if ioffset >= buf.len() {
275        return Err(PrecompileError::other("offset out of bounds"));
276    }
277    let slice = &buf[ioffset..];
278
279    load_arbos(input)?;
280
281    // Try to RLP-decode as byte string first.
282    let (decoded, bytes_read) =
283        rlp_decode_bytes(slice).map_err(|_| PrecompileError::other("RLP decode failed"))?;
284
285    let (addr, final_bytes_read) = if decoded.len() == 20 {
286        // Raw 20-byte address.
287        (Address::from_slice(&decoded), bytes_read)
288    } else {
289        // Re-decode as u64 index.
290        let (index, idx_bytes_read) =
291            rlp_decode_u64(slice).map_err(|_| PrecompileError::other("RLP decode index failed"))?;
292
293        let table_key = derive_subspace_key(ROOT_STORAGE_KEY, ADDRESS_TABLE_SUBSPACE);
294
295        // Bounds check: index < numItems.
296        let num_items_slot = map_slot(table_key.as_slice(), 0);
297        let num_items = sload_field(input, num_items_slot)?;
298        if U256::from(index) >= num_items {
299            return Err(PrecompileError::other(
300                "index does not exist in AddressTable",
301            ));
302        }
303
304        let entry_slot = map_slot(table_key.as_slice(), index + 1);
305        let value = sload_field(input, entry_slot)?;
306
307        // Extract 20-byte address from the 32-byte stored value.
308        let value_bytes = value.to_be_bytes::<32>();
309        let result_addr = Address::from_slice(&value_bytes[12..32]);
310        (result_addr, idx_bytes_read)
311    };
312
313    // ABI-encode (address, uint256).
314    let mut output = Vec::with_capacity(64);
315    output.extend_from_slice(&alloy_primitives::B256::left_padding_from(addr.as_slice()).0);
316    output.extend_from_slice(&U256::from(final_bytes_read as u64).to_be_bytes::<32>());
317
318    // Body: OAS(1) + 0 (raw addr) or OAS(1) + numItems(1) + backing(1) (index).
319    let body_sloads: u64 = if decoded.len() == 20 { 1 } else { 3 };
320    let arg_words = (data_len as u64).saturating_sub(4).div_ceil(32);
321    Ok(PrecompileOutput::new(
322        (body_sloads * SLOAD_GAS + (arg_words + 2) * COPY_GAS).min(gas_limit),
323        output.into(),
324    ))
325}
326
327// ── Minimal RLP helpers ─────────────────────────────────────────────
328
329/// RLP-encode a u64 value.
330fn rlp_encode_u64(val: u64) -> Vec<u8> {
331    if val == 0 {
332        return vec![0x80];
333    }
334    if val < 128 {
335        return vec![val as u8];
336    }
337    let bytes = val.to_be_bytes();
338    let start = bytes.iter().position(|&b| b != 0).unwrap_or(7);
339    let len = 8 - start;
340    let mut out = Vec::with_capacity(1 + len);
341    out.push(0x80 + len as u8);
342    out.extend_from_slice(&bytes[start..]);
343    out
344}
345
346/// RLP-encode a byte slice as an RLP string.
347fn rlp_encode_bytes(data: &[u8]) -> Vec<u8> {
348    if data.len() == 1 && data[0] < 128 {
349        return vec![data[0]];
350    }
351    if data.len() < 56 {
352        let mut out = Vec::with_capacity(1 + data.len());
353        out.push(0x80 + data.len() as u8);
354        out.extend_from_slice(data);
355        return out;
356    }
357    let len_bytes = {
358        let l = data.len() as u64;
359        let bytes = l.to_be_bytes();
360        let start = bytes.iter().position(|&b| b != 0).unwrap_or(7);
361        bytes[start..].to_vec()
362    };
363    let mut out = Vec::with_capacity(1 + len_bytes.len() + data.len());
364    out.push(0xb7 + len_bytes.len() as u8);
365    out.extend_from_slice(&len_bytes);
366    out.extend_from_slice(data);
367    out
368}
369
370/// RLP-decode a byte string from a slice, returning (decoded_bytes, total_bytes_consumed).
371fn rlp_decode_bytes(data: &[u8]) -> Result<(Vec<u8>, usize), &'static str> {
372    if data.is_empty() {
373        return Err("empty input");
374    }
375    let prefix = data[0];
376    if prefix < 0x80 {
377        // Single byte.
378        Ok((vec![prefix], 1))
379    } else if prefix <= 0xb7 {
380        // Short string (0-55 bytes).
381        let len = (prefix - 0x80) as usize;
382        if data.len() < 1 + len {
383            return Err("truncated short string");
384        }
385        Ok((data[1..1 + len].to_vec(), 1 + len))
386    } else if prefix <= 0xbf {
387        // Long string.
388        let len_of_len = (prefix - 0xb7) as usize;
389        if data.len() < 1 + len_of_len {
390            return Err("truncated long string length");
391        }
392        let mut len_bytes = [0u8; 8];
393        len_bytes[8 - len_of_len..].copy_from_slice(&data[1..1 + len_of_len]);
394        let len = u64::from_be_bytes(len_bytes) as usize;
395        let total = 1 + len_of_len + len;
396        if data.len() < total {
397            return Err("truncated long string data");
398        }
399        Ok((data[1 + len_of_len..total].to_vec(), total))
400    } else {
401        Err("unexpected list prefix")
402    }
403}
404
405/// RLP-decode a u64 from a slice.
406fn rlp_decode_u64(data: &[u8]) -> Result<(u64, usize), &'static str> {
407    if data.is_empty() {
408        return Err("empty input");
409    }
410    let prefix = data[0];
411    if prefix == 0x80 {
412        // Zero.
413        Ok((0, 1))
414    } else if prefix < 0x80 {
415        // Single byte value.
416        Ok((prefix as u64, 1))
417    } else if prefix <= 0x88 {
418        // Short string encoding for integers (up to 8 bytes).
419        let len = (prefix - 0x80) as usize;
420        if len > 8 || data.len() < 1 + len {
421            return Err("invalid u64 encoding");
422        }
423        let mut bytes = [0u8; 8];
424        bytes[8 - len..].copy_from_slice(&data[1..1 + len]);
425        Ok((u64::from_be_bytes(bytes), 1 + len))
426    } else {
427        Err("value too large for u64")
428    }
429}