arb_precompiles/
arbgasinfo.rs

1use std::sync::Arc;
2
3use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
4use alloy_primitives::{Address, U256};
5use alloy_sol_types::SolInterface;
6use arb_context::ArbPrecompileCtx;
7use arb_storage::ARBOS_STATE_ADDRESS;
8use revm::{
9    context_interface::block::Block,
10    precompile::{PrecompileId, PrecompileOutput, PrecompileResult},
11};
12
13use crate::{ArbPrecompileError, interfaces::IArbGasInfo};
14
15/// ArbGasInfo precompile address (0x6c).
16pub const ARBGASINFO_ADDRESS: Address = Address::new([
17    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
18    0x00, 0x00, 0x00, 0x6c,
19]);
20
21const SLOAD_GAS: u64 = 800;
22const COPY_GAS: u64 = 3;
23
24const TX_DATA_NON_ZERO_GAS: u64 = 16;
25const ASSUMED_SIMPLE_TX_SIZE: u64 = 140;
26const STORAGE_WRITE_COST: u64 = 20_000;
27
28use arbos::l1_pricing::L1_PRICER_FUNDS_POOL_ADDRESS;
29
30pub fn create_arbgasinfo_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
31    DynPrecompile::new_stateful(PrecompileId::custom("arbgasinfo"), move |input| {
32        handler(input, &ctx)
33    })
34}
35
36fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
37    let mut gas_used = 0u64;
38    let gas_limit = input.gas;
39    crate::init_precompile_gas(&mut gas_used, ctx, input.data.len());
40
41    let call = match IArbGasInfo::ArbGasInfoCalls::abi_decode(input.data) {
42        Ok(c) => c,
43        Err(_) => return crate::burn_all_revert(gas_limit),
44    };
45    if let Some(r) = crate::reject_nonpayable_value(input.value, input.data, gas_limit, &[]) {
46        return r;
47    }
48    if let Some(r) = crate::reject_delegate_nonpure(
49        input.target_address != input.bytecode_address,
50        input.data,
51        gas_limit,
52        &[],
53    ) {
54        return r;
55    }
56
57    use IArbGasInfo::ArbGasInfoCalls as Calls;
58    let result = match call {
59        Calls::getL1BaseFeeEstimate(_) | Calls::getL1GasPriceEstimate(_) => {
60            read_l1_price_per_unit(&mut input, &mut gas_used, ctx)
61        }
62        Calls::getMinimumGasPrice(_) => read_l2_min_base_fee(&mut input, &mut gas_used, ctx),
63        Calls::getPricesInWei(_) | Calls::getPricesInWeiWithAggregator(_) => {
64            handle_prices_in_wei(&mut input, &mut gas_used, ctx)
65        }
66        Calls::getGasAccountingParams(_) => {
67            handle_gas_accounting_params(&mut input, &mut gas_used, ctx)
68        }
69        Calls::getCurrentTxL1GasFees(_) => {
70            let fee = U256::from(ctx.tx_snapshot().poster_fee);
71            crate::charge_computation(&mut gas_used, ctx, COPY_GAS);
72            Ok(PrecompileOutput::new(
73                gas_used.min(gas_limit),
74                fee.to_be_bytes::<32>().to_vec().into(),
75            ))
76        }
77        Calls::getPricesInArbGas(_) | Calls::getPricesInArbGasWithAggregator(_) => {
78            handle_prices_in_arbgas(&mut input, &mut gas_used, ctx)
79        }
80        Calls::getL1BaseFeeEstimateInertia(_) => read_l1_inertia(&mut input, &mut gas_used, ctx),
81        Calls::getGasBacklog(_) => read_l2_gas_backlog(&mut input, &mut gas_used, ctx),
82        Calls::getPricingInertia(_) => read_l2_pricing_inertia(&mut input, &mut gas_used, ctx),
83        Calls::getGasBacklogTolerance(_) => {
84            read_l2_backlog_tolerance(&mut input, &mut gas_used, ctx)
85        }
86        Calls::getL1PricingSurplus(_) => handle_l1_pricing_surplus(&mut input, &mut gas_used, ctx),
87        Calls::getPerBatchGasCharge(_) => {
88            read_l1_per_batch_gas_cost(&mut input, &mut gas_used, ctx)
89        }
90        Calls::getAmortizedCostCapBips(_) => {
91            read_l1_amortized_cost_cap_bips(&mut input, &mut gas_used, ctx)
92        }
93        Calls::getL1FeesAvailable(_) => {
94            if let Some(r) = crate::check_method_version(ctx, gas_limit, 10, 0) {
95                return r;
96            }
97            read_l1_fees_available(&mut input, &mut gas_used, ctx)
98        }
99        Calls::getL1RewardRate(_) => {
100            if let Some(r) = crate::check_method_version(ctx, gas_limit, 11, 0) {
101                return r;
102            }
103            read_l1_per_unit_reward(&mut input, &mut gas_used, ctx)
104        }
105        Calls::getL1RewardRecipient(_) => {
106            if let Some(r) = crate::check_method_version(ctx, gas_limit, 11, 0) {
107                return r;
108            }
109            read_l1_pay_rewards_to(&mut input, &mut gas_used, ctx)
110        }
111        Calls::getL1PricingEquilibrationUnits(_) => {
112            if let Some(r) = crate::check_method_version(ctx, gas_limit, 20, 0) {
113                return r;
114            }
115            read_l1_equilibration_units(&mut input, &mut gas_used, ctx)
116        }
117        Calls::getLastL1PricingUpdateTime(_) => {
118            if let Some(r) = crate::check_method_version(ctx, gas_limit, 20, 0) {
119                return r;
120            }
121            read_l1_last_update_time(&mut input, &mut gas_used, ctx)
122        }
123        Calls::getL1PricingFundsDueForRewards(_) => {
124            if let Some(r) = crate::check_method_version(ctx, gas_limit, 20, 0) {
125                return r;
126            }
127            read_l1_funds_due_for_rewards(&mut input, &mut gas_used, ctx)
128        }
129        Calls::getL1PricingUnitsSinceUpdate(_) => {
130            if let Some(r) = crate::check_method_version(ctx, gas_limit, 20, 0) {
131                return r;
132            }
133            read_l1_units_since_update(&mut input, &mut gas_used, ctx)
134        }
135        Calls::getLastL1PricingSurplus(_) => {
136            if let Some(r) = crate::check_method_version(ctx, gas_limit, 20, 0) {
137                return r;
138            }
139            read_l1_last_surplus(&mut input, &mut gas_used, ctx)
140        }
141        Calls::getMaxBlockGasLimit(_) => {
142            if let Some(r) = crate::check_method_version(ctx, gas_limit, 50, 0) {
143                return r;
144            }
145            read_l2_per_block_gas_limit(&mut input, &mut gas_used, ctx)
146        }
147        Calls::getMaxTxGasLimit(_) => {
148            if let Some(r) = crate::check_method_version(ctx, gas_limit, 50, 0) {
149                return r;
150            }
151            read_l2_per_tx_gas_limit(&mut input, &mut gas_used, ctx)
152        }
153        Calls::getGasPricingConstraints(_) => {
154            if let Some(r) = crate::check_method_version(ctx, gas_limit, 50, 0) {
155                return r;
156            }
157            handle_gas_pricing_constraints(&mut input, &mut gas_used, ctx)
158        }
159        Calls::getMultiGasPricingConstraints(_) => {
160            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
161                return r;
162            }
163            handle_multi_gas_pricing_constraints(&mut input, &mut gas_used, ctx)
164        }
165        Calls::getMultiGasBaseFee(_) => {
166            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
167                return r;
168            }
169            handle_multi_gas_base_fee(&mut input, &mut gas_used, ctx)
170        }
171    };
172    crate::gas_check(ctx, gas_limit, gas_used, result)
173}
174
175// ── helpers ──────────────────────────────────────────────────────────
176
177fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), ArbPrecompileError> {
178    input
179        .internals_mut()
180        .load_account(ARBOS_STATE_ADDRESS)
181        .map_err(ArbPrecompileError::fatal)?;
182    Ok(())
183}
184
185fn field_read_output(
186    gas_used: &mut u64,
187    ctx: &ArbPrecompileCtx,
188    gas_limit: u64,
189    value: U256,
190) -> PrecompileResult {
191    // init already charged the OpenArbosState read and L2Calldata; body
192    // adds one storage read for the field and the result-copy as computation.
193    crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
194    crate::charge_computation(gas_used, ctx, COPY_GAS);
195    Ok(PrecompileOutput::new(
196        (*gas_used).min(gas_limit),
197        value.to_be_bytes::<32>().to_vec().into(),
198    ))
199}
200
201// ── L1 pricing field readers ────────────────────────────────────────
202
203fn read_l1_price_per_unit(
204    input: &mut PrecompileInput<'_>,
205    gas_used: &mut u64,
206    ctx: &ArbPrecompileCtx,
207) -> PrecompileResult {
208    let gas_limit = input.gas;
209    load_arbos(input)?;
210    let internals = input.internals_mut();
211    let arb_state = ctx
212        .block
213        .arbos_state(internals)
214        .map_err(ArbPrecompileError::fatal)?;
215    let value = arb_state
216        .l1_pricing_state
217        .price_per_unit(internals)
218        .map_err(ArbPrecompileError::fatal)?;
219    field_read_output(gas_used, ctx, gas_limit, value)
220}
221
222fn read_l1_inertia(
223    input: &mut PrecompileInput<'_>,
224    gas_used: &mut u64,
225    ctx: &ArbPrecompileCtx,
226) -> PrecompileResult {
227    let gas_limit = input.gas;
228    load_arbos(input)?;
229    let internals = input.internals_mut();
230    let arb_state = ctx
231        .block
232        .arbos_state(internals)
233        .map_err(ArbPrecompileError::fatal)?;
234    let value = arb_state
235        .l1_pricing_state
236        .inertia(internals)
237        .map_err(ArbPrecompileError::fatal)?;
238    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
239}
240
241fn read_l1_per_unit_reward(
242    input: &mut PrecompileInput<'_>,
243    gas_used: &mut u64,
244    ctx: &ArbPrecompileCtx,
245) -> PrecompileResult {
246    let gas_limit = input.gas;
247    load_arbos(input)?;
248    let internals = input.internals_mut();
249    let arb_state = ctx
250        .block
251        .arbos_state(internals)
252        .map_err(ArbPrecompileError::fatal)?;
253    let value = arb_state
254        .l1_pricing_state
255        .per_unit_reward(internals)
256        .map_err(ArbPrecompileError::fatal)?;
257    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
258}
259
260fn read_l1_pay_rewards_to(
261    input: &mut PrecompileInput<'_>,
262    gas_used: &mut u64,
263    ctx: &ArbPrecompileCtx,
264) -> PrecompileResult {
265    let gas_limit = input.gas;
266    load_arbos(input)?;
267    let internals = input.internals_mut();
268    let arb_state = ctx
269        .block
270        .arbos_state(internals)
271        .map_err(ArbPrecompileError::fatal)?;
272    let addr = arb_state
273        .l1_pricing_state
274        .pay_rewards_to(internals)
275        .map_err(ArbPrecompileError::fatal)?;
276    field_read_output(
277        gas_used,
278        ctx,
279        gas_limit,
280        U256::from_be_slice(addr.as_slice()),
281    )
282}
283
284fn read_l1_last_surplus(
285    input: &mut PrecompileInput<'_>,
286    gas_used: &mut u64,
287    ctx: &ArbPrecompileCtx,
288) -> PrecompileResult {
289    let gas_limit = input.gas;
290    load_arbos(input)?;
291    let internals = input.internals_mut();
292    let arb_state = ctx
293        .block
294        .arbos_state(internals)
295        .map_err(ArbPrecompileError::fatal)?;
296    let (magnitude, negative) = arb_state
297        .l1_pricing_state
298        .last_surplus(internals)
299        .map_err(ArbPrecompileError::fatal)?;
300    let raw = if negative {
301        U256::ZERO.wrapping_sub(magnitude)
302    } else {
303        magnitude
304    };
305    field_read_output(gas_used, ctx, gas_limit, raw)
306}
307
308fn read_l1_per_batch_gas_cost(
309    input: &mut PrecompileInput<'_>,
310    gas_used: &mut u64,
311    ctx: &ArbPrecompileCtx,
312) -> PrecompileResult {
313    let gas_limit = input.gas;
314    load_arbos(input)?;
315    let internals = input.internals_mut();
316    let arb_state = ctx
317        .block
318        .arbos_state(internals)
319        .map_err(ArbPrecompileError::fatal)?;
320    let value = arb_state
321        .l1_pricing_state
322        .per_batch_gas_cost(internals)
323        .map_err(ArbPrecompileError::fatal)?;
324    field_read_output(gas_used, ctx, gas_limit, U256::from(value as u64))
325}
326
327fn read_l1_amortized_cost_cap_bips(
328    input: &mut PrecompileInput<'_>,
329    gas_used: &mut u64,
330    ctx: &ArbPrecompileCtx,
331) -> PrecompileResult {
332    let gas_limit = input.gas;
333    load_arbos(input)?;
334    let internals = input.internals_mut();
335    let arb_state = ctx
336        .block
337        .arbos_state(internals)
338        .map_err(ArbPrecompileError::fatal)?;
339    let value = arb_state
340        .l1_pricing_state
341        .amortized_cost_cap_bips(internals)
342        .map_err(ArbPrecompileError::fatal)?;
343    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
344}
345
346fn read_l1_equilibration_units(
347    input: &mut PrecompileInput<'_>,
348    gas_used: &mut u64,
349    ctx: &ArbPrecompileCtx,
350) -> PrecompileResult {
351    let gas_limit = input.gas;
352    load_arbos(input)?;
353    let internals = input.internals_mut();
354    let arb_state = ctx
355        .block
356        .arbos_state(internals)
357        .map_err(ArbPrecompileError::fatal)?;
358    let value = arb_state
359        .l1_pricing_state
360        .equilibration_units(internals)
361        .map_err(ArbPrecompileError::fatal)?;
362    field_read_output(gas_used, ctx, gas_limit, value)
363}
364
365fn read_l1_last_update_time(
366    input: &mut PrecompileInput<'_>,
367    gas_used: &mut u64,
368    ctx: &ArbPrecompileCtx,
369) -> PrecompileResult {
370    let gas_limit = input.gas;
371    load_arbos(input)?;
372    let internals = input.internals_mut();
373    let arb_state = ctx
374        .block
375        .arbos_state(internals)
376        .map_err(ArbPrecompileError::fatal)?;
377    let value = arb_state
378        .l1_pricing_state
379        .last_update_time(internals)
380        .map_err(ArbPrecompileError::fatal)?;
381    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
382}
383
384fn read_l1_funds_due_for_rewards(
385    input: &mut PrecompileInput<'_>,
386    gas_used: &mut u64,
387    ctx: &ArbPrecompileCtx,
388) -> PrecompileResult {
389    let gas_limit = input.gas;
390    load_arbos(input)?;
391    let internals = input.internals_mut();
392    let arb_state = ctx
393        .block
394        .arbos_state(internals)
395        .map_err(ArbPrecompileError::fatal)?;
396    let value = arb_state
397        .l1_pricing_state
398        .funds_due_for_rewards(internals)
399        .map_err(ArbPrecompileError::fatal)?;
400    field_read_output(gas_used, ctx, gas_limit, value)
401}
402
403fn read_l1_units_since_update(
404    input: &mut PrecompileInput<'_>,
405    gas_used: &mut u64,
406    ctx: &ArbPrecompileCtx,
407) -> PrecompileResult {
408    let gas_limit = input.gas;
409    load_arbos(input)?;
410    let internals = input.internals_mut();
411    let arb_state = ctx
412        .block
413        .arbos_state(internals)
414        .map_err(ArbPrecompileError::fatal)?;
415    let value = arb_state
416        .l1_pricing_state
417        .units_since_update(internals)
418        .map_err(ArbPrecompileError::fatal)?;
419    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
420}
421
422fn read_l1_fees_available(
423    input: &mut PrecompileInput<'_>,
424    gas_used: &mut u64,
425    ctx: &ArbPrecompileCtx,
426) -> PrecompileResult {
427    let gas_limit = input.gas;
428    load_arbos(input)?;
429    let internals = input.internals_mut();
430    let arb_state = ctx
431        .block
432        .arbos_state(internals)
433        .map_err(ArbPrecompileError::fatal)?;
434    let value = arb_state
435        .l1_pricing_state
436        .l1_fees_available(internals)
437        .map_err(ArbPrecompileError::fatal)?;
438    field_read_output(gas_used, ctx, gas_limit, value)
439}
440
441// ── L2 pricing field readers ────────────────────────────────────────
442
443fn read_l2_min_base_fee(
444    input: &mut PrecompileInput<'_>,
445    gas_used: &mut u64,
446    ctx: &ArbPrecompileCtx,
447) -> PrecompileResult {
448    let gas_limit = input.gas;
449    load_arbos(input)?;
450    let internals = input.internals_mut();
451    let arb_state = ctx
452        .block
453        .arbos_state(internals)
454        .map_err(ArbPrecompileError::fatal)?;
455    let value = arb_state
456        .l2_pricing_state
457        .min_base_fee_wei(internals)
458        .map_err(ArbPrecompileError::fatal)?;
459    field_read_output(gas_used, ctx, gas_limit, value)
460}
461
462fn read_l2_gas_backlog(
463    input: &mut PrecompileInput<'_>,
464    gas_used: &mut u64,
465    ctx: &ArbPrecompileCtx,
466) -> PrecompileResult {
467    let gas_limit = input.gas;
468    load_arbos(input)?;
469    let internals = input.internals_mut();
470    let arb_state = ctx
471        .block
472        .arbos_state(internals)
473        .map_err(ArbPrecompileError::fatal)?;
474    let value = arb_state
475        .l2_pricing_state
476        .gas_backlog(internals)
477        .map_err(ArbPrecompileError::fatal)?;
478    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
479}
480
481fn read_l2_pricing_inertia(
482    input: &mut PrecompileInput<'_>,
483    gas_used: &mut u64,
484    ctx: &ArbPrecompileCtx,
485) -> PrecompileResult {
486    let gas_limit = input.gas;
487    load_arbos(input)?;
488    let internals = input.internals_mut();
489    let arb_state = ctx
490        .block
491        .arbos_state(internals)
492        .map_err(ArbPrecompileError::fatal)?;
493    let value = arb_state
494        .l2_pricing_state
495        .pricing_inertia(internals)
496        .map_err(ArbPrecompileError::fatal)?;
497    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
498}
499
500fn read_l2_backlog_tolerance(
501    input: &mut PrecompileInput<'_>,
502    gas_used: &mut u64,
503    ctx: &ArbPrecompileCtx,
504) -> PrecompileResult {
505    let gas_limit = input.gas;
506    load_arbos(input)?;
507    let internals = input.internals_mut();
508    let arb_state = ctx
509        .block
510        .arbos_state(internals)
511        .map_err(ArbPrecompileError::fatal)?;
512    let value = arb_state
513        .l2_pricing_state
514        .backlog_tolerance(internals)
515        .map_err(ArbPrecompileError::fatal)?;
516    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
517}
518
519fn read_l2_per_block_gas_limit(
520    input: &mut PrecompileInput<'_>,
521    gas_used: &mut u64,
522    ctx: &ArbPrecompileCtx,
523) -> PrecompileResult {
524    let gas_limit = input.gas;
525    load_arbos(input)?;
526    let internals = input.internals_mut();
527    let arb_state = ctx
528        .block
529        .arbos_state(internals)
530        .map_err(ArbPrecompileError::fatal)?;
531    let value = arb_state
532        .l2_pricing_state
533        .per_block_gas_limit(internals)
534        .map_err(ArbPrecompileError::fatal)?;
535    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
536}
537
538fn read_l2_per_tx_gas_limit(
539    input: &mut PrecompileInput<'_>,
540    gas_used: &mut u64,
541    ctx: &ArbPrecompileCtx,
542) -> PrecompileResult {
543    let gas_limit = input.gas;
544    load_arbos(input)?;
545    let internals = input.internals_mut();
546    let arb_state = ctx
547        .block
548        .arbos_state(internals)
549        .map_err(ArbPrecompileError::fatal)?;
550    let value = arb_state
551        .l2_pricing_state
552        .per_tx_gas_limit(internals)
553        .map_err(ArbPrecompileError::fatal)?;
554    field_read_output(gas_used, ctx, gas_limit, U256::from(value))
555}
556
557// ── Compound handlers ───────────────────────────────────────────────
558
559/// Compute L1 pricing surplus.
560/// v10+: `L1FeesAvailable - (TotalFundsDue + FundsDueForRewards)` (signed).
561/// pre-v10: `Balance(L1PricerFundsPool) - (TotalFundsDue + FundsDueForRewards)`.
562fn handle_l1_pricing_surplus(
563    input: &mut PrecompileInput<'_>,
564    gas_used: &mut u64,
565    ctx: &ArbPrecompileCtx,
566) -> PrecompileResult {
567    let gas_limit = input.gas;
568    let arbos_version = ctx.block.arbos_version;
569    load_arbos(input)?;
570
571    let internals = input.internals_mut();
572    let arb_state = ctx
573        .block
574        .arbos_state(internals)
575        .map_err(ArbPrecompileError::fatal)?;
576
577    let bpt = arb_state.l1_pricing_state.batch_poster_table();
578    let total_funds_due = bpt
579        .total_funds_due(internals)
580        .map_err(ArbPrecompileError::fatal)?;
581    let funds_due_for_rewards = arb_state
582        .l1_pricing_state
583        .funds_due_for_rewards(internals)
584        .map_err(ArbPrecompileError::fatal)?;
585    let need_funds = total_funds_due.saturating_add(funds_due_for_rewards);
586
587    let have_funds = if arbos_version >= 10 {
588        arb_state
589            .l1_pricing_state
590            .l1_fees_available(internals)
591            .map_err(ArbPrecompileError::fatal)?
592    } else {
593        let account = internals
594            .load_account(L1_PRICER_FUNDS_POOL_ADDRESS)
595            .map_err(ArbPrecompileError::fatal)?;
596        account.data.info.balance
597    };
598
599    let surplus = if have_funds >= need_funds {
600        have_funds - need_funds
601    } else {
602        let deficit = need_funds - have_funds;
603        U256::ZERO.wrapping_sub(deficit)
604    };
605
606    // body reads (init covers the OpenArbosState).
607    let body_sloads = if arbos_version >= 10 { 3 } else { 2 };
608    crate::charge_storage_read(gas_used, ctx, body_sloads * SLOAD_GAS);
609    crate::charge_computation(gas_used, ctx, COPY_GAS);
610    Ok(PrecompileOutput::new(
611        (*gas_used).min(gas_limit),
612        surplus.to_be_bytes::<32>().to_vec().into(),
613    ))
614}
615
616fn handle_prices_in_wei(
617    input: &mut PrecompileInput<'_>,
618    gas_used: &mut u64,
619    ctx: &ArbPrecompileCtx,
620) -> PrecompileResult {
621    let data_len = input.data.len();
622    let gas_limit = input.gas;
623    let arbos_version = ctx.block.arbos_version;
624
625    // Reth zeros BlockEnv basefee for eth_call without a gas price;
626    // fall back to the L2PricingState slot (written at StartBlock) so
627    // eth_call returns the current block's basefee.
628    let block_basefee = U256::from(input.internals().block_env().basefee());
629    load_arbos(input)?;
630
631    let internals = input.internals_mut();
632    let arb_state = ctx
633        .block
634        .arbos_state(internals)
635        .map_err(ArbPrecompileError::fatal)?;
636
637    let l1_price = arb_state
638        .l1_pricing_state
639        .price_per_unit(internals)
640        .map_err(ArbPrecompileError::fatal)?;
641
642    // Pre-v4: no MinBaseFeeWei read; perArbGasBase = l2GasPrice, congestion = 0.
643    let read_min_base = arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_4;
644    let l2_min = if read_min_base {
645        arb_state
646            .l2_pricing_state
647            .min_base_fee_wei(internals)
648            .map_err(ArbPrecompileError::fatal)?
649    } else {
650        U256::ZERO
651    };
652    let l2_gas_price = if block_basefee.is_zero() {
653        arb_state
654            .l2_pricing_state
655            .base_fee_wei(internals)
656            .map_err(ArbPrecompileError::fatal)?
657    } else {
658        block_basefee
659    };
660
661    let wei_for_l1_calldata = l1_price.saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS));
662    let per_l2_tx = wei_for_l1_calldata.saturating_mul(U256::from(ASSUMED_SIMPLE_TX_SIZE));
663    let (per_arbgas_base, per_arbgas_congestion) = if read_min_base {
664        let base = l2_gas_price.min(l2_min);
665        (base, l2_gas_price.saturating_sub(base))
666    } else {
667        (l2_gas_price, U256::ZERO)
668    };
669    let per_arbgas_total = l2_gas_price;
670    let wei_for_l2_storage = l2_gas_price.saturating_mul(U256::from(STORAGE_WRITE_COST));
671
672    let mut out = Vec::with_capacity(192);
673    out.extend_from_slice(&per_l2_tx.to_be_bytes::<32>());
674    out.extend_from_slice(&wei_for_l1_calldata.to_be_bytes::<32>());
675    out.extend_from_slice(&wei_for_l2_storage.to_be_bytes::<32>());
676    out.extend_from_slice(&per_arbgas_base.to_be_bytes::<32>());
677    out.extend_from_slice(&per_arbgas_congestion.to_be_bytes::<32>());
678    out.extend_from_slice(&per_arbgas_total.to_be_bytes::<32>());
679
680    // body reads (1 pre-v4, 2 v4+); copy for result words (6).
681    let _ = data_len;
682    let body_sloads = if read_min_base { 2 } else { 1 };
683    crate::charge_storage_read(gas_used, ctx, body_sloads * SLOAD_GAS);
684    crate::charge_computation(gas_used, ctx, 6 * COPY_GAS);
685    Ok(PrecompileOutput::new(
686        (*gas_used).min(gas_limit),
687        out.into(),
688    ))
689}
690
691fn handle_gas_accounting_params(
692    input: &mut PrecompileInput<'_>,
693    gas_used: &mut u64,
694    ctx: &ArbPrecompileCtx,
695) -> PrecompileResult {
696    let gas_limit = input.gas;
697    load_arbos(input)?;
698
699    let internals = input.internals_mut();
700    let arb_state = ctx
701        .block
702        .arbos_state(internals)
703        .map_err(ArbPrecompileError::fatal)?;
704    let speed_limit = arb_state
705        .l2_pricing_state
706        .speed_limit_per_second(internals)
707        .map_err(ArbPrecompileError::fatal)?;
708    let gas_limit_val = arb_state
709        .l2_pricing_state
710        .per_block_gas_limit(internals)
711        .map_err(ArbPrecompileError::fatal)?;
712
713    let speed_word = U256::from(speed_limit);
714    let limit_word = U256::from(gas_limit_val);
715    let mut out = Vec::with_capacity(96);
716    out.extend_from_slice(&speed_word.to_be_bytes::<32>());
717    out.extend_from_slice(&limit_word.to_be_bytes::<32>());
718    out.extend_from_slice(&limit_word.to_be_bytes::<32>());
719
720    crate::charge_storage_read(gas_used, ctx, 2 * SLOAD_GAS);
721    crate::charge_computation(gas_used, ctx, 3 * COPY_GAS);
722    Ok(PrecompileOutput::new(
723        (*gas_used).min(gas_limit),
724        out.into(),
725    ))
726}
727
728fn handle_prices_in_arbgas(
729    input: &mut PrecompileInput<'_>,
730    gas_used: &mut u64,
731    ctx: &ArbPrecompileCtx,
732) -> PrecompileResult {
733    let data_len = input.data.len();
734    let gas_limit = input.gas;
735
736    let block_basefee = U256::from(input.internals().block_env().basefee());
737    load_arbos(input)?;
738
739    let internals = input.internals_mut();
740    let arb_state = ctx
741        .block
742        .arbos_state(internals)
743        .map_err(ArbPrecompileError::fatal)?;
744    let l1_price = arb_state
745        .l1_pricing_state
746        .price_per_unit(internals)
747        .map_err(ArbPrecompileError::fatal)?;
748    let l2_gas_price = if block_basefee.is_zero() {
749        arb_state
750            .l2_pricing_state
751            .base_fee_wei(internals)
752            .map_err(ArbPrecompileError::fatal)?
753    } else {
754        block_basefee
755    };
756
757    let arbos_version = ctx.block.arbos_version;
758    let wei_for_l1_calldata = l1_price.saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS));
759
760    let gas_for_l1_calldata = if l2_gas_price > U256::ZERO {
761        wei_for_l1_calldata / l2_gas_price
762    } else {
763        U256::ZERO
764    };
765    // Pre-v4: gasPerL2Tx = AssumedSimpleTxSize (constant).
766    // v4+: gasPerL2Tx = wei_per_l2_tx / l2_gas_price.
767    let gas_per_l2_tx = if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_4 {
768        let wei_per_l2_tx = wei_for_l1_calldata.saturating_mul(U256::from(ASSUMED_SIMPLE_TX_SIZE));
769        if l2_gas_price > U256::ZERO {
770            wei_per_l2_tx / l2_gas_price
771        } else {
772            U256::ZERO
773        }
774    } else {
775        U256::from(ASSUMED_SIMPLE_TX_SIZE)
776    };
777
778    let mut out = Vec::with_capacity(96);
779    out.extend_from_slice(&gas_per_l2_tx.to_be_bytes::<32>());
780    out.extend_from_slice(&gas_for_l1_calldata.to_be_bytes::<32>());
781    out.extend_from_slice(&U256::from(STORAGE_WRITE_COST).to_be_bytes::<32>());
782
783    // body reads (1 SLOAD for L1 price). l2GasPrice comes from evm.Context.BaseFee (free).
784    let _ = data_len;
785    crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
786    crate::charge_computation(gas_used, ctx, 3 * COPY_GAS);
787    Ok(PrecompileOutput::new(
788        (*gas_used).min(gas_limit),
789        out.into(),
790    ))
791}
792
793// ── Constraint getters (ArbOS v50+) ─────────────────────────────────
794
795/// Index of `ResourceKindSingleDim` — special-cased to fall back to the
796/// global L2 base fee in `getMultiGasBaseFee`.
797const RESOURCE_KIND_SINGLE_DIM: u64 = 6;
798
799/// Returns `[][3]uint64` — (target, adjustmentWindow, backlog) per constraint.
800fn handle_gas_pricing_constraints(
801    input: &mut PrecompileInput<'_>,
802    gas_used: &mut u64,
803    ctx: &ArbPrecompileCtx,
804) -> PrecompileResult {
805    let gas_limit = input.gas;
806    load_arbos(input)?;
807
808    let internals = input.internals_mut();
809    let arb_state = ctx
810        .block
811        .arbos_state(internals)
812        .map_err(ArbPrecompileError::fatal)?;
813
814    let count = arb_state
815        .l2_pricing_state
816        .gas_constraints_length(internals)
817        .map_err(ArbPrecompileError::fatal)?;
818    let mut sloads: u64 = 2; // OAS + vec length
819
820    // ABI: offset to dynamic array, then length, then N×3 uint64 values.
821    let mut out = Vec::with_capacity(64 + count as usize * 96);
822    out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
823    out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
824
825    for i in 0..count {
826        let constraint = arb_state.l2_pricing_state.open_gas_constraint_at(i);
827        let target = constraint
828            .target(internals)
829            .map_err(ArbPrecompileError::fatal)?;
830        let window = constraint
831            .adjustment_window(internals)
832            .map_err(ArbPrecompileError::fatal)?;
833        let backlog = constraint
834            .backlog(internals)
835            .map_err(ArbPrecompileError::fatal)?;
836
837        out.extend_from_slice(&U256::from(target).to_be_bytes::<32>());
838        out.extend_from_slice(&U256::from(window).to_be_bytes::<32>());
839        out.extend_from_slice(&U256::from(backlog).to_be_bytes::<32>());
840        sloads += 3;
841    }
842
843    let result_words = (out.len() as u64).div_ceil(32);
844    // Subtract the OpenArbosState SLOAD already covered by init.
845    let body_sloads = sloads.saturating_sub(1);
846    crate::charge_storage_read(gas_used, ctx, body_sloads * SLOAD_GAS);
847    crate::charge_computation(gas_used, ctx, result_words * COPY_GAS);
848    Ok(PrecompileOutput::new(
849        (*gas_used).min(gas_limit),
850        out.into(),
851    ))
852}
853
854/// Returns `[]MultiGasConstraint` ABI-encoded.
855///
856/// MultiGasConstraint = (WeightedResource[] resources, uint32 adjustmentWindowSecs,
857///                        uint64 targetPerSec, uint64 backlog)
858/// WeightedResource   = (uint8 resource, uint64 weight)
859fn handle_multi_gas_pricing_constraints(
860    input: &mut PrecompileInput<'_>,
861    gas_used: &mut u64,
862    ctx: &ArbPrecompileCtx,
863) -> PrecompileResult {
864    use arb_primitives::multigas::ResourceKind;
865    let gas_limit = input.gas;
866    load_arbos(input)?;
867
868    let internals = input.internals_mut();
869    let arb_state = ctx
870        .block
871        .arbos_state(internals)
872        .map_err(ArbPrecompileError::fatal)?;
873
874    let count = arb_state
875        .l2_pricing_state
876        .multi_gas_constraints_length(internals)
877        .map_err(ArbPrecompileError::fatal)?;
878    let mut sloads: u64 = 2; // OAS + vec length
879
880    struct ConstraintData {
881        target: u64,
882        window: u32,
883        backlog: u64,
884        resources: Vec<(u8, u64)>,
885    }
886    let mut constraints = Vec::with_capacity(count as usize);
887
888    for i in 0..count {
889        let constraint = arb_state.l2_pricing_state.open_multi_gas_constraint_at(i);
890        let target = constraint
891            .target(internals)
892            .map_err(ArbPrecompileError::fatal)?;
893        let window = constraint
894            .adjustment_window(internals)
895            .map_err(ArbPrecompileError::fatal)?;
896        let backlog = constraint
897            .backlog(internals)
898            .map_err(ArbPrecompileError::fatal)?;
899        sloads += 3;
900
901        let mut resources = Vec::new();
902        for kind in ResourceKind::ALL {
903            let weight = constraint
904                .resource_weight(internals, kind)
905                .map_err(ArbPrecompileError::fatal)?;
906            sloads += 1;
907            if weight > 0 {
908                resources.push((kind as u8, weight));
909            }
910        }
911        constraints.push(ConstraintData {
912            target,
913            window,
914            backlog,
915            resources,
916        });
917    }
918
919    let n = constraints.len();
920    let mut out = Vec::new();
921    out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
922    out.extend_from_slice(&U256::from(n).to_be_bytes::<32>());
923
924    let elem_sizes: Vec<usize> = constraints
925        .iter()
926        .map(|c| 4 * 32 + 32 + c.resources.len() * 64)
927        .collect();
928
929    let mut running_offset = n * 32;
930    for size in &elem_sizes {
931        out.extend_from_slice(&U256::from(running_offset).to_be_bytes::<32>());
932        running_offset += size;
933    }
934
935    for c in &constraints {
936        let m = c.resources.len();
937        out.extend_from_slice(&U256::from(4u64 * 32).to_be_bytes::<32>());
938        out.extend_from_slice(&U256::from(c.window).to_be_bytes::<32>());
939        out.extend_from_slice(&U256::from(c.target).to_be_bytes::<32>());
940        out.extend_from_slice(&U256::from(c.backlog).to_be_bytes::<32>());
941        out.extend_from_slice(&U256::from(m).to_be_bytes::<32>());
942        for &(kind, weight) in &c.resources {
943            out.extend_from_slice(&U256::from(kind).to_be_bytes::<32>());
944            out.extend_from_slice(&U256::from(weight).to_be_bytes::<32>());
945        }
946    }
947
948    let result_words = (out.len() as u64).div_ceil(32);
949    let body_sloads = sloads.saturating_sub(1);
950    crate::charge_storage_read(gas_used, ctx, body_sloads * SLOAD_GAS);
951    crate::charge_computation(gas_used, ctx, result_words * COPY_GAS);
952    Ok(PrecompileOutput::new(
953        (*gas_used).min(gas_limit),
954        out.into(),
955    ))
956}
957
958/// Returns `uint256[]` — current-block base fee per resource kind. Reads BaseFeeWei,
959/// then per-kind fees; for `ResourceKindSingleDim` and any zero per-kind fee, falls
960/// back to BaseFeeWei.
961fn handle_multi_gas_base_fee(
962    input: &mut PrecompileInput<'_>,
963    gas_used: &mut u64,
964    ctx: &ArbPrecompileCtx,
965) -> PrecompileResult {
966    use arb_primitives::multigas::{NUM_RESOURCE_KIND, ResourceKind};
967    let gas_limit = input.gas;
968    load_arbos(input)?;
969
970    let internals = input.internals_mut();
971    let arb_state = ctx
972        .block
973        .arbos_state(internals)
974        .map_err(ArbPrecompileError::fatal)?;
975
976    let base_fee_wei = arb_state
977        .l2_pricing_state
978        .base_fee_wei(internals)
979        .map_err(ArbPrecompileError::fatal)?;
980    let multi_gas_fees = arb_state.l2_pricing_state.multi_gas_fees();
981
982    let mut out = Vec::with_capacity(64 + NUM_RESOURCE_KIND * 32);
983    out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
984    out.extend_from_slice(&U256::from(NUM_RESOURCE_KIND).to_be_bytes::<32>());
985
986    for kind in ResourceKind::ALL {
987        let raw = multi_gas_fees
988            .get_current_block_fee(internals, kind)
989            .map_err(ArbPrecompileError::fatal)?;
990        let fee = if kind as u64 == RESOURCE_KIND_SINGLE_DIM || raw == U256::ZERO {
991            base_fee_wei
992        } else {
993            raw
994        };
995        out.extend_from_slice(&fee.to_be_bytes::<32>());
996    }
997
998    let result_words = (out.len() as u64).div_ceil(32);
999    // body reads: 1 SLOAD for base_fee_wei + NUM_RESOURCE_KIND per-kind fee SLOADs.
1000    let body_sloads = 1 + NUM_RESOURCE_KIND as u64;
1001    crate::charge_storage_read(gas_used, ctx, body_sloads * SLOAD_GAS);
1002    crate::charge_computation(gas_used, ctx, result_words * COPY_GAS);
1003    Ok(PrecompileOutput::new(
1004        (*gas_used).min(gas_limit),
1005        out.into(),
1006    ))
1007}