arb_precompiles/
arbwasmcache.rs

1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, B256, U256};
3use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
4
5use crate::storage_slot::{
6    derive_subspace_key, map_slot, map_slot_b256, ARBOS_STATE_ADDRESS, CACHE_MANAGERS_KEY,
7    PROGRAMS_DATA_KEY, PROGRAMS_SUBSPACE, ROOT_STORAGE_KEY,
8};
9
10/// ArbWasmCache precompile address (0x72).
11pub const ARBWASMCACHE_ADDRESS: Address = Address::new([
12    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13    0x00, 0x00, 0x00, 0x72,
14]);
15
16// Function selectors.
17const IS_CACHE_MANAGER: [u8; 4] = [0xf1, 0x37, 0xfc, 0xda];
18const ALL_CACHE_MANAGERS: [u8; 4] = [0x35, 0x17, 0x3c, 0x26];
19const CACHE_CODEHASH: [u8; 4] = [0x0e, 0xa0, 0x7a, 0x7a];
20const CACHE_PROGRAM: [u8; 4] = [0xb6, 0xf4, 0xfb, 0x22];
21const EVICT_CODEHASH: [u8; 4] = [0xd4, 0x56, 0xcd, 0x34];
22const CODEHASH_IS_CACHED: [u8; 4] = [0x47, 0x97, 0x00, 0xf6];
23
24const SLOAD_GAS: u64 = 800;
25const COPY_GAS: u64 = 3;
26
27/// AddressSet by_address sub-key.
28const BY_ADDRESS_KEY: &[u8] = &[0];
29
30pub fn create_arbwasmcache_precompile() -> DynPrecompile {
31    DynPrecompile::new_stateful(PrecompileId::custom("arbwasmcache"), handler)
32}
33
34fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
35    // ArbWasmCache requires ArbOS >= 30 (Stylus).
36    if let Some(result) =
37        crate::check_precompile_version(arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS)
38    {
39        return result;
40    }
41
42    let data = input.data;
43    if data.len() < 4 {
44        return Err(PrecompileError::other("input too short"));
45    }
46
47    let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
48
49    let result = match selector {
50        // CacheCodehash: available only on ArbOS 30, replaced by CacheProgram at 31.
51        CACHE_CODEHASH => {
52            if let Some(result) = crate::check_method_version(
53                arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS,
54                arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS,
55            ) {
56                return result;
57            }
58            let _ = &mut input;
59            Err(PrecompileError::other("caller is not a cache manager"))
60        }
61        // CacheProgram: requires ArbOS >= 31 (StylusFixes).
62        CACHE_PROGRAM => {
63            if let Some(result) = crate::check_method_version(
64                arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_FIXES,
65                0,
66            ) {
67                return result;
68            }
69            let _ = &mut input;
70            Err(PrecompileError::other("caller is not a cache manager"))
71        }
72        IS_CACHE_MANAGER => handle_is_cache_manager(&mut input),
73        ALL_CACHE_MANAGERS => handle_all_cache_managers(&mut input),
74        CODEHASH_IS_CACHED => handle_codehash_is_cached(&mut input),
75        EVICT_CODEHASH => {
76            let _ = &mut input;
77            Err(PrecompileError::other("caller is not a cache manager"))
78        }
79        _ => Err(PrecompileError::other("unknown ArbWasmCache selector")),
80    };
81    crate::gas_check(input.gas, result)
82}
83
84// ── Helpers ──────────────────────────────────────────────────────────
85
86fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
87    input
88        .internals_mut()
89        .load_account(ARBOS_STATE_ADDRESS)
90        .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
91    Ok(())
92}
93
94fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
95    let val = input
96        .internals_mut()
97        .sload(ARBOS_STATE_ADDRESS, slot)
98        .map_err(|_| PrecompileError::other("sload failed"))?;
99    Ok(val.data)
100}
101
102/// Compute the cache managers AddressSet storage key.
103fn cache_managers_key() -> B256 {
104    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
105    derive_subspace_key(programs_key.as_slice(), CACHE_MANAGERS_KEY)
106}
107
108/// Check if an address is a cache manager member.
109fn handle_is_cache_manager(input: &mut PrecompileInput<'_>) -> PrecompileResult {
110    let data = input.data;
111    if data.len() < 36 {
112        return Err(PrecompileError::other("calldata too short for address arg"));
113    }
114    // Address is right-aligned in 32-byte word.
115    let mut addr_bytes = [0u8; 20];
116    addr_bytes.copy_from_slice(&data[16..36]);
117    let addr = Address::from(addr_bytes);
118
119    load_arbos(input)?;
120
121    let cm_key = cache_managers_key();
122    let by_addr_key = derive_subspace_key(cm_key.as_slice(), BY_ADDRESS_KEY);
123    let addr_hash = address_to_b256(addr);
124    let slot = map_slot_b256(by_addr_key.as_slice(), &addr_hash);
125    let value = sload_field(input, slot)?;
126
127    let is_member = value != U256::ZERO;
128    let result = if is_member {
129        U256::from(1u64)
130    } else {
131        U256::ZERO
132    };
133    Ok(PrecompileOutput::new(
134        SLOAD_GAS + COPY_GAS,
135        result.to_be_bytes::<32>().to_vec().into(),
136    ))
137}
138
139/// Return all cache manager addresses.
140fn handle_all_cache_managers(input: &mut PrecompileInput<'_>) -> PrecompileResult {
141    load_arbos(input)?;
142
143    let cm_key = cache_managers_key();
144    let size_slot = map_slot(cm_key.as_slice(), 0);
145    let size = sload_field(input, size_slot)?.saturating_to::<u64>();
146    let mut sloads: u64 = 1;
147
148    // Cap to prevent excessive reads.
149    let count = size.min(256);
150
151    // ABI: offset to dynamic array, then length, then elements.
152    let mut out = Vec::with_capacity(64 + count as usize * 32);
153    out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
154    out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
155
156    for i in 1..=count {
157        let addr_slot = map_slot(cm_key.as_slice(), i);
158        let addr_value = sload_field(input, addr_slot)?;
159        out.extend_from_slice(&addr_value.to_be_bytes::<32>());
160        sloads += 1;
161    }
162
163    Ok(PrecompileOutput::new(
164        (sloads * SLOAD_GAS + COPY_GAS).min(input.gas),
165        out.into(),
166    ))
167}
168
169/// Check if a program codehash is cached.
170fn handle_codehash_is_cached(input: &mut PrecompileInput<'_>) -> PrecompileResult {
171    let data = input.data;
172    if data.len() < 36 {
173        return Err(PrecompileError::other("calldata too short for bytes32 arg"));
174    }
175    let mut bytes = [0u8; 32];
176    bytes.copy_from_slice(&data[4..36]);
177    let codehash = B256::from(bytes);
178
179    load_arbos(input)?;
180
181    let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
182    let data_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_DATA_KEY);
183    let program_slot = map_slot_b256(data_key.as_slice(), &codehash);
184    let program_word = sload_field(input, program_slot)?;
185
186    // Byte 14 of the program word is the cached flag.
187    let word_bytes = program_word.to_be_bytes::<32>();
188    let is_cached = word_bytes[14] != 0;
189
190    let result = if is_cached {
191        U256::from(1u64)
192    } else {
193        U256::ZERO
194    };
195    Ok(PrecompileOutput::new(
196        SLOAD_GAS + COPY_GAS,
197        result.to_be_bytes::<32>().to_vec().into(),
198    ))
199}
200
201fn address_to_b256(addr: Address) -> B256 {
202    let mut bytes = [0u8; 32];
203    bytes[12..32].copy_from_slice(addr.as_slice());
204    B256::from(bytes)
205}