1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, B256, U256};
3use alloy_sol_types::{SolError, SolEvent, SolInterface};
4use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
5
6use crate::{
7 interfaces::IArbWasm,
8 storage_slot::{
9 derive_subspace_key, map_slot, map_slot_b256, ARBOS_STATE_ADDRESS, PROGRAMS_DATA_KEY,
10 PROGRAMS_PARAMS_KEY, PROGRAMS_SUBSPACE, ROOT_STORAGE_KEY,
11 },
12};
13
14pub const ARBWASM_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, 0x71,
18]);
19
20const SLOAD_GAS: u64 = 800;
21const SSTORE_GAS: u64 = 20_000;
22const COPY_GAS: u64 = 3;
23const WARM_SLOAD_GAS: u64 = 100;
24
25const INITIAL_PAGE_RAMP: u64 = 620674314;
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() -> DynPrecompile {
33 DynPrecompile::new_stateful(PrecompileId::custom("arbwasm"), handler)
34}
35
36fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
37 if let Some(result) =
38 crate::check_precompile_version(arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS)
39 {
40 return result;
41 }
42
43 let gas_limit = input.gas;
44
45 let call = match IArbWasm::ArbWasmCalls::abi_decode(input.data) {
46 Ok(c) => c,
47 Err(_) => return crate::burn_all_revert(gas_limit),
48 };
49
50 use IArbWasm::ArbWasmCalls as Calls;
51 match &call {
53 Calls::activateProgram(c) => return handle_activate_program(input, c.program),
54 Calls::codehashKeepalive(c) => return handle_codehash_keepalive(input, c.codehash),
55 _ => {}
56 }
57
58 crate::init_precompile_gas(input.data.len());
59
60 let result = match call {
61 Calls::stylusVersion(_) => {
62 let params = load_params_word(&mut input)?;
63 let version = u16::from_be_bytes([params[0], params[1]]);
64 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(version))
65 }
66 Calls::inkPrice(_) => {
67 let params = load_params_word(&mut input)?;
68 let ink_price = (params[2] as u32) << 16 | (params[3] as u32) << 8 | params[4] as u32;
69 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(ink_price))
70 }
71 Calls::maxStackDepth(_) => {
72 let params = load_params_word(&mut input)?;
73 let depth = u32::from_be_bytes([params[5], params[6], params[7], params[8]]);
74 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(depth))
75 }
76 Calls::freePages(_) => {
77 let params = load_params_word(&mut input)?;
78 let pages = u16::from_be_bytes([params[9], params[10]]);
79 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(pages))
80 }
81 Calls::pageGas(_) => {
82 let params = load_params_word(&mut input)?;
83 let gas = u16::from_be_bytes([params[11], params[12]]);
84 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(gas))
85 }
86 Calls::pageRamp(_) => {
87 load_arbos(&mut input)?;
88 ok_u256(COPY_GAS, U256::from(INITIAL_PAGE_RAMP))
89 }
90 Calls::pageLimit(_) => {
91 let params = load_params_word(&mut input)?;
92 let limit = u16::from_be_bytes([params[13], params[14]]);
93 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(limit))
94 }
95 Calls::minInitGas(_) => {
96 if let Some(result) = crate::check_method_version(
97 input.gas,
98 arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_CHARGING_FIXES,
99 0,
100 ) {
101 return result;
102 }
103 let params = load_params_word(&mut input)?;
104 let min_init = params[15] as u64;
105 let min_cached = params[16] as u64;
106 let init = min_init.saturating_mul(MIN_INIT_GAS_UNITS);
107 let cached = min_cached.saturating_mul(MIN_CACHED_GAS_UNITS);
108 ok_two_u256(SLOAD_GAS + COPY_GAS, U256::from(init), U256::from(cached))
109 }
110 Calls::initCostScalar(_) => {
111 let params = load_params_word(&mut input)?;
112 let scalar = params[17] as u64;
113 ok_u256(
114 SLOAD_GAS + COPY_GAS,
115 U256::from(scalar.saturating_mul(COST_SCALAR_PERCENT)),
116 )
117 }
118 Calls::expiryDays(_) => {
119 let params = load_params_word(&mut input)?;
120 let days = u16::from_be_bytes([params[19], params[20]]);
121 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(days))
122 }
123 Calls::keepaliveDays(_) => {
124 let params = load_params_word(&mut input)?;
125 let days = u16::from_be_bytes([params[21], params[22]]);
126 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(days))
127 }
128 Calls::blockCacheSize(_) => {
129 let params = load_params_word(&mut input)?;
130 let size = u16::from_be_bytes([params[23], params[24]]);
131 ok_u256(SLOAD_GAS + COPY_GAS, U256::from(size))
132 }
133 Calls::activationGas(_) => {
134 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
135 let activation_key = derive_subspace_key(programs_key.as_slice(), &[5]);
136 let slot = map_slot(activation_key.as_slice(), 0);
137 let gas = sload_field(&mut input, slot)?;
138 ok_u256(SLOAD_GAS + COPY_GAS, gas)
139 }
140 Calls::codehashVersion(c) => {
141 let (params_word, program_word) = load_params_and_program(&mut input, c.codehash)?;
142 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
143 let expiry_days = params_expiry_days(¶ms_word);
144 let program = parse_program(&program_word, ¶ms_word);
145 if let Err(r) =
146 validate_active_program(&program, params_version, expiry_days, input.gas)
147 {
148 return r;
149 }
150 ok_u256(2 * SLOAD_GAS + COPY_GAS, U256::from(program.version))
151 }
152 Calls::codehashAsmSize(c) => {
153 let (params_word, program_word) = load_params_and_program(&mut input, c.codehash)?;
154 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
155 let expiry_days = params_expiry_days(¶ms_word);
156 let program = parse_program(&program_word, ¶ms_word);
157 if let Err(r) =
158 validate_active_program(&program, params_version, expiry_days, input.gas)
159 {
160 return r;
161 }
162 let asm_size = program.asm_estimate_kb.saturating_mul(1024);
163 ok_u256(
164 SLOAD_GAS + WARM_SLOAD_GAS + SLOAD_GAS + 2 * COPY_GAS,
165 U256::from(asm_size),
166 )
167 }
168 Calls::programVersion(c) => {
169 let codehash = get_account_codehash(&mut input, c.program)?;
170 let (params_word, program_word) = load_params_and_program(&mut input, codehash)?;
171 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
172 let expiry_days = params_expiry_days(¶ms_word);
173 let program = parse_program(&program_word, ¶ms_word);
174 if let Err(r) =
175 validate_active_program(&program, params_version, expiry_days, input.gas)
176 {
177 return r;
178 }
179 ok_u256(3 * SLOAD_GAS + COPY_GAS, U256::from(program.version))
180 }
181 Calls::programInitGas(c) => {
182 let codehash = get_account_codehash(&mut input, c.program)?;
183 let (params_word, program_word) = load_params_and_program(&mut input, codehash)?;
184 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
185 let expiry_days = params_expiry_days(¶ms_word);
186 let program = parse_program(&program_word, ¶ms_word);
187 if let Err(r) =
188 validate_active_program(&program, params_version, expiry_days, input.gas)
189 {
190 return r;
191 }
192
193 let min_init = params_word[15] as u64;
194 let min_cached = params_word[16] as u64;
195 let init_cost_scalar = params_word[17] as u64;
196 let cached_cost_scalar = params_word[18] as u64;
197
198 let init_base = min_init.saturating_mul(MIN_INIT_GAS_UNITS);
199 let init_dyno =
200 (program.init_cost as u64).saturating_mul(init_cost_scalar * COST_SCALAR_PERCENT);
201 let mut init_gas = init_base.saturating_add(div_ceil(init_dyno, 100));
202
203 let cached_base = min_cached.saturating_mul(MIN_CACHED_GAS_UNITS);
204 let cached_dyno = (program.cached_cost as u64)
205 .saturating_mul(cached_cost_scalar * COST_SCALAR_PERCENT);
206 let cached_gas = cached_base.saturating_add(div_ceil(cached_dyno, 100));
207
208 if params_version > 1 {
209 init_gas = init_gas.saturating_add(cached_gas);
210 }
211
212 ok_two_u256(
213 3 * SLOAD_GAS + COPY_GAS,
214 U256::from(init_gas),
215 U256::from(cached_gas),
216 )
217 }
218 Calls::programMemoryFootprint(c) => {
219 let codehash = get_account_codehash(&mut input, c.program)?;
220 let (params_word, program_word) = load_params_and_program(&mut input, codehash)?;
221 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
222 let expiry_days = params_expiry_days(¶ms_word);
223 let program = parse_program(&program_word, ¶ms_word);
224 if let Err(r) =
225 validate_active_program(&program, params_version, expiry_days, input.gas)
226 {
227 return r;
228 }
229 ok_u256(3 * SLOAD_GAS + COPY_GAS, U256::from(program.footprint))
230 }
231 Calls::programTimeLeft(c) => {
232 let codehash = get_account_codehash(&mut input, c.program)?;
233 let (params_word, program_word) = load_params_and_program(&mut input, codehash)?;
234 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
235 let expiry_days = params_expiry_days(¶ms_word);
236 let program = parse_program(&program_word, ¶ms_word);
237 if let Err(r) =
238 validate_active_program(&program, params_version, expiry_days, input.gas)
239 {
240 return r;
241 }
242
243 let expiry_seconds = (expiry_days as u64) * 24 * 3600;
244 let time_left = expiry_seconds.saturating_sub(program.age_seconds);
245 ok_u256(3 * SLOAD_GAS + COPY_GAS, U256::from(time_left))
246 }
247 Calls::activateProgram(_) | Calls::codehashKeepalive(_) => unreachable!(),
248 };
249 crate::gas_check(input.gas, result)
250}
251
252fn params_expiry_days(params_word: &[u8; 32]) -> u16 {
255 u16::from_be_bytes([params_word[19], params_word[20]])
256}
257
258fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
259 input
260 .internals_mut()
261 .load_account(ARBOS_STATE_ADDRESS)
262 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
263 Ok(())
264}
265
266fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
267 let val = input
268 .internals_mut()
269 .sload(ARBOS_STATE_ADDRESS, slot)
270 .map_err(|_| PrecompileError::other("sload failed"))?;
271 crate::charge_precompile_gas(SLOAD_GAS);
272 Ok(val.data)
273}
274
275fn load_params_word(input: &mut PrecompileInput<'_>) -> Result<[u8; 32], PrecompileError> {
277 load_arbos(input)?;
278 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
279 let params_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_PARAMS_KEY);
280 let slot = map_slot(params_key.as_slice(), 0);
281 let value = sload_field(input, slot)?;
282 Ok(value.to_be_bytes::<32>())
283}
284
285fn load_params_and_program(
286 input: &mut PrecompileInput<'_>,
287 codehash: B256,
288) -> Result<([u8; 32], [u8; 32]), PrecompileError> {
289 load_arbos(input)?;
290 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
291
292 let params_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_PARAMS_KEY);
294 let params_slot = map_slot(params_key.as_slice(), 0);
295 let params_value = sload_field(input, params_slot)?;
296
297 let data_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_DATA_KEY);
298 let program_slot = map_slot_b256(data_key.as_slice(), &codehash);
299 let program_value = sload_field(input, program_slot)?;
300
301 Ok((
302 params_value.to_be_bytes::<32>(),
303 program_value.to_be_bytes::<32>(),
304 ))
305}
306
307fn get_account_codehash(
309 input: &mut PrecompileInput<'_>,
310 address: Address,
311) -> Result<B256, PrecompileError> {
312 let account = input
313 .internals_mut()
314 .load_account(address)
315 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
316 Ok(account.data.info.code_hash)
317}
318
319struct ProgramInfo {
321 version: u16,
322 init_cost: u16,
323 cached_cost: u16,
324 footprint: u16,
325 asm_estimate_kb: u32,
326 age_seconds: u64,
327}
328
329const ARBITRUM_START_TIME: u64 = 1421388000;
331
332fn parse_program(data: &[u8; 32], params_word: &[u8; 32]) -> ProgramInfo {
333 let version = u16::from_be_bytes([data[0], data[1]]);
334 let init_cost = u16::from_be_bytes([data[2], data[3]]);
335 let cached_cost = u16::from_be_bytes([data[4], data[5]]);
336 let footprint = u16::from_be_bytes([data[6], data[7]]);
337 let activated_at = (data[8] as u32) << 16 | (data[9] as u32) << 8 | data[10] as u32;
338 let asm_estimate_kb = (data[11] as u32) << 16 | (data[12] as u32) << 8 | data[13] as u32;
339
340 let _ = params_word;
341 let age_seconds = hours_to_age(block_timestamp(), activated_at);
342
343 ProgramInfo {
344 version,
345 init_cost,
346 cached_cost,
347 footprint,
348 asm_estimate_kb,
349 age_seconds,
350 }
351}
352
353fn block_timestamp() -> u64 {
355 crate::get_block_timestamp()
356}
357
358fn hours_to_age(time: u64, hours: u32) -> u64 {
359 let seconds = (hours as u64).saturating_mul(3600);
360 let activated_at = ARBITRUM_START_TIME.saturating_add(seconds);
361 time.saturating_sub(activated_at)
362}
363
364fn validate_active_program(
367 program: &ProgramInfo,
368 params_version: u16,
369 expiry_days: u16,
370 gas_limit: u64,
371) -> Result<(), PrecompileResult> {
372 if program.version == 0 {
373 let data = IArbWasm::ProgramNotActivated {}.abi_encode();
374 return Err(crate::sol_error_revert(data, gas_limit));
375 }
376 if program.version != params_version {
377 let data = IArbWasm::ProgramNeedsUpgrade {
378 version: program.version,
379 stylusVersion: params_version,
380 }
381 .abi_encode();
382 return Err(crate::sol_error_revert(data, gas_limit));
383 }
384 let expiry_seconds = (expiry_days as u64).saturating_mul(86_400);
385 if program.age_seconds > expiry_seconds {
386 let data = IArbWasm::ProgramExpired {
387 ageInSeconds: program.age_seconds,
388 }
389 .abi_encode();
390 return Err(crate::sol_error_revert(data, gas_limit));
391 }
392 Ok(())
393}
394
395fn ok_u256(gas_cost: u64, value: U256) -> PrecompileResult {
396 Ok(PrecompileOutput::new(
397 gas_cost,
398 value.to_be_bytes::<32>().to_vec().into(),
399 ))
400}
401
402fn ok_two_u256(gas_cost: u64, a: U256, b: U256) -> PrecompileResult {
403 let mut out = Vec::with_capacity(64);
404 out.extend_from_slice(&a.to_be_bytes::<32>());
405 out.extend_from_slice(&b.to_be_bytes::<32>());
406 Ok(PrecompileOutput::new(gas_cost, out.into()))
407}
408
409fn div_ceil(a: u64, b: u64) -> u64 {
410 a.div_ceil(b)
411}
412
413fn hours_since_arbitrum(time: u64) -> u32 {
414 let elapsed = time.saturating_sub(ARBITRUM_START_TIME);
415 (elapsed / 3600) as u32
416}
417
418fn approx_exp_basis_points(x: u64) -> u64 {
421 let b = 10_000u64;
422 let accuracy = 12u64;
423 let mut res = b + x / accuracy;
424 for i in (1..accuracy).rev() {
425 res = b + res.saturating_mul(x) / (i * b);
426 }
427 res
428}
429
430fn handle_activate_program(
431 mut input: PrecompileInput<'_>,
432 program_address: Address,
433) -> PrecompileResult {
434 const ACTIVATION_UPFRONT_GAS: u64 = 1_659_168;
435
436 crate::reset_precompile_gas();
437 let args_cost = COPY_GAS * (input.data.len() as u64).saturating_sub(4).div_ceil(32);
438 crate::charge_precompile_gas(args_cost);
439 crate::charge_precompile_gas(SLOAD_GAS); crate::charge_precompile_gas(ACTIVATION_UPFRONT_GAS);
441
442 let code_hash = {
443 let account = input
444 .internals_mut()
445 .load_account(program_address)
446 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
447 account.data.info.code_hash
448 };
449
450 let code_bytes = {
451 let code_account = input
452 .internals_mut()
453 .load_account_code(program_address)
454 .map_err(|e| PrecompileError::other(format!("load_account_code: {e:?}")))?;
455 code_account
456 .data
457 .code()
458 .map(|c| c.original_bytes())
459 .unwrap_or_default()
460 .to_vec()
461 };
462
463 if code_bytes.is_empty()
464 || !arb_stylus::is_stylus_deployable(&code_bytes, crate::get_arbos_version())
465 {
466 return Err(PrecompileError::other("ProgramNotWasm()"));
467 }
468
469 let wasm = arb_stylus::decompress_wasm(&code_bytes)
470 .map_err(|e| PrecompileError::other(format!("ProgramNotWasm: {e}")))?;
471
472 load_arbos(&mut input)?;
474 crate::charge_precompile_gas(100); let params_word = {
476 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
477 let params_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_PARAMS_KEY);
478 let slot = map_slot(params_key.as_slice(), 0);
479 let val = input
480 .internals_mut()
481 .sload(ARBOS_STATE_ADDRESS, slot)
482 .map_err(|_| PrecompileError::other("sload failed"))?
483 .data;
484 val.to_be_bytes::<32>()
485 };
486 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
487 let page_limit = u16::from_be_bytes([params_word[13], params_word[14]]);
488
489 let time = block_timestamp();
490 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
491 let data_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_DATA_KEY);
492 let program_slot = map_slot_b256(data_key.as_slice(), &code_hash);
493
494 let existing = sload_field(&mut input, program_slot)?;
495 let existing_bytes = existing.to_be_bytes::<32>();
496 let existing_version = u16::from_be_bytes([existing_bytes[0], existing_bytes[1]]);
497 let was_cached = existing_bytes[14] != 0;
498
499 if existing_version == params_version {
500 let activated_at = (existing_bytes[8] as u32) << 16
501 | (existing_bytes[9] as u32) << 8
502 | existing_bytes[10] as u32;
503 let age = hours_to_age(time, activated_at);
504 let expiry_days = u16::from_be_bytes([params_word[19], params_word[20]]);
505 if age <= (expiry_days as u64) * 86400 {
506 return Err(PrecompileError::other("ProgramUpToDate()"));
507 }
508 }
509
510 let gas_available = input.gas.saturating_sub(crate::get_precompile_gas());
511 let mut gas_for_prover = gas_available;
512 let diag_pre_prover = crate::get_precompile_gas();
513 let diag_gas_to_prover = gas_for_prover;
514
515 let info = match arb_stylus::activate_program(
516 &wasm,
517 code_hash.as_ref(),
518 params_version,
519 crate::get_arbos_version(),
520 page_limit,
521 false,
522 &mut gas_for_prover,
523 ) {
524 Ok(info) => info,
525 Err(e) => {
526 crate::charge_precompile_gas(gas_available);
527 return Err(PrecompileError::other(format!("{e}")));
528 }
529 };
530
531 let prover_gas_used = gas_available.saturating_sub(gas_for_prover);
532 crate::charge_precompile_gas(prover_gas_used);
533 let wasm_hash = alloy_primitives::keccak256(&wasm);
534 tracing::warn!(target: "stylus",
535 input_gas = input.gas, pre_prover = diag_pre_prover, to_prover = diag_gas_to_prover,
536 prover_used = prover_gas_used, after_prover = crate::get_precompile_gas(),
537 wasm_len = wasm.len(), %wasm_hash, "activateProgram gas breakdown");
538
539 let module_hashes_key = derive_subspace_key(programs_key.as_slice(), &[2]);
541 let module_hash_slot = map_slot_b256(module_hashes_key.as_slice(), &code_hash);
542 input
543 .internals_mut()
544 .sstore(
545 ARBOS_STATE_ADDRESS,
546 module_hash_slot,
547 U256::from_be_bytes(info.module_hash.0),
548 )
549 .map_err(|_| PrecompileError::other("sstore failed"))?;
550 crate::charge_precompile_gas(SSTORE_GAS);
551 let data_pricer_key = derive_subspace_key(programs_key.as_slice(), &[3]);
552 let demand: u32 =
553 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 0))?.to::<u64>() as u32;
554 let bps: u32 =
555 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 1))?.to::<u64>() as u32;
556 let last_update: u64 =
557 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 2))?.to::<u64>();
558 let min_price: u32 =
559 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 3))?.to::<u64>() as u32;
560 let inertia: u32 =
561 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 4))?.to::<u64>() as u32;
562
563 let passed = (time.saturating_sub(last_update)) as u32;
564 let credit = bps.saturating_mul(passed);
565 let new_demand = demand
566 .saturating_sub(credit)
567 .saturating_add(info.asm_estimate);
568
569 input
570 .internals_mut()
571 .sstore(
572 ARBOS_STATE_ADDRESS,
573 map_slot(data_pricer_key.as_slice(), 0),
574 U256::from(new_demand),
575 )
576 .map_err(|_| PrecompileError::other("sstore failed"))?;
577 crate::charge_precompile_gas(SSTORE_GAS);
578 input
579 .internals_mut()
580 .sstore(
581 ARBOS_STATE_ADDRESS,
582 map_slot(data_pricer_key.as_slice(), 2),
583 U256::from(time),
584 )
585 .map_err(|_| PrecompileError::other("sstore failed"))?;
586 crate::charge_precompile_gas(SSTORE_GAS);
587
588 let exponent = if inertia > 0 {
589 10_000u64 * (new_demand as u64) / (inertia as u64)
590 } else {
591 0
592 };
593 let multiplier = approx_exp_basis_points(exponent);
594 let cost_per_byte = (min_price as u64).saturating_mul(multiplier) / 10_000;
595 let data_fee = U256::from(cost_per_byte.saturating_mul(info.asm_estimate as u64));
596
597 let estimate_kb = div_ceil(info.asm_estimate as u64, 1024).min(0xFF_FFFF) as u32;
599 let hours = hours_since_arbitrum(time);
600 let mut pd = [0u8; 32];
601 pd[0..2].copy_from_slice(¶ms_version.to_be_bytes());
602 pd[2..4].copy_from_slice(&info.init_gas.to_be_bytes());
603 pd[4..6].copy_from_slice(&info.cached_init_gas.to_be_bytes());
604 pd[6..8].copy_from_slice(&info.footprint.to_be_bytes());
605 pd[8] = (hours >> 16) as u8;
606 pd[9] = (hours >> 8) as u8;
607 pd[10] = hours as u8;
608 pd[11] = (estimate_kb >> 16) as u8;
609 pd[12] = (estimate_kb >> 8) as u8;
610 pd[13] = estimate_kb as u8;
611 pd[14] = was_cached as u8;
612
613 input
614 .internals_mut()
615 .sstore(ARBOS_STATE_ADDRESS, program_slot, U256::from_be_bytes(pd))
616 .map_err(|_| PrecompileError::other("sstore failed"))?;
617 crate::charge_precompile_gas(SSTORE_GAS);
618
619 crate::charge_precompile_gas(SLOAD_GAS);
621
622 crate::set_stylus_activation_request(Some(program_address));
624 crate::set_stylus_activation_data_fee(data_fee);
625
626 let event_topic = IArbWasm::ProgramActivated::SIGNATURE_HASH;
627 let mut event_data = Vec::with_capacity(128);
628 event_data.extend_from_slice(&info.module_hash.0);
629 event_data.extend_from_slice(&[0u8; 12]);
630 event_data.extend_from_slice(program_address.as_slice());
631 event_data.extend_from_slice(&data_fee.to_be_bytes::<32>());
632 let mut ver = [0u8; 32];
633 ver[30..32].copy_from_slice(¶ms_version.to_be_bytes());
634 event_data.extend_from_slice(&ver);
635 let event_gas = 375 + 2 * 375 + 8 * event_data.len() as u64;
637 crate::charge_precompile_gas(event_gas);
638 crate::emit_log(ARBWASM_ADDRESS, &[event_topic, code_hash], &event_data);
639
640 let return_data = {
642 let mut output = Vec::with_capacity(64);
643 let mut ver_out = [0u8; 32];
644 ver_out[30..32].copy_from_slice(¶ms_version.to_be_bytes());
645 output.extend_from_slice(&ver_out);
646 output.extend_from_slice(&data_fee.to_be_bytes::<32>());
647 output
648 };
649 let return_gas = COPY_GAS * (return_data.len() as u64).div_ceil(32);
650 crate::charge_precompile_gas(return_gas);
651
652 let gas_used = crate::get_precompile_gas();
653 tracing::warn!(target: "stylus",
654 total = gas_used, args = args_cost, prover = prover_gas_used,
655 event = event_gas, ret = return_gas, "activateProgram total gas");
656 Ok(PrecompileOutput::new(gas_used, return_data.into()))
657}
658
659fn handle_codehash_keepalive(mut input: PrecompileInput<'_>, codehash: B256) -> PrecompileResult {
660 crate::reset_precompile_gas();
661 let args_cost = COPY_GAS * (input.data.len() as u64).saturating_sub(4).div_ceil(32);
662 crate::charge_precompile_gas(args_cost);
663
664 load_arbos(&mut input)?;
665 let params_word = load_params_word(&mut input)?;
666 let params_version = u16::from_be_bytes([params_word[0], params_word[1]]);
667 let keepalive_days = u16::from_be_bytes([params_word[21], params_word[22]]);
668 let expiry_days = u16::from_be_bytes([params_word[19], params_word[20]]);
669 let time = block_timestamp();
670
671 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
672 let data_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_DATA_KEY);
673 let program_slot = map_slot_b256(data_key.as_slice(), &codehash);
674 let program_bytes = sload_field(&mut input, program_slot)?.to_be_bytes::<32>();
675 let program = parse_program(&program_bytes, ¶ms_word);
676
677 if program.version == 0 {
678 return Err(PrecompileError::other("ProgramNotActivated()"));
679 }
680 let age = hours_to_age(
681 time,
682 program_bytes[8] as u32 * 65536 + program_bytes[9] as u32 * 256 + program_bytes[10] as u32,
683 );
684 if age > (expiry_days as u64) * 86400 {
685 return Err(PrecompileError::other("ProgramExpired()"));
686 }
687 if program.version != params_version {
688 return Err(PrecompileError::other("ProgramNeedsUpgrade()"));
689 }
690 if age < (keepalive_days as u64) * 86400 {
691 return Err(PrecompileError::other("ProgramKeepaliveTooSoon()"));
692 }
693
694 let asm_size = program.asm_estimate_kb * 1024;
695
696 let data_pricer_key = derive_subspace_key(programs_key.as_slice(), &[3]);
698 let demand: u32 =
699 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 0))?.to::<u64>() as u32;
700 let bps: u32 =
701 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 1))?.to::<u64>() as u32;
702 let last_update: u64 =
703 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 2))?.to::<u64>();
704 let min_price: u32 =
705 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 3))?.to::<u64>() as u32;
706 let inertia: u32 =
707 sload_field(&mut input, map_slot(data_pricer_key.as_slice(), 4))?.to::<u64>() as u32;
708
709 let passed = (time.saturating_sub(last_update)) as u32;
710 let credit = bps.saturating_mul(passed);
711 let new_demand = demand.saturating_sub(credit).saturating_add(asm_size);
712
713 input
714 .internals_mut()
715 .sstore(
716 ARBOS_STATE_ADDRESS,
717 map_slot(data_pricer_key.as_slice(), 0),
718 U256::from(new_demand),
719 )
720 .map_err(|_| PrecompileError::other("sstore failed"))?;
721 crate::charge_precompile_gas(SSTORE_GAS);
722 input
723 .internals_mut()
724 .sstore(
725 ARBOS_STATE_ADDRESS,
726 map_slot(data_pricer_key.as_slice(), 2),
727 U256::from(time),
728 )
729 .map_err(|_| PrecompileError::other("sstore failed"))?;
730 crate::charge_precompile_gas(SSTORE_GAS);
731
732 let exponent = if inertia > 0 {
733 10_000u64 * (new_demand as u64) / (inertia as u64)
734 } else {
735 0
736 };
737 let multiplier = approx_exp_basis_points(exponent);
738 let cost_per_byte = (min_price as u64).saturating_mul(multiplier) / 10_000;
739 let data_fee = U256::from(cost_per_byte.saturating_mul(asm_size as u64));
740
741 let hours = hours_since_arbitrum(time);
743 let mut pd = program_bytes;
744 pd[8] = (hours >> 16) as u8;
745 pd[9] = (hours >> 8) as u8;
746 pd[10] = hours as u8;
747
748 input
749 .internals_mut()
750 .sstore(ARBOS_STATE_ADDRESS, program_slot, U256::from_be_bytes(pd))
751 .map_err(|_| PrecompileError::other("sstore failed"))?;
752 crate::charge_precompile_gas(SSTORE_GAS);
753
754 crate::charge_precompile_gas(SLOAD_GAS);
756
757 crate::set_stylus_keepalive_request(Some(codehash));
759 crate::set_stylus_activation_data_fee(data_fee);
760
761 let event_topic = IArbWasm::ProgramLifetimeExtended::SIGNATURE_HASH;
762 let mut event_data = Vec::with_capacity(32);
763 event_data.extend_from_slice(&data_fee.to_be_bytes::<32>());
764 let event_gas = 375 + 2 * 375 + 8 * event_data.len() as u64;
765 crate::charge_precompile_gas(event_gas);
766 crate::emit_log(ARBWASM_ADDRESS, &[event_topic, codehash], &event_data);
767
768 let gas_used = crate::get_precompile_gas();
770 Ok(PrecompileOutput::new(gas_used, Vec::new().into()))
771}