1use std::sync::Arc;
2
3use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
4use alloy_primitives::{Address, B256, Log, U256};
5use alloy_sol_types::{SolError, SolEvent, SolInterface};
6use arb_context::ArbPrecompileCtx;
7use arb_storage::ARBOS_STATE_ADDRESS;
8use arbos::programs::{Program, params::StylusParams};
9use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult};
10
11use crate::{
12 ArbPrecompileError,
13 interfaces::{IArbWasm, IArbWasmCache},
14};
15
16pub const ARBWASMCACHE_ADDRESS: Address = Address::new([
18 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
19 0x00, 0x00, 0x00, 0x72,
20]);
21
22const SLOAD_GAS: u64 = 800;
23const COPY_GAS: u64 = 3;
24
25const COLD_ACCOUNT_ACCESS_GAS: u64 = 2600;
26const SSTORE_SET_GAS: u64 = 20_000;
27const SSTORE_RESET_GAS: u64 = 5_000;
28
29const EMIT_UPDATE_PROGRAM_CACHE_GAS: u64 = 375 + 3 * 375 + 32 * 8;
32
33pub fn create_arbwasmcache_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
34 DynPrecompile::new_stateful(PrecompileId::custom("arbwasmcache"), move |input| {
35 handler(input, &ctx)
36 })
37}
38
39fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
40 if let Some(result) =
41 crate::check_precompile_version(ctx, arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS)
42 {
43 return result;
44 }
45
46 let mut gas_used = 0u64;
47 let gas_limit = input.gas;
48 crate::init_precompile_gas(&mut gas_used, ctx, input.data.len());
49
50 let call = match IArbWasmCache::ArbWasmCacheCalls::abi_decode(input.data) {
51 Ok(c) => c,
52 Err(_) => return crate::burn_all_revert(gas_limit),
53 };
54 if let Some(r) = crate::reject_nonpayable_value(input.value, input.data, gas_limit, &[]) {
55 return r;
56 }
57 if let Some(r) = crate::reject_static_write(
58 input.is_static,
59 input.data,
60 gas_limit,
61 &[
62 [0x4c, 0xea, 0xc8, 0x17],
63 [0xe7, 0x3a, 0xc9, 0xf2],
64 [0xce, 0x97, 0x20, 0x13],
65 ],
66 ) {
67 return r;
68 }
69 if let Some(r) = crate::reject_delegate_nonpure(
70 input.target_address != input.bytecode_address,
71 input.data,
72 gas_limit,
73 &[],
74 ) {
75 return r;
76 }
77
78 use IArbWasmCache::ArbWasmCacheCalls;
79 let result = match call {
80 ArbWasmCacheCalls::cacheCodehash(c) => {
81 handle_cache_codehash(&mut input, ctx, &mut gas_used, c.codehash)
82 }
83 ArbWasmCacheCalls::cacheProgram(c) => {
84 handle_cache_program(&mut input, ctx, &mut gas_used, c.addr)
85 }
86 ArbWasmCacheCalls::evictCodehash(c) => {
87 handle_evict_codehash(&mut input, ctx, &mut gas_used, c.codehash)
88 }
89 ArbWasmCacheCalls::isCacheManager(c) => {
90 handle_is_cache_manager(&mut input, &mut gas_used, c.manager, ctx)
91 }
92 ArbWasmCacheCalls::allCacheManagers(_) => {
93 handle_all_cache_managers(&mut input, &mut gas_used, ctx)
94 }
95 ArbWasmCacheCalls::codehashIsCached(c) => {
96 handle_codehash_is_cached(&mut input, &mut gas_used, ctx, c.codehash)
97 }
98 };
99 crate::gas_check(ctx, gas_limit, gas_used, result)
100}
101
102fn words_for_bytes(n: u64) -> u64 {
103 n.div_ceil(32)
104}
105
106fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), ArbPrecompileError> {
109 input
110 .internals_mut()
111 .load_account(ARBOS_STATE_ADDRESS)
112 .map_err(ArbPrecompileError::fatal)?;
113 Ok(())
114}
115
116fn handle_is_cache_manager(
117 input: &mut PrecompileInput<'_>,
118 gas_used: &mut u64,
119 addr: Address,
120 ctx: &ArbPrecompileCtx,
121) -> PrecompileResult {
122 let gas_limit = input.gas;
123 load_arbos(input)?;
124
125 let internals = input.internals_mut();
126 let arb_state = ctx
127 .block
128 .arbos_state(internals)
129 .map_err(ArbPrecompileError::fatal)?;
130 let is_member = arb_state
131 .programs
132 .cache_managers
133 .is_member(internals, addr)
134 .map_err(ArbPrecompileError::fatal)?;
135
136 let result = if is_member {
137 U256::from(1u64)
138 } else {
139 U256::ZERO
140 };
141 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
142 crate::charge_computation(gas_used, ctx, COPY_GAS * words_for_bytes(32));
143 Ok(PrecompileOutput::new(
144 (*gas_used).min(gas_limit),
145 result.to_be_bytes::<32>().to_vec().into(),
146 ))
147}
148
149fn handle_all_cache_managers(
151 input: &mut PrecompileInput<'_>,
152 gas_used: &mut u64,
153 ctx: &ArbPrecompileCtx,
154) -> PrecompileResult {
155 let gas_limit = input.gas;
156 load_arbos(input)?;
157
158 let internals = input.internals_mut();
159 let arb_state = ctx
160 .block
161 .arbos_state(internals)
162 .map_err(ArbPrecompileError::fatal)?;
163 let members = arb_state
164 .programs
165 .cache_managers
166 .all_members(internals, 256)
167 .map_err(ArbPrecompileError::fatal)?;
168 let count = members.len() as u64;
169 let sloads: u64 = 1 + count;
170
171 let mut out = Vec::with_capacity(64 + members.len() * 32);
172 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
173 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
174 for member in &members {
175 let mut word = [0u8; 32];
176 word[12..32].copy_from_slice(member.as_slice());
177 out.extend_from_slice(&word);
178 }
179
180 crate::charge_storage_read(gas_used, ctx, sloads * SLOAD_GAS);
181 crate::charge_computation(gas_used, ctx, COPY_GAS * words_for_bytes(out.len() as u64));
182 Ok(PrecompileOutput::new(
183 (*gas_used).min(gas_limit),
184 out.into(),
185 ))
186}
187
188fn handle_codehash_is_cached(
189 input: &mut PrecompileInput<'_>,
190 gas_used: &mut u64,
191 ctx: &ArbPrecompileCtx,
192 codehash: B256,
193) -> PrecompileResult {
194 let gas_limit = input.gas;
195 load_arbos(input)?;
196
197 let time = ctx.block.block_timestamp;
198 let internals = input.internals_mut();
199 let arb_state = ctx
200 .block
201 .arbos_state(internals)
202 .map_err(ArbPrecompileError::fatal)?;
203 let program = arb_state
204 .programs
205 .get_program(internals, codehash, time)
206 .map_err(ArbPrecompileError::fatal)?;
207
208 let result = if program.cached {
209 U256::from(1u64)
210 } else {
211 U256::ZERO
212 };
213 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
214 crate::charge_computation(gas_used, ctx, COPY_GAS * words_for_bytes(32));
215 Ok(PrecompileOutput::new(
216 (*gas_used).min(gas_limit),
217 result.to_be_bytes::<32>().to_vec().into(),
218 ))
219}
220
221fn caller_has_cache_access(
225 input: &mut PrecompileInput<'_>,
226 caller: Address,
227 ctx: &ArbPrecompileCtx,
228) -> Result<(bool, u64), ArbPrecompileError> {
229 let internals = input.internals_mut();
230 let arb_state = ctx
231 .block
232 .arbos_state(internals)
233 .map_err(ArbPrecompileError::fatal)?;
234 if arb_state
235 .programs
236 .cache_managers
237 .is_member(internals, caller)
238 .map_err(ArbPrecompileError::fatal)?
239 {
240 return Ok((true, SLOAD_GAS));
241 }
242 let is_owner = arb_state
243 .chain_owners
244 .is_member(internals, caller)
245 .map_err(ArbPrecompileError::fatal)?;
246 Ok((is_owner, 2 * SLOAD_GAS))
247}
248
249fn read_params_and_program(
250 input: &mut PrecompileInput<'_>,
251 codehash: B256,
252 time: u64,
253 ctx: &ArbPrecompileCtx,
254) -> Result<(StylusParams, Program), ArbPrecompileError> {
255 let internals = input.internals_mut();
256 let arb_state = ctx
257 .block
258 .arbos_state(internals)
259 .map_err(ArbPrecompileError::fatal)?;
260 let params = arb_state
261 .programs
262 .params(internals)
263 .map_err(ArbPrecompileError::fatal)?;
264 let program = arb_state
265 .programs
266 .get_program(internals, codehash, time)
267 .map_err(ArbPrecompileError::fatal)?;
268 Ok((params, program))
269}
270
271fn set_program_cached(
274 input: &mut PrecompileInput<'_>,
275 ctx: &ArbPrecompileCtx,
276 gas_used: &mut u64,
277 codehash: B256,
278 cache: bool,
279 pre_set_gas: u64,
280) -> PrecompileResult {
281 let caller = input.caller;
282 let now = ctx.block.block_timestamp;
283 let gas_limit = input.gas;
284
285 crate::charge_storage_read(gas_used, ctx, pre_set_gas);
288
289 load_arbos(input)?;
290
291 let (has_access, access_gas) = caller_has_cache_access(input, caller, ctx)?;
292 crate::charge_storage_read(gas_used, ctx, access_gas);
293 if !has_access {
294 return crate::burn_all_revert(gas_limit);
295 }
296
297 let (params, mut program) = read_params_and_program(input, codehash, now, ctx)?;
298 crate::charge_params_read(gas_used, ctx);
300 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
301 let already_cached = program.cached;
302 let expiry_seconds = (params.expiry_days as u64).saturating_mul(86_400);
303 let expired = program.age_seconds > expiry_seconds;
304
305 if cache && program.version != params.version {
306 let data = IArbWasm::ProgramNeedsUpgrade {
307 version: program.version,
308 stylusVersion: params.version,
309 }
310 .abi_encode();
311 return crate::sol_error_revert(gas_used, ctx, data, gas_limit);
312 }
313 if cache && expired {
314 let data = IArbWasm::ProgramExpired {
315 ageInSeconds: program.age_seconds,
316 }
317 .abi_encode();
318 return crate::sol_error_revert(gas_used, ctx, data, gas_limit);
319 }
320 if already_cached == cache {
321 return Ok(PrecompileOutput::new(
323 (*gas_used).min(gas_limit),
324 Vec::new().into(),
325 ));
326 }
327
328 program.cached = cache;
329 let prog_init_cost = program.init_cost;
330 {
331 let internals = input.internals_mut();
332 let arb_state = ctx
333 .block
334 .arbos_state(internals)
335 .map_err(ArbPrecompileError::fatal)?;
336 arb_state
337 .programs
338 .set_program(internals, codehash, program)
339 .map_err(ArbPrecompileError::fatal)?;
340 }
341 let stored = program.to_storage();
342 let stored_u = U256::from_be_bytes(stored.0);
343 let sstore_gas = if stored_u == U256::ZERO {
344 SSTORE_RESET_GAS
345 } else {
346 SSTORE_SET_GAS
347 };
348
349 let topic1 = address_to_b256(caller);
350 let event_data = U256::from(cache as u64).to_be_bytes::<32>().to_vec();
351 input.internals_mut().log(Log::new_unchecked(
352 ARBWASMCACHE_ADDRESS,
353 vec![
354 IArbWasmCache::UpdateProgramCache::SIGNATURE_HASH,
355 topic1,
356 codehash,
357 ],
358 event_data.into(),
359 ));
360
361 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
364 crate::charge_storage_read(gas_used, ctx, prog_init_cost as u64);
365 crate::charge_history_growth(gas_used, ctx, EMIT_UPDATE_PROGRAM_CACHE_GAS);
366 crate::charge_storage_write(gas_used, ctx, sstore_gas);
367 Ok(PrecompileOutput::new(
368 (*gas_used).min(gas_limit),
369 Vec::new().into(),
370 ))
371}
372
373fn address_to_b256(addr: Address) -> B256 {
374 let mut bytes = [0u8; 32];
375 bytes[12..32].copy_from_slice(addr.as_slice());
376 B256::from(bytes)
377}
378
379fn handle_cache_codehash(
380 input: &mut PrecompileInput<'_>,
381 ctx: &ArbPrecompileCtx,
382 gas_used: &mut u64,
383 codehash: B256,
384) -> PrecompileResult {
385 if let Some(r) = crate::check_method_version(
386 ctx,
387 input.gas,
388 arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS,
389 arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS,
390 ) {
391 return r;
392 }
393 set_program_cached(input, ctx, gas_used, codehash, true, 0)
394}
395
396fn handle_cache_program(
399 input: &mut PrecompileInput<'_>,
400 ctx: &ArbPrecompileCtx,
401 gas_used: &mut u64,
402 addr: Address,
403) -> PrecompileResult {
404 if let Some(r) = crate::check_method_version(
405 ctx,
406 input.gas,
407 arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_FIXES,
408 0,
409 ) {
410 return r;
411 }
412 let codehash = crate::without_access_list_effect(input.internals_mut(), |internals| {
413 internals
414 .load_account(addr)
415 .map(|acct| acct.data.info.code_hash)
416 .map_err(ArbPrecompileError::fatal)
417 })?;
418 set_program_cached(
419 input,
420 ctx,
421 gas_used,
422 codehash,
423 true,
424 COLD_ACCOUNT_ACCESS_GAS,
425 )
426}
427
428fn handle_evict_codehash(
429 input: &mut PrecompileInput<'_>,
430 ctx: &ArbPrecompileCtx,
431 gas_used: &mut u64,
432 codehash: B256,
433) -> PrecompileResult {
434 set_program_cached(input, ctx, gas_used, codehash, false, 0)
435}