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, hours_since_arbitrum, hours_to_age, params::StylusParams};
9use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult};
10
11use crate::{ArbPrecompileError, interfaces::IArbWasm};
12
13pub const ARBWASM_ADDRESS: Address = Address::new([
15 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16 0x00, 0x00, 0x00, 0x71,
17]);
18
19const SLOAD_GAS: u64 = 800;
20const SSTORE_GAS: u64 = 20_000;
21const COPY_GAS: u64 = 3;
22const WARM_SLOAD_GAS: u64 = 100;
23const STORAGE_CODE_HASH_COST: u64 = 2_600;
24const FRAMEWORK_GAS_PROGRAM_ADDR: u64 = COPY_GAS + 800;
25const PROGRAM_LOOKUP_GAS: u64 =
26 FRAMEWORK_GAS_PROGRAM_ADDR + WARM_SLOAD_GAS + STORAGE_CODE_HASH_COST + SLOAD_GAS;
27
28const MIN_INIT_GAS_UNITS: u64 = 128;
29const MIN_CACHED_GAS_UNITS: u64 = 32;
30const COST_SCALAR_PERCENT: u64 = 2;
31
32pub fn create_arbwasm_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
33 DynPrecompile::new_stateful(PrecompileId::custom("arbwasm"), move |input| {
34 handler(input, &ctx)
35 })
36}
37
38fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
39 if let Some(result) =
40 crate::check_precompile_version(ctx, arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS)
41 {
42 return result;
43 }
44
45 let gas_limit = input.gas;
46
47 let call = match IArbWasm::ArbWasmCalls::abi_decode(input.data) {
48 Ok(c) => c,
49 Err(_) => return crate::burn_all_revert(gas_limit),
50 };
51
52 if let Some(r) = crate::reject_nonpayable_value(
53 input.value,
54 input.data,
55 gas_limit,
56 &[[0x58, 0xc7, 0x80, 0xc2], [0xc6, 0x89, 0xba, 0xd5]],
57 ) {
58 return r;
59 }
60 if let Some(r) = crate::reject_static_write(
61 input.is_static,
62 input.data,
63 gas_limit,
64 &[[0x58, 0xc7, 0x80, 0xc2], [0xc6, 0x89, 0xba, 0xd5]],
65 ) {
66 return r;
67 }
68 if let Some(r) = crate::reject_delegate_nonpure(
69 input.target_address != input.bytecode_address,
70 input.data,
71 gas_limit,
72 &[],
73 ) {
74 return r;
75 }
76
77 use IArbWasm::ArbWasmCalls as Calls;
78 match &call {
79 Calls::activateProgram(c) => return handle_activate_program(input, ctx, c.program),
80 Calls::codehashKeepalive(c) => return handle_codehash_keepalive(input, ctx, c.codehash),
81 _ => {}
82 }
83
84 let mut gas_used = 0u64;
85 crate::init_precompile_gas(&mut gas_used, ctx, input.data.len());
86
87 let result = match call {
88 Calls::stylusVersion(_) => {
89 let params = load_params(&mut input, &mut gas_used, ctx)?;
90 ok_u256(&mut gas_used, ctx, input.gas, U256::from(params.version))
91 }
92 Calls::inkPrice(_) => {
93 let params = load_params(&mut input, &mut gas_used, ctx)?;
94 ok_u256(&mut gas_used, ctx, input.gas, U256::from(params.ink_price))
95 }
96 Calls::maxStackDepth(_) => {
97 let params = load_params(&mut input, &mut gas_used, ctx)?;
98 ok_u256(
99 &mut gas_used,
100 ctx,
101 input.gas,
102 U256::from(params.max_stack_depth),
103 )
104 }
105 Calls::freePages(_) => {
106 let params = load_params(&mut input, &mut gas_used, ctx)?;
107 ok_u256(&mut gas_used, ctx, input.gas, U256::from(params.free_pages))
108 }
109 Calls::pageGas(_) => {
110 let params = load_params(&mut input, &mut gas_used, ctx)?;
111 ok_u256(&mut gas_used, ctx, input.gas, U256::from(params.page_gas))
112 }
113 Calls::pageRamp(_) => {
114 let params = load_params(&mut input, &mut gas_used, ctx)?;
115 ok_u256(&mut gas_used, ctx, input.gas, U256::from(params.page_ramp))
116 }
117 Calls::pageLimit(_) => {
118 let params = load_params(&mut input, &mut gas_used, ctx)?;
119 ok_u256(&mut gas_used, ctx, input.gas, U256::from(params.page_limit))
120 }
121 Calls::minInitGas(_) => {
122 let params = load_params(&mut input, &mut gas_used, ctx)?;
123 if ctx.block.arbos_version
124 < arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_CHARGING_FIXES
125 {
126 let pre_revert_gas = (SLOAD_GAS + WARM_SLOAD_GAS).min(input.gas);
127 return Ok(PrecompileOutput::new_reverted(
128 pre_revert_gas,
129 Default::default(),
130 ));
131 }
132 let init = (params.min_init_gas as u64).saturating_mul(MIN_INIT_GAS_UNITS);
133 let cached = (params.min_cached_init_gas as u64).saturating_mul(MIN_CACHED_GAS_UNITS);
134 ok_two_u256(
135 &mut gas_used,
136 ctx,
137 input.gas,
138 U256::from(init),
139 U256::from(cached),
140 )
141 }
142 Calls::initCostScalar(_) => {
143 let params = load_params(&mut input, &mut gas_used, ctx)?;
144 let scalar = params.init_cost_scalar as u64;
145 ok_u256(
146 &mut gas_used,
147 ctx,
148 input.gas,
149 U256::from(scalar.saturating_mul(COST_SCALAR_PERCENT)),
150 )
151 }
152 Calls::expiryDays(_) => {
153 let params = load_params(&mut input, &mut gas_used, ctx)?;
154 ok_u256(
155 &mut gas_used,
156 ctx,
157 input.gas,
158 U256::from(params.expiry_days),
159 )
160 }
161 Calls::keepaliveDays(_) => {
162 let params = load_params(&mut input, &mut gas_used, ctx)?;
163 ok_u256(
164 &mut gas_used,
165 ctx,
166 input.gas,
167 U256::from(params.keepalive_days),
168 )
169 }
170 Calls::blockCacheSize(_) => {
171 let params = load_params(&mut input, &mut gas_used, ctx)?;
172 ok_u256(
173 &mut gas_used,
174 ctx,
175 input.gas,
176 U256::from(params.block_cache_size),
177 )
178 }
179 Calls::activationGas(_) => {
180 if let Some(r) = crate::check_method_version(
181 ctx,
182 input.gas,
183 arb_chainspec::arbos_version::ARBOS_VERSION_59,
184 0,
185 ) {
186 return r;
187 }
188 load_arbos(&mut input)?;
189 let internals = input.internals_mut();
190 let arb_state = ctx
191 .block
192 .arbos_state(internals)
193 .map_err(ArbPrecompileError::fatal)?;
194 let gas = arb_state
195 .programs
196 .activation_gas(internals)
197 .map_err(ArbPrecompileError::fatal)?;
198 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
199 ok_u256(&mut gas_used, ctx, input.gas, U256::from(gas))
200 }
201 Calls::codehashVersion(c) => {
202 const LOOKUP_GAS: u64 = SLOAD_GAS + WARM_SLOAD_GAS + SLOAD_GAS + COPY_GAS;
203
204 let (params, program) =
205 load_params_and_program(&mut input, ctx, &mut gas_used, c.codehash)?;
206 if let Err(r) = validate_active_program(
207 &program,
208 params.version,
209 params.expiry_days,
210 input.gas,
211 LOOKUP_GAS,
212 ) {
213 return r;
214 }
215 ok_u256(&mut gas_used, ctx, input.gas, U256::from(program.version))
216 }
217 Calls::codehashAsmSize(c) => {
218 const LOOKUP_GAS: u64 = SLOAD_GAS + WARM_SLOAD_GAS + SLOAD_GAS + COPY_GAS;
219
220 let (params, program) =
221 load_params_and_program(&mut input, ctx, &mut gas_used, c.codehash)?;
222 if let Err(r) = validate_active_program(
223 &program,
224 params.version,
225 params.expiry_days,
226 input.gas,
227 LOOKUP_GAS,
228 ) {
229 return r;
230 }
231 ok_u256(
232 &mut gas_used,
233 ctx,
234 input.gas,
235 U256::from(program.asm_size()),
236 )
237 }
238 Calls::programVersion(c) => {
239 let codehash = get_account_codehash(&mut input, ctx, &mut gas_used, c.program)?;
240 let (params, program) =
241 load_params_and_program(&mut input, ctx, &mut gas_used, codehash)?;
242 if let Err(r) = validate_active_program(
243 &program,
244 params.version,
245 params.expiry_days,
246 input.gas,
247 PROGRAM_LOOKUP_GAS,
248 ) {
249 return r;
250 }
251 ok_u256(&mut gas_used, ctx, input.gas, U256::from(program.version))
252 }
253 Calls::programInitGas(c) => {
254 let codehash = get_account_codehash(&mut input, ctx, &mut gas_used, c.program)?;
255 let (params, program) =
256 load_params_and_program(&mut input, ctx, &mut gas_used, codehash)?;
257 if let Err(r) = validate_active_program(
258 &program,
259 params.version,
260 params.expiry_days,
261 input.gas,
262 PROGRAM_LOOKUP_GAS,
263 ) {
264 return r;
265 }
266
267 let mut init_gas = program.init_gas(¶ms);
268 let cached_gas = program.cached_gas(¶ms);
269 if params.version > 1 {
270 init_gas = init_gas.saturating_add(cached_gas);
271 }
272
273 ok_two_u256(
274 &mut gas_used,
275 ctx,
276 input.gas,
277 U256::from(init_gas),
278 U256::from(cached_gas),
279 )
280 }
281 Calls::programMemoryFootprint(c) => {
282 let codehash = get_account_codehash(&mut input, ctx, &mut gas_used, c.program)?;
283 let (params, program) =
284 load_params_and_program(&mut input, ctx, &mut gas_used, codehash)?;
285 if let Err(r) = validate_active_program(
286 &program,
287 params.version,
288 params.expiry_days,
289 input.gas,
290 PROGRAM_LOOKUP_GAS,
291 ) {
292 return r;
293 }
294 ok_u256(&mut gas_used, ctx, input.gas, U256::from(program.footprint))
295 }
296 Calls::programTimeLeft(c) => {
297 let codehash = get_account_codehash(&mut input, ctx, &mut gas_used, c.program)?;
298 let (params, program) =
299 load_params_and_program(&mut input, ctx, &mut gas_used, codehash)?;
300 if let Err(r) = validate_active_program(
301 &program,
302 params.version,
303 params.expiry_days,
304 input.gas,
305 PROGRAM_LOOKUP_GAS,
306 ) {
307 return r;
308 }
309
310 let expiry_seconds = (params.expiry_days as u64) * 24 * 3600;
311 let time_left = expiry_seconds.saturating_sub(program.age_seconds);
312 ok_u256(&mut gas_used, ctx, input.gas, U256::from(time_left))
313 }
314 Calls::activateProgram(_) | Calls::codehashKeepalive(_) => unreachable!(),
315 };
316 crate::gas_check(ctx, input.gas, gas_used, result)
317}
318
319fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), ArbPrecompileError> {
322 input
323 .internals_mut()
324 .load_account(ARBOS_STATE_ADDRESS)
325 .map_err(ArbPrecompileError::fatal)?;
326 Ok(())
327}
328
329fn load_params(
333 input: &mut PrecompileInput<'_>,
334 gas_used: &mut u64,
335 ctx: &ArbPrecompileCtx,
336) -> Result<StylusParams, ArbPrecompileError> {
337 load_arbos(input)?;
338 let internals = input.internals_mut();
339 let arb_state = ctx
340 .block
341 .arbos_state(internals)
342 .map_err(ArbPrecompileError::fatal)?;
343 let params = arb_state
344 .programs
345 .params(internals)
346 .map_err(ArbPrecompileError::fatal)?;
347 crate::charge_params_read(gas_used, ctx);
348 Ok(params)
349}
350
351fn load_params_and_program(
352 input: &mut PrecompileInput<'_>,
353 ctx: &ArbPrecompileCtx,
354 gas_used: &mut u64,
355 codehash: B256,
356) -> Result<(StylusParams, Program), ArbPrecompileError> {
357 load_arbos(input)?;
358 let time = ctx.block.block_timestamp;
359 let internals = input.internals_mut();
360 let arb_state = ctx
361 .block
362 .arbos_state(internals)
363 .map_err(ArbPrecompileError::fatal)?;
364 let params = arb_state
365 .programs
366 .params(internals)
367 .map_err(ArbPrecompileError::fatal)?;
368 let program = arb_state
369 .programs
370 .get_program(internals, codehash, time)
371 .map_err(ArbPrecompileError::fatal)?;
372 crate::charge_params_read(gas_used, ctx);
374 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
375 Ok((params, program))
376}
377
378fn get_account_codehash(
380 input: &mut PrecompileInput<'_>,
381 ctx: &ArbPrecompileCtx,
382 gas_used: &mut u64,
383 address: Address,
384) -> Result<B256, ArbPrecompileError> {
385 let codehash = crate::without_access_list_effect(input.internals_mut(), |internals| {
386 Ok::<_, ArbPrecompileError>(
387 internals
388 .load_account(address)
389 .map_err(ArbPrecompileError::fatal)?
390 .data
391 .info
392 .code_hash,
393 )
394 })?;
395 crate::charge_storage_read(gas_used, ctx, STORAGE_CODE_HASH_COST);
396 Ok(codehash)
397}
398
399fn validate_active_program(
405 program: &Program,
406 params_version: u16,
407 expiry_days: u16,
408 gas_limit: u64,
409 lookup_gas: u64,
410) -> Result<(), PrecompileResult> {
411 if program.version == 0 {
412 let data = IArbWasm::ProgramNotActivated {}.abi_encode();
413 return Err(revert_with_payload(data, lookup_gas, gas_limit));
414 }
415 if program.version != params_version {
416 let data = IArbWasm::ProgramNeedsUpgrade {
417 version: program.version,
418 stylusVersion: params_version,
419 }
420 .abi_encode();
421 return Err(revert_with_payload(data, lookup_gas, gas_limit));
422 }
423 let expiry_seconds = (expiry_days as u64).saturating_mul(86_400);
424 if program.age_seconds > expiry_seconds {
425 let data = IArbWasm::ProgramExpired {
426 ageInSeconds: program.age_seconds,
427 }
428 .abi_encode();
429 return Err(revert_with_payload(data, lookup_gas, gas_limit));
430 }
431 Ok(())
432}
433
434fn revert_with_payload(payload: Vec<u8>, lookup_gas: u64, gas_limit: u64) -> PrecompileResult {
436 let result_cost = COPY_GAS.saturating_mul((payload.len() as u64).div_ceil(32));
437 let gas_used = lookup_gas.saturating_add(result_cost);
438 Ok(PrecompileOutput::new_reverted(
439 gas_used.min(gas_limit),
440 payload.into(),
441 ))
442}
443
444fn ok_u256(
447 gas_used: &mut u64,
448 ctx: &ArbPrecompileCtx,
449 gas_limit: u64,
450 value: U256,
451) -> PrecompileResult {
452 crate::charge_computation(gas_used, ctx, COPY_GAS);
453 Ok(PrecompileOutput::new(
454 (*gas_used).min(gas_limit),
455 value.to_be_bytes::<32>().to_vec().into(),
456 ))
457}
458
459fn ok_two_u256(
460 gas_used: &mut u64,
461 ctx: &ArbPrecompileCtx,
462 gas_limit: u64,
463 a: U256,
464 b: U256,
465) -> PrecompileResult {
466 let mut out = Vec::with_capacity(64);
467 out.extend_from_slice(&a.to_be_bytes::<32>());
468 out.extend_from_slice(&b.to_be_bytes::<32>());
469 crate::charge_computation(gas_used, ctx, 2 * COPY_GAS);
470 Ok(PrecompileOutput::new(
471 (*gas_used).min(gas_limit),
472 out.into(),
473 ))
474}
475
476fn div_ceil(a: u64, b: u64) -> u64 {
477 a.div_ceil(b)
478}
479
480fn handle_activate_program(
481 mut input: PrecompileInput<'_>,
482 ctx: &ArbPrecompileCtx,
483 program_address: Address,
484) -> PrecompileResult {
485 const ACTIVATION_UPFRONT_GAS: u64 = 1_659_168;
486
487 let mut gas_used = 0u64;
488 let args_cost = COPY_GAS * (input.data.len() as u64).saturating_sub(4).div_ceil(32);
489 crate::charge_l2_calldata(&mut gas_used, ctx, args_cost);
490 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
491
492 if ctx.block.arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_59 {
493 load_arbos(&mut input)?;
494 let internals = input.internals_mut();
495 let arb_state = ctx
496 .block
497 .arbos_state(internals)
498 .map_err(ArbPrecompileError::fatal)?;
499 let activation_gas = arb_state
500 .programs
501 .activation_gas(internals)
502 .map_err(ArbPrecompileError::fatal)?;
503 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
504 crate::charge_computation(&mut gas_used, ctx, activation_gas);
506 }
507
508 crate::charge_computation(&mut gas_used, ctx, ACTIVATION_UPFRONT_GAS);
509
510 let (code_hash, code_bytes) =
511 crate::without_access_list_effect(input.internals_mut(), |internals| {
512 let code_hash = internals
513 .load_account(program_address)
514 .map_err(ArbPrecompileError::fatal)?
515 .data
516 .info
517 .code_hash;
518 let code_bytes = internals
519 .load_account_code(program_address)
520 .map_err(ArbPrecompileError::fatal)?
521 .data
522 .code()
523 .map(|c| c.original_bytes())
524 .unwrap_or_default()
525 .to_vec();
526 Ok::<_, ArbPrecompileError>((code_hash, code_bytes))
527 })?;
528
529 load_arbos(&mut input)?;
530 let time = ctx.block.block_timestamp;
531 crate::charge_computation(&mut gas_used, ctx, WARM_SLOAD_GAS);
532 let (params, existing_program) = {
533 let internals = input.internals_mut();
534 let arb_state = ctx
535 .block
536 .arbos_state(internals)
537 .map_err(ArbPrecompileError::fatal)?;
538 let params = arb_state
539 .programs
540 .params(internals)
541 .map_err(ArbPrecompileError::fatal)?;
542 let existing = arb_state
543 .programs
544 .get_program(internals, code_hash, time)
545 .map_err(ArbPrecompileError::fatal)?;
546 (params, existing)
547 };
548 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
549
550 if existing_program.version == params.version
553 && existing_program.age_seconds <= (params.expiry_days as u64) * 86400
554 {
555 return crate::revert_sol_error(
556 &mut gas_used,
557 ctx,
558 IArbWasm::ProgramUpToDate {}.abi_encode(),
559 input.gas,
560 );
561 }
562
563 if code_bytes.is_empty() {
564 return crate::revert_sol_error(
565 &mut gas_used,
566 ctx,
567 IArbWasm::ProgramNotWasm {}.abi_encode(),
568 input.gas,
569 );
570 }
571 if !arb_stylus::is_stylus_deployable(&code_bytes, ctx.block.arbos_version) {
572 let arbos_v = ctx.block.arbos_version;
573 if arbos_v < arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_CONTRACT_LIMIT
574 || arb_stylus::is_stylus_fragment(&code_bytes)
575 {
576 return Ok(PrecompileOutput::new_reverted(
577 gas_used.min(input.gas),
578 Default::default(),
579 ));
580 }
581 return crate::revert_sol_error(
582 &mut gas_used,
583 ctx,
584 IArbWasm::ProgramNotWasm {}.abi_encode(),
585 input.gas,
586 );
587 }
588
589 let gas_limit = input.gas;
593 let wasm_result = if arb_stylus::is_stylus_root(&code_bytes) {
594 const MAX_FRAGMENT_CODE_SIZE: u64 = 24_576;
595 let max_wasm_size = params.max_wasm_size;
596 let max_fragment_count = params.max_fragment_count;
597 arb_stylus::get_wasm_from_root(
598 &code_bytes,
599 max_wasm_size,
600 max_fragment_count,
601 true,
602 |addr| {
603 let (warm, code) = {
604 let load = input
605 .internals_mut()
606 .load_account_code(addr)
607 .map_err(|e| arb_stylus::StylusError::Activation(format!("{e}")))?;
608 let code = load
609 .data
610 .code()
611 .map(|c| c.original_bytes())
612 .unwrap_or_default()
613 .to_vec();
614 (!load.is_cold, code)
615 };
616 if gas_limit.saturating_sub(gas_used)
619 < arb_stylus::fragment_read_gas(warm, MAX_FRAGMENT_CODE_SIZE)
620 {
621 gas_used = gas_limit;
622 return Err(arb_stylus::StylusError::InvalidProgram(
623 "out of gas reading fragment",
624 ));
625 }
626 crate::charge_storage_read(
627 &mut gas_used,
628 ctx,
629 arb_stylus::fragment_read_gas(warm, code.len() as u64),
630 );
631 Ok(code)
632 },
633 )
634 } else {
635 arb_stylus::decompress_wasm(&code_bytes)
636 };
637 let wasm = match wasm_result {
638 Ok(w) => w,
639 Err(_) => {
640 return Ok(PrecompileOutput::new_reverted(
643 gas_used.min(input.gas),
644 Default::default(),
645 ));
646 }
647 };
648
649 let was_cached = existing_program.cached;
650
651 let gas_available = input.gas.saturating_sub(gas_used);
652 let mut gas_for_prover = gas_available;
653
654 let info = match arb_stylus::activate_program(
655 &wasm,
656 code_hash.as_ref(),
657 params.version,
658 ctx.block.arbos_version,
659 params.page_limit,
660 false,
661 &mut gas_for_prover,
662 ) {
663 Ok(info) => info,
664 Err(_) => {
665 crate::charge_computation(&mut gas_used, ctx, gas_available);
666 return Err(ArbPrecompileError::empty_revert(gas_used).into());
667 }
668 };
669
670 let prover_gas_used = gas_available.saturating_sub(gas_for_prover);
671 crate::charge_computation(&mut gas_used, ctx, prover_gas_used);
672
673 if was_cached {
676 let _prev_module_hash = {
677 let internals = input.internals_mut();
678 let arb_state = ctx
679 .block
680 .arbos_state(internals)
681 .map_err(ArbPrecompileError::fatal)?;
682 arb_state
683 .programs
684 .get_module_hash(internals, code_hash)
685 .map_err(ArbPrecompileError::fatal)?
686 };
687 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
688 }
689
690 {
691 let internals = input.internals_mut();
692 let arb_state = ctx
693 .block
694 .arbos_state(internals)
695 .map_err(ArbPrecompileError::fatal)?;
696 arb_state
697 .programs
698 .set_module_hash(internals, code_hash, info.module_hash)
699 .map_err(ArbPrecompileError::fatal)?;
700 }
701 crate::charge_storage_write(&mut gas_used, ctx, SSTORE_GAS);
702
703 let data_fee = {
704 let internals = input.internals_mut();
705 let arb_state = ctx
706 .block
707 .arbos_state(internals)
708 .map_err(ArbPrecompileError::fatal)?;
709 arb_state
710 .programs
711 .data_pricer
712 .update_model(internals, info.asm_estimate, time)
713 .map_err(ArbPrecompileError::fatal)?
714 };
715 crate::charge_storage_read(&mut gas_used, ctx, 5 * SLOAD_GAS);
716 crate::charge_storage_write(&mut gas_used, ctx, 2 * SSTORE_GAS);
717
718 let estimate_kb = div_ceil(info.asm_estimate as u64, 1024).min(0xFF_FFFF) as u32;
719 let new_program = Program {
720 version: params.version,
721 init_cost: info.init_gas,
722 cached_cost: info.cached_init_gas,
723 footprint: info.footprint,
724 asm_estimate_kb: estimate_kb,
725 activated_at: hours_since_arbitrum(time),
726 age_seconds: 0,
727 cached: was_cached,
728 };
729 {
730 let internals = input.internals_mut();
731 let arb_state = ctx
732 .block
733 .arbos_state(internals)
734 .map_err(ArbPrecompileError::fatal)?;
735 arb_state
736 .programs
737 .set_program(internals, code_hash, new_program)
738 .map_err(ArbPrecompileError::fatal)?;
739 }
740 crate::charge_storage_write(&mut gas_used, ctx, SSTORE_GAS);
741
742 let stashed_outer_value = ctx.stylus_call_value();
743 let inner_call_value = input.value;
744 let effective_value = if inner_call_value > U256::ZERO {
745 inner_call_value
746 } else {
747 stashed_outer_value
748 };
749 if effective_value < data_fee {
750 return crate::revert_sol_error(
751 &mut gas_used,
752 ctx,
753 IArbWasm::ProgramInsufficientValue {
754 have: effective_value,
755 want: data_fee,
756 }
757 .abi_encode(),
758 input.gas,
759 );
760 }
761
762 if inner_call_value > U256::ZERO {
763 let caller = input.caller;
764 let network_addr = {
765 let internals = input.internals_mut();
766 let arb_state = ctx
767 .block
768 .arbos_state(internals)
769 .map_err(ArbPrecompileError::fatal)?;
770 arb_state
771 .network_fee_account(internals)
772 .map_err(ArbPrecompileError::fatal)?
773 };
774 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
775 let _ = input
776 .internals_mut()
777 .transfer(ARBWASM_ADDRESS, network_addr, data_fee);
778 let repay = inner_call_value.saturating_sub(data_fee);
779 if repay > U256::ZERO {
780 let _ = input
781 .internals_mut()
782 .transfer(ARBWASM_ADDRESS, caller, repay);
783 }
784 ctx.set_stylus_activation_addr(Some(program_address));
785 } else {
786 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
787 ctx.set_stylus_activation_addr(Some(program_address));
788 ctx.set_stylus_activation_data_fee(data_fee);
789 }
790
791 let event_topic = IArbWasm::ProgramActivated::SIGNATURE_HASH;
792 let mut event_data = Vec::with_capacity(128);
793 event_data.extend_from_slice(&info.module_hash.0);
794 event_data.extend_from_slice(&[0u8; 12]);
795 event_data.extend_from_slice(program_address.as_slice());
796 event_data.extend_from_slice(&data_fee.to_be_bytes::<32>());
797 let mut ver = [0u8; 32];
798 ver[30..32].copy_from_slice(¶ms.version.to_be_bytes());
799 event_data.extend_from_slice(&ver);
800 let event_gas = 375 + 2 * 375 + 8 * event_data.len() as u64;
801 crate::charge_history_growth(&mut gas_used, ctx, event_gas);
802 input.internals_mut().log(Log::new_unchecked(
803 ARBWASM_ADDRESS,
804 vec![event_topic, code_hash],
805 event_data.into(),
806 ));
807
808 let return_data = {
809 let mut output = Vec::with_capacity(64);
810 let mut ver_out = [0u8; 32];
811 ver_out[30..32].copy_from_slice(¶ms.version.to_be_bytes());
812 output.extend_from_slice(&ver_out);
813 output.extend_from_slice(&data_fee.to_be_bytes::<32>());
814 output
815 };
816 let return_gas = COPY_GAS * (return_data.len() as u64).div_ceil(32);
817 crate::charge_computation(&mut gas_used, ctx, return_gas);
818
819 if gas_used > input.gas {
820 return Err(ArbPrecompileError::OutOfGas.into());
821 }
822 Ok(PrecompileOutput::new(gas_used, return_data.into()))
823}
824
825fn handle_codehash_keepalive(
826 mut input: PrecompileInput<'_>,
827 ctx: &ArbPrecompileCtx,
828 codehash: B256,
829) -> PrecompileResult {
830 let mut gas_used = 0u64;
831 let args_cost = COPY_GAS * (input.data.len() as u64).saturating_sub(4).div_ceil(32);
832 crate::charge_l2_calldata(&mut gas_used, ctx, args_cost);
833
834 load_arbos(&mut input)?;
835 let time = ctx.block.block_timestamp;
836 let (params, mut program) = {
837 let internals = input.internals_mut();
838 let arb_state = ctx
839 .block
840 .arbos_state(internals)
841 .map_err(ArbPrecompileError::fatal)?;
842 let params = arb_state
843 .programs
844 .params(internals)
845 .map_err(ArbPrecompileError::fatal)?;
846 let program = arb_state
847 .programs
848 .get_program(internals, codehash, time)
849 .map_err(ArbPrecompileError::fatal)?;
850 (params, program)
851 };
852 crate::charge_params_read(&mut gas_used, ctx);
853 crate::charge_storage_read(&mut gas_used, ctx, 2 * SLOAD_GAS);
854
855 if program.version == 0 {
856 return crate::revert_sol_error(
857 &mut gas_used,
858 ctx,
859 IArbWasm::ProgramNotActivated {}.abi_encode(),
860 input.gas,
861 );
862 }
863 if program.version != params.version {
864 return crate::revert_sol_error(
865 &mut gas_used,
866 ctx,
867 IArbWasm::ProgramNeedsUpgrade {
868 version: program.version,
869 stylusVersion: params.version,
870 }
871 .abi_encode(),
872 input.gas,
873 );
874 }
875 let age = hours_to_age(time, program.activated_at);
876 if age > (params.expiry_days as u64) * 86400 {
877 return crate::revert_sol_error(
878 &mut gas_used,
879 ctx,
880 IArbWasm::ProgramExpired { ageInSeconds: age }.abi_encode(),
881 input.gas,
882 );
883 }
884 if age < (params.keepalive_days as u64) * 86400 {
885 return crate::revert_sol_error(
886 &mut gas_used,
887 ctx,
888 IArbWasm::ProgramKeepaliveTooSoon { ageInSeconds: age }.abi_encode(),
889 input.gas,
890 );
891 }
892
893 let asm_size = program.asm_size();
894
895 let data_fee = {
896 let internals = input.internals_mut();
897 let arb_state = ctx
898 .block
899 .arbos_state(internals)
900 .map_err(ArbPrecompileError::fatal)?;
901 arb_state
902 .programs
903 .data_pricer
904 .update_model(internals, asm_size, time)
905 .map_err(ArbPrecompileError::fatal)?
906 };
907 crate::charge_storage_read(&mut gas_used, ctx, 5 * SLOAD_GAS);
908 crate::charge_storage_write(&mut gas_used, ctx, 2 * SSTORE_GAS);
909
910 program.activated_at = hours_since_arbitrum(time);
911 program.age_seconds = 0;
912 {
913 let internals = input.internals_mut();
914 let arb_state = ctx
915 .block
916 .arbos_state(internals)
917 .map_err(ArbPrecompileError::fatal)?;
918 arb_state
919 .programs
920 .set_program(internals, codehash, program)
921 .map_err(ArbPrecompileError::fatal)?;
922 }
923 crate::charge_storage_write(&mut gas_used, ctx, SSTORE_GAS);
924
925 let stashed_outer_value = ctx.stylus_call_value();
926 let inner_call_value = input.value;
927 let effective_value = if inner_call_value > U256::ZERO {
928 inner_call_value
929 } else {
930 stashed_outer_value
931 };
932 if effective_value < data_fee {
933 return crate::revert_sol_error(
934 &mut gas_used,
935 ctx,
936 IArbWasm::ProgramInsufficientValue {
937 have: effective_value,
938 want: data_fee,
939 }
940 .abi_encode(),
941 input.gas,
942 );
943 }
944
945 if inner_call_value > U256::ZERO {
946 let caller = input.caller;
947 let network_addr = {
948 let internals = input.internals_mut();
949 let arb_state = ctx
950 .block
951 .arbos_state(internals)
952 .map_err(ArbPrecompileError::fatal)?;
953 arb_state
954 .network_fee_account(internals)
955 .map_err(ArbPrecompileError::fatal)?
956 };
957 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
958 let _ = input
959 .internals_mut()
960 .transfer(ARBWASM_ADDRESS, network_addr, data_fee);
961 let repay = inner_call_value.saturating_sub(data_fee);
962 if repay > U256::ZERO {
963 let _ = input
964 .internals_mut()
965 .transfer(ARBWASM_ADDRESS, caller, repay);
966 }
967 ctx.set_stylus_keepalive_hash(Some(codehash));
968 } else {
969 crate::charge_storage_read(&mut gas_used, ctx, SLOAD_GAS);
970 ctx.set_stylus_keepalive_hash(Some(codehash));
971 ctx.set_stylus_activation_data_fee(data_fee);
972 }
973
974 let event_topic = IArbWasm::ProgramLifetimeExtended::SIGNATURE_HASH;
975 let mut event_data = Vec::with_capacity(32);
976 event_data.extend_from_slice(&data_fee.to_be_bytes::<32>());
977 let event_gas = 375 + 2 * 375 + 8 * event_data.len() as u64;
978 crate::charge_history_growth(&mut gas_used, ctx, event_gas);
979 input.internals_mut().log(Log::new_unchecked(
980 ARBWASM_ADDRESS,
981 vec![event_topic, codehash],
982 event_data.into(),
983 ));
984
985 if gas_used > input.gas {
986 return Err(ArbPrecompileError::OutOfGas.into());
987 }
988 Ok(PrecompileOutput::new(gas_used, Vec::new().into()))
989}
990
991#[cfg(test)]
992mod failure_gas_tests {
993 use super::*;
994
995 fn unactivated() -> Program {
996 Program {
997 version: 0,
998 init_cost: 0,
999 cached_cost: 0,
1000 footprint: 0,
1001 asm_estimate_kb: 0,
1002 activated_at: 0,
1003 age_seconds: 0,
1004 cached: false,
1005 }
1006 }
1007
1008 fn revert_gas(lookup_gas: u64) -> u64 {
1009 let r = validate_active_program(&unactivated(), 1, 365, 1_000_000, lookup_gas)
1010 .expect_err("unactivated program should revert");
1011 let out = r.expect("revert wraps an Ok(PrecompileOutput)");
1012 out.gas_used
1013 }
1014
1015 #[test]
1016 fn codehash_asm_size_failure_charges_lookup_plus_error_word() {
1017 const LOOKUP_GAS: u64 = SLOAD_GAS + WARM_SLOAD_GAS + SLOAD_GAS + COPY_GAS;
1018 assert_eq!(LOOKUP_GAS, 1703);
1019 assert_eq!(revert_gas(LOOKUP_GAS), 1706);
1020 }
1021
1022 #[test]
1023 fn program_version_family_failure_charges_lookup_plus_error_word() {
1024 assert_eq!(PROGRAM_LOOKUP_GAS, 4303);
1025 assert_eq!(revert_gas(PROGRAM_LOOKUP_GAS), 4306);
1026 }
1027
1028 #[test]
1029 fn revert_is_capped_at_gas_limit() {
1030 let r = validate_active_program(&unactivated(), 1, 365, 500, 1703)
1031 .expect_err("unactivated program should revert");
1032 let out = r.expect("revert wraps an Ok(PrecompileOutput)");
1033 assert_eq!(out.gas_used, 500);
1034 }
1035}