arb_precompiles/
arbowner.rs

1use std::sync::Arc;
2
3use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
4use alloy_primitives::{Address, B256, U256};
5use alloy_sol_types::{SolEvent, SolInterface};
6use arb_context::ArbPrecompileCtx;
7use arb_primitives::multigas::NUM_RESOURCE_KIND;
8use arb_storage::{
9    ARBOS_STATE_ADDRESS, STORAGE_READ_GAS, STORAGE_WRITE_GAS, STORAGE_WRITE_ZERO_GAS, write_cost,
10};
11use arbos::{
12    address_set::AddressSet,
13    programs::params::{
14        COST_SCALAR_PERCENT, MIN_CACHED_GAS_UNITS, MIN_INIT_GAS_UNITS, StylusParams,
15    },
16};
17use revm::{
18    precompile::{PrecompileId, PrecompileOutput, PrecompileResult},
19    primitives::Log,
20};
21
22use crate::{ArbPrecompileError, interfaces::IArbOwner};
23
24/// ArbOwner precompile address (0x70).
25pub const ARBOWNER_ADDRESS: Address = Address::new([
26    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
27    0x00, 0x00, 0x00, 0x70,
28]);
29
30const ARBOS_VERSION_60: u64 = 60;
31
32use arbos::l1_pricing::L1_PRICER_FUNDS_POOL_ADDRESS;
33
34const SLOAD_GAS: u64 = STORAGE_READ_GAS;
35const SSTORE_GAS: u64 = STORAGE_WRITE_GAS;
36const SSTORE_ZERO_GAS: u64 = STORAGE_WRITE_ZERO_GAS;
37const COPY_GAS: u64 = 3;
38/// StylusParams is read as a warm access, not a cold SLOAD.
39const WARM_SLOAD_GAS: u64 = 100;
40
41pub fn create_arbowner_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
42    DynPrecompile::new_stateful(PrecompileId::custom("arbowner"), move |input| {
43        handler(input, &ctx)
44    })
45}
46
47fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
48    // Access-controlled methods report zero gas at the receipt; the per-tx
49    // multi-gas accumulator must also reflect that so they cannot escalate the
50    // v60 backlog. Snapshot here and restore on every return path.
51    let mg_snapshot = ctx.snapshot_precompile_multi_gas();
52    let mut gas_used = 0u64;
53    let gas_limit = input.gas;
54    let data = input.data;
55    if data.len() < 4 {
56        ctx.restore_precompile_multi_gas(mg_snapshot);
57        return crate::burn_all_revert(gas_limit);
58    }
59    let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
60
61    if let Some(r) = crate::reject_nonpayable_value(input.value, data, gas_limit, &[]) {
62        ctx.restore_precompile_multi_gas(mg_snapshot);
63        return r;
64    }
65
66    // Only the read-only owner queries are allowed under STATICCALL/read-only;
67    // every setter reverts before the owner check.
68    if let Some(r) = crate::reject_static_unless_read(
69        input.is_static,
70        data,
71        gas_limit,
72        &[
73            [0x26, 0xef, 0x7f, 0x68], // isChainOwner(address)
74            [0x51, 0x6b, 0x4e, 0x0f], // getAllChainOwners()
75            [0xc6, 0x86, 0xf4, 0xdb], // isNativeTokenOwner(address)
76            [0x3f, 0x86, 0x01, 0xe4], // getAllNativeTokenOwners()
77            [0xb3, 0x23, 0x52, 0xc3], // isTransactionFilterer(address)
78            [0x59, 0x5f, 0xbb, 0x5a], // getAllTransactionFilterers()
79            [0x3c, 0xaa, 0x5f, 0x12], // getFilteredFundsRecipient()
80            [0x2d, 0x91, 0x25, 0xe9], // getNetworkFeeAccount()
81            [0xee, 0x95, 0xa8, 0x24], // getInfraFeeAccount()
82        ],
83    ) {
84        ctx.restore_precompile_multi_gas(mg_snapshot);
85        return r;
86    }
87
88    // No owner method is pure, so none may be reached via DELEGATECALL.
89    if let Some(r) = crate::reject_delegate_nonpure(
90        input.target_address != input.bytecode_address,
91        data,
92        gas_limit,
93        &[],
94    ) {
95        ctx.restore_precompile_multi_gas(mg_snapshot);
96        return r;
97    }
98
99    if let Err(e) = verify_owner(&mut input, &mut gas_used, ctx) {
100        ctx.restore_precompile_multi_gas(mg_snapshot);
101        return Err(e.into());
102    }
103
104    let call = match IArbOwner::ArbOwnerCalls::abi_decode(data) {
105        Ok(c) => c,
106        Err(_) => {
107            ctx.restore_precompile_multi_gas(mg_snapshot);
108            return crate::burn_all_revert(gas_limit);
109        }
110    };
111
112    gas_used = 0;
113    crate::init_precompile_gas(&mut gas_used, ctx, data.len());
114
115    use IArbOwner::ArbOwnerCalls as Calls;
116    let is_read_only = matches!(
117        call,
118        Calls::getNetworkFeeAccount(_)
119            | Calls::getInfraFeeAccount(_)
120            | Calls::isChainOwner(_)
121            | Calls::getAllChainOwners(_)
122            | Calls::isTransactionFilterer(_)
123            | Calls::getAllTransactionFilterers(_)
124            | Calls::isNativeTokenOwner(_)
125            | Calls::getAllNativeTokenOwners(_)
126            | Calls::getFilteredFundsRecipient(_)
127    );
128
129    let result = match call {
130        // Getters
131        Calls::getNetworkFeeAccount(_) => {
132            handle_get_network_fee_account(&mut input, &mut gas_used, ctx)
133        }
134        Calls::getInfraFeeAccount(_) => {
135            if let Some(r) = crate::check_method_version(ctx, gas_limit, 5, 0) {
136                return r;
137            }
138            handle_get_infra_fee_account(&mut input, &mut gas_used, ctx)
139        }
140        Calls::getFilteredFundsRecipient(_) => {
141            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
142                return r;
143            }
144            handle_get_filtered_funds_recipient(&mut input, &mut gas_used, ctx)
145        }
146        Calls::isChainOwner(_) => {
147            handle_is_member(&mut input, &mut gas_used, AddressSetKind::ChainOwners, ctx)
148        }
149        Calls::getAllChainOwners(_) => {
150            handle_get_all_members(&mut input, &mut gas_used, AddressSetKind::ChainOwners, ctx)
151        }
152        Calls::getAllTransactionFilterers(_) => {
153            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
154                return r;
155            }
156            handle_get_all_members(
157                &mut input,
158                &mut gas_used,
159                AddressSetKind::TransactionFilterers,
160                ctx,
161            )
162        }
163        Calls::getAllNativeTokenOwners(_) => {
164            if let Some(r) = crate::check_method_version(ctx, gas_limit, 41, 0) {
165                return r;
166            }
167            handle_get_all_members(
168                &mut input,
169                &mut gas_used,
170                AddressSetKind::NativeTokenOwners,
171                ctx,
172            )
173        }
174        Calls::isTransactionFilterer(_) => {
175            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
176                return r;
177            }
178            handle_is_member(
179                &mut input,
180                &mut gas_used,
181                AddressSetKind::TransactionFilterers,
182                ctx,
183            )
184        }
185        Calls::isNativeTokenOwner(_) => {
186            if let Some(r) = crate::check_method_version(ctx, gas_limit, 41, 0) {
187                return r;
188            }
189            handle_is_member(
190                &mut input,
191                &mut gas_used,
192                AddressSetKind::NativeTokenOwners,
193                ctx,
194            )
195        }
196
197        // Chain owner management
198        Calls::addChainOwner(_) => handle_add_chain_owner(&mut input, &mut gas_used, ctx),
199        Calls::removeChainOwner(_) => handle_remove_chain_owner(&mut input, &mut gas_used, ctx),
200
201        // Root state setters
202        Calls::setNetworkFeeAccount(_) => {
203            handle_set_network_fee_account(&mut input, &mut gas_used, ctx)
204        }
205        Calls::setInfraFeeAccount(_) => {
206            if let Some(r) = crate::check_method_version(ctx, gas_limit, 5, 0) {
207                return r;
208            }
209            handle_set_infra_fee_account(&mut input, &mut gas_used, ctx)
210        }
211        Calls::setBrotliCompressionLevel(_) => {
212            if let Some(r) = crate::check_method_version(ctx, gas_limit, 20, 0) {
213                return r;
214            }
215            handle_set_brotli_compression_level(&mut input, &mut gas_used, ctx)
216        }
217        Calls::scheduleArbOSUpgrade(_) => handle_schedule_upgrade(&mut input, &mut gas_used, ctx),
218
219        // L2 pricing setters
220        Calls::setSpeedLimit(_) => match data.get(4..36) {
221            None => Err(ArbPrecompileError::empty_revert(gas_used).into()),
222            Some(bytes) if U256::from_be_slice(bytes).is_zero() => {
223                Err(ArbPrecompileError::empty_revert(gas_used).into())
224            }
225            Some(_) => handle_set_speed_limit(&mut input, &mut gas_used, ctx),
226        },
227        Calls::setL2BaseFee(_) => handle_set_l2_base_fee(&mut input, &mut gas_used, ctx),
228        Calls::setMinimumL2BaseFee(_) => handle_set_min_l2_base_fee(&mut input, &mut gas_used, ctx),
229        Calls::setMaxBlockGasLimit(_) => {
230            if let Some(r) = crate::check_method_version(ctx, gas_limit, 50, 0) {
231                return r;
232            }
233            handle_set_max_block_gas_limit(&mut input, &mut gas_used, ctx)
234        }
235        Calls::setMaxTxGasLimit(_) => handle_set_max_tx_gas_limit(&mut input, &mut gas_used, ctx),
236        Calls::setL2GasPricingInertia(_) => match data.get(4..36) {
237            None => Err(ArbPrecompileError::empty_revert(gas_used).into()),
238            Some(bytes) if U256::from_be_slice(bytes).is_zero() => {
239                Err(ArbPrecompileError::empty_revert(gas_used).into())
240            }
241            Some(_) => handle_set_l2_pricing_inertia(&mut input, &mut gas_used, ctx),
242        },
243        Calls::setL2GasBacklogTolerance(_) => {
244            handle_set_l2_backlog_tolerance(&mut input, &mut gas_used, ctx)
245        }
246        Calls::setGasBacklog(_) => {
247            if let Some(r) = crate::check_method_version(ctx, gas_limit, 50, 0) {
248                return r;
249            }
250            handle_set_gas_backlog(&mut input, &mut gas_used, ctx)
251        }
252
253        // L1 pricing setters
254        Calls::setL1PricingEquilibrationUnits(_) => {
255            handle_set_l1_equilibration_units(&mut input, &mut gas_used, ctx)
256        }
257        Calls::setL1PricingInertia(_) | Calls::setL1BaseFeeEstimateInertia(_) => {
258            handle_set_l1_inertia(&mut input, &mut gas_used, ctx)
259        }
260        Calls::setL1PricingRewardRecipient(_) => {
261            handle_set_l1_pay_rewards_to(&mut input, &mut gas_used, ctx)
262        }
263        Calls::setL1PricingRewardRate(_) => {
264            handle_set_l1_per_unit_reward(&mut input, &mut gas_used, ctx)
265        }
266        Calls::setL1PricePerUnit(_) => handle_set_l1_price_per_unit(&mut input, &mut gas_used, ctx),
267        Calls::setParentGasFloorPerToken(_) => {
268            if let Some(r) = crate::check_method_version(ctx, gas_limit, 50, 0) {
269                return r;
270            }
271            handle_set_parent_gas_floor_per_token(&mut input, &mut gas_used, ctx)
272        }
273        Calls::setPerBatchGasCharge(_) => {
274            handle_set_per_batch_gas_cost(&mut input, &mut gas_used, ctx)
275        }
276        Calls::setAmortizedCostCapBips(_) => {
277            handle_set_amortized_cost_cap_bips(&mut input, &mut gas_used, ctx)
278        }
279        Calls::releaseL1PricerSurplusFunds(_) => {
280            if let Some(r) = crate::check_method_version(ctx, gas_limit, 10, 0) {
281                return r;
282            }
283            handle_release_l1_pricer_surplus_funds(&mut input, &mut gas_used, ctx)
284        }
285
286        // Stylus/Wasm parameter setters (all require ArbOS >= 30)
287        Calls::setInkPrice(_) => {
288            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
289                return r;
290            }
291            match read_u32_param(gas_used, data) {
292                Err(e) => Err(e.into()),
293                Ok(val) if val == 0 || val > 0xFF_FFFF => {
294                    Err(ArbPrecompileError::empty_revert(gas_used).into())
295                }
296                Ok(val) => {
297                    write_stylus_param(&mut input, &mut gas_used, |p| p.ink_price = val, ctx)
298                }
299            }
300        }
301        Calls::setWasmMaxStackDepth(_) => {
302            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
303                return r;
304            }
305            let val = read_u32_param(gas_used, data)?;
306            write_stylus_param(&mut input, &mut gas_used, |p| p.max_stack_depth = val, ctx)
307        }
308        Calls::setWasmFreePages(_) => {
309            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
310                return r;
311            }
312            let val = read_u16_param(gas_used, data, 0)?;
313            write_stylus_param(&mut input, &mut gas_used, |p| p.free_pages = val, ctx)
314        }
315        Calls::setWasmPageGas(_) => {
316            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
317                return r;
318            }
319            let val = read_u16_param(gas_used, data, 0)?;
320            write_stylus_param(&mut input, &mut gas_used, |p| p.page_gas = val, ctx)
321        }
322        Calls::setWasmPageLimit(_) => {
323            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
324                return r;
325            }
326            let val = read_u16_param(gas_used, data, 0)?;
327            write_stylus_param(&mut input, &mut gas_used, |p| p.page_limit = val, ctx)
328        }
329        Calls::setWasmMinInitGas(_) => {
330            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
331                return r;
332            }
333            let gas = read_u8_param(gas_used, data, 0)?;
334            let cached = read_u16_param(gas_used, data, 1)?;
335            let min_init_gas = saturating_u8(div_ceil(gas as u64, MIN_INIT_GAS_UNITS));
336            let min_cached_init_gas = saturating_u8(div_ceil(cached as u64, MIN_CACHED_GAS_UNITS));
337            write_stylus_param(
338                &mut input,
339                &mut gas_used,
340                |p| {
341                    p.min_init_gas = min_init_gas;
342                    p.min_cached_init_gas = min_cached_init_gas;
343                },
344                ctx,
345            )
346        }
347        Calls::setWasmInitCostScalar(_) => {
348            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
349                return r;
350            }
351            let percent = read_u64_param(gas_used, data)?;
352            let stored = saturating_u8(div_ceil(percent, COST_SCALAR_PERCENT));
353            write_stylus_param(
354                &mut input,
355                &mut gas_used,
356                |p| p.init_cost_scalar = stored,
357                ctx,
358            )
359        }
360        Calls::setWasmExpiryDays(_) => {
361            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
362                return r;
363            }
364            let val = read_u16_param(gas_used, data, 0)?;
365            write_stylus_param(&mut input, &mut gas_used, |p| p.expiry_days = val, ctx)
366        }
367        Calls::setWasmKeepaliveDays(_) => {
368            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
369                return r;
370            }
371            let val = read_u16_param(gas_used, data, 0)?;
372            write_stylus_param(&mut input, &mut gas_used, |p| p.keepalive_days = val, ctx)
373        }
374        Calls::setWasmBlockCacheSize(_) => {
375            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
376                return r;
377            }
378            let val = read_u16_param(gas_used, data, 0)?;
379            write_stylus_param(&mut input, &mut gas_used, |p| p.block_cache_size = val, ctx)
380        }
381        Calls::setWasmMaxSize(_) => {
382            if let Some(r) = crate::check_method_version(ctx, gas_limit, 40, 0) {
383                return r;
384            }
385            let val = read_u32_param(gas_used, data)?;
386            write_stylus_param(&mut input, &mut gas_used, |p| p.max_wasm_size = val, ctx)
387        }
388        Calls::setWasmActivationGas(_) => {
389            if let Some(r) = crate::check_method_version(
390                ctx,
391                gas_limit,
392                arb_chainspec::arbos_version::ARBOS_VERSION_59,
393                0,
394            ) {
395                return r;
396            }
397            if data.len() < 36 {
398                return crate::burn_all_revert(gas_limit);
399            }
400            let val = U256::from_be_slice(&data[4..36]);
401            handle_set_activation_gas(&mut input, &mut gas_used, val, ctx)
402        }
403        Calls::setMaxStylusContractFragments(_) => {
404            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
405                return r;
406            }
407            let val = read_u8_param(gas_used, data, 0)?;
408            write_stylus_param(
409                &mut input,
410                &mut gas_used,
411                |p| p.max_fragment_count = val,
412                ctx,
413            )
414        }
415        Calls::addWasmCacheManager(_) => {
416            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
417                return r;
418            }
419            handle_add_cache_manager(&mut input, &mut gas_used, ctx)
420        }
421        Calls::removeWasmCacheManager(_) => {
422            if let Some(r) = crate::check_method_version(ctx, gas_limit, 30, 0) {
423                return r;
424            }
425            handle_remove_cache_manager(&mut input, &mut gas_used, ctx)
426        }
427        Calls::setCalldataPriceIncrease(_) => {
428            if let Some(r) = crate::check_method_version(ctx, gas_limit, 40, 0) {
429                return r;
430            }
431            handle_set_calldata_price_increase(&mut input, &mut gas_used, ctx)
432        }
433        Calls::setCollectTips(_) => {
434            if let Some(r) = crate::check_method_version(ctx, gas_limit, ARBOS_VERSION_60, 0) {
435                return r;
436            }
437            handle_set_collect_tips(&mut input, &mut gas_used, ctx)
438        }
439
440        // Transaction filtering (all ArbOS >= 60)
441        Calls::addTransactionFilterer(_) => {
442            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
443                return r;
444            }
445            handle_add_to_set_with_feature_check(
446                &mut input,
447                &mut gas_used,
448                AddressSetKind::TransactionFilterers,
449                FeatureTimeKind::TransactionFiltering,
450                Some(IArbOwner::TransactionFiltererAdded::SIGNATURE_HASH),
451                ctx,
452            )
453        }
454        Calls::removeTransactionFilterer(_) => {
455            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
456                return r;
457            }
458            handle_remove_from_set(
459                &mut input,
460                &mut gas_used,
461                AddressSetKind::TransactionFilterers,
462                Some(IArbOwner::TransactionFiltererRemoved::SIGNATURE_HASH),
463                ctx,
464            )
465        }
466        Calls::setTransactionFilteringFrom(_) => {
467            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
468                return r;
469            }
470            handle_set_feature_time(
471                &mut input,
472                &mut gas_used,
473                FeatureTimeKind::TransactionFiltering,
474                ctx,
475            )
476        }
477        Calls::setFilteredFundsRecipient(_) => {
478            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
479                return r;
480            }
481            handle_set_filtered_funds_recipient(&mut input, &mut gas_used, ctx)
482        }
483
484        // Native token management (all ArbOS >= 41)
485        Calls::setNativeTokenManagementFrom(_) => {
486            if let Some(r) = crate::check_method_version(ctx, gas_limit, 41, 0) {
487                return r;
488            }
489            handle_set_feature_time(&mut input, &mut gas_used, FeatureTimeKind::NativeToken, ctx)
490        }
491        Calls::addNativeTokenOwner(_) => {
492            if let Some(r) = crate::check_method_version(ctx, gas_limit, 41, 0) {
493                return r;
494            }
495            handle_add_to_set_with_feature_check(
496                &mut input,
497                &mut gas_used,
498                AddressSetKind::NativeTokenOwners,
499                FeatureTimeKind::NativeToken,
500                Some(IArbOwner::NativeTokenOwnerAdded::SIGNATURE_HASH),
501                ctx,
502            )
503        }
504        Calls::removeNativeTokenOwner(_) => {
505            if let Some(r) = crate::check_method_version(ctx, gas_limit, 41, 0) {
506                return r;
507            }
508            handle_remove_from_set(
509                &mut input,
510                &mut gas_used,
511                AddressSetKind::NativeTokenOwners,
512                Some(IArbOwner::NativeTokenOwnerRemoved::SIGNATURE_HASH),
513                ctx,
514            )
515        }
516
517        // Gas pricing constraints
518        Calls::setGasPricingConstraints(_) => {
519            if let Some(r) = crate::check_method_version(ctx, gas_limit, 50, 0) {
520                return r;
521            }
522            handle_set_gas_pricing_constraints(&mut input, &mut gas_used, ctx)
523        }
524        Calls::setMultiGasPricingConstraints(_) => {
525            if let Some(r) = crate::check_method_version(ctx, gas_limit, 60, 0) {
526                return r;
527            }
528            handle_set_multi_gas_pricing_constraints(&mut input, &mut gas_used, ctx)
529        }
530
531        // Chain config (ArbOS >= 11)
532        Calls::setChainConfig(_) => {
533            if let Some(r) = crate::check_method_version(ctx, gas_limit, 11, 0) {
534                return r;
535            }
536            handle_set_chain_config(&mut input, &mut gas_used, ctx)
537        }
538    };
539
540    let result = match result {
541        Ok(output) => {
542            if output.reverted {
543                Ok(PrecompileOutput::new_reverted(0, output.bytes))
544            } else {
545                let arbos_version = ctx.block.arbos_version;
546                if !is_read_only || arbos_version < 11 {
547                    emit_owner_acts(&mut input, &selector, data);
548                }
549                Ok(PrecompileOutput::new(0, output.bytes))
550            }
551        }
552        Err(_) => Ok(PrecompileOutput::new_reverted(0, Default::default())),
553    };
554    ctx.restore_precompile_multi_gas(mg_snapshot);
555    // Over-budget reverts billing zero gas, not out-of-gas burning the forwarded amount.
556    if gas_used > gas_limit {
557        return Ok(PrecompileOutput::new_reverted(0, Default::default()));
558    }
559    result
560}
561
562// Owner verification
563
564fn verify_owner(
565    input: &mut PrecompileInput<'_>,
566    gas_used: &mut u64,
567    ctx: &ArbPrecompileCtx,
568) -> Result<(), ArbPrecompileError> {
569    let caller = input.caller;
570    load_arbos(input)?;
571
572    let internals = input.internals_mut();
573    let arb_state = ctx
574        .block
575        .arbos_state(internals)
576        .map_err(ArbPrecompileError::fatal)?;
577    let is_owner = arb_state
578        .chain_owners
579        .is_member(internals, caller)
580        .map_err(ArbPrecompileError::fatal)?;
581    crate::charge_precompile_gas(gas_used, 2 * SLOAD_GAS);
582    if !is_owner {
583        return Err(ArbPrecompileError::empty_revert(*gas_used));
584    }
585    Ok(())
586}
587
588// Storage helpers
589
590fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), ArbPrecompileError> {
591    input
592        .internals_mut()
593        .load_account(ARBOS_STATE_ADDRESS)
594        .map_err(ArbPrecompileError::fatal)?;
595    Ok(())
596}
597
598#[derive(Clone, Copy)]
599enum AddressSetKind {
600    ChainOwners,
601    NativeTokenOwners,
602    TransactionFilterers,
603}
604
605#[derive(Clone, Copy)]
606enum FeatureTimeKind {
607    NativeToken,
608    TransactionFiltering,
609}
610
611fn address_set<'a, D, B>(
612    state: &'a arbos::arbos_state::ArbosState<'_, D, B>,
613    kind: AddressSetKind,
614) -> &'a AddressSet<'a, D>
615where
616    B: arbos::burn::Burner,
617{
618    match kind {
619        AddressSetKind::ChainOwners => &state.chain_owners,
620        AddressSetKind::NativeTokenOwners => &state.native_token_owners,
621        AddressSetKind::TransactionFilterers => &state.transaction_filterers,
622    }
623}
624
625fn read_feature_time<D, B, C>(
626    state: &arbos::arbos_state::ArbosState<'_, D, B>,
627    backend: &mut C,
628    kind: FeatureTimeKind,
629) -> Result<u64, ArbPrecompileError>
630where
631    B: arbos::burn::Burner,
632    C: arb_storage::StorageBackend,
633{
634    match kind {
635        FeatureTimeKind::NativeToken => state.native_token_management_from_time(backend),
636        FeatureTimeKind::TransactionFiltering => state.transaction_filtering_from_time(backend),
637    }
638    .map_err(ArbPrecompileError::fatal)
639}
640
641fn write_feature_time<D, B, C>(
642    state: &arbos::arbos_state::ArbosState<'_, D, B>,
643    backend: &mut C,
644    kind: FeatureTimeKind,
645    value: u64,
646) -> Result<(), ArbPrecompileError>
647where
648    B: arbos::burn::Burner,
649    C: arb_storage::StorageBackend,
650{
651    match kind {
652        FeatureTimeKind::NativeToken => state.set_native_token_management_from_time(backend, value),
653        FeatureTimeKind::TransactionFiltering => {
654            state.set_transaction_filtering_from_time(backend, value)
655        }
656    }
657    .map_err(ArbPrecompileError::fatal)
658}
659
660fn field_read_output(gas_limit: u64, gas_used: u64, value: U256) -> PrecompileResult {
661    Ok(PrecompileOutput::new(
662        gas_used.min(gas_limit),
663        value.to_be_bytes::<32>().to_vec().into(),
664    ))
665}
666
667// Root field readers
668
669fn handle_get_network_fee_account(
670    input: &mut PrecompileInput<'_>,
671    gas_used: &mut u64,
672    ctx: &ArbPrecompileCtx,
673) -> PrecompileResult {
674    let gas_limit = input.gas;
675    load_arbos(input)?;
676    let internals = input.internals_mut();
677    let arb_state = ctx
678        .block
679        .arbos_state(internals)
680        .map_err(ArbPrecompileError::fatal)?;
681    let addr = arb_state
682        .network_fee_account(internals)
683        .map_err(ArbPrecompileError::fatal)?;
684    crate::charge_precompile_gas(gas_used, SLOAD_GAS + COPY_GAS);
685    field_read_output(gas_limit, *gas_used, U256::from_be_slice(addr.as_slice()))
686}
687
688fn handle_get_infra_fee_account(
689    input: &mut PrecompileInput<'_>,
690    gas_used: &mut u64,
691    ctx: &ArbPrecompileCtx,
692) -> PrecompileResult {
693    let gas_limit = input.gas;
694    load_arbos(input)?;
695    let internals = input.internals_mut();
696    let arb_state = ctx
697        .block
698        .arbos_state(internals)
699        .map_err(ArbPrecompileError::fatal)?;
700    let addr = arb_state
701        .infra_fee_account(internals)
702        .map_err(ArbPrecompileError::fatal)?;
703    crate::charge_precompile_gas(gas_used, SLOAD_GAS + COPY_GAS);
704    field_read_output(gas_limit, *gas_used, U256::from_be_slice(addr.as_slice()))
705}
706
707fn handle_get_filtered_funds_recipient(
708    input: &mut PrecompileInput<'_>,
709    gas_used: &mut u64,
710    ctx: &ArbPrecompileCtx,
711) -> PrecompileResult {
712    let gas_limit = input.gas;
713    load_arbos(input)?;
714    let internals = input.internals_mut();
715    let arb_state = ctx
716        .block
717        .arbos_state(internals)
718        .map_err(ArbPrecompileError::fatal)?;
719    let addr = arb_state
720        .filtered_funds_recipient(internals)
721        .map_err(ArbPrecompileError::fatal)?;
722    crate::charge_precompile_gas(gas_used, SLOAD_GAS + COPY_GAS);
723    field_read_output(gas_limit, *gas_used, U256::from_be_slice(addr.as_slice()))
724}
725
726// Root field setters
727
728fn handle_set_network_fee_account(
729    input: &mut PrecompileInput<'_>,
730    gas_used: &mut u64,
731    ctx: &ArbPrecompileCtx,
732) -> PrecompileResult {
733    let data = input.data;
734    if data.len() < 36 {
735        return crate::burn_all_revert(input.gas);
736    }
737    let gas_limit = input.gas;
738    let addr = Address::from_slice(&data[16..36]);
739    load_arbos(input)?;
740    let internals = input.internals_mut();
741    let arb_state = ctx
742        .block
743        .arbos_state(internals)
744        .map_err(ArbPrecompileError::fatal)?;
745    arb_state
746        .set_network_fee_account(internals, addr)
747        .map_err(ArbPrecompileError::fatal)?;
748    ctx.block
749        .state_params_dirty
750        .store(true, std::sync::atomic::Ordering::Relaxed);
751    crate::charge_precompile_gas(gas_used, write_cost(addr.is_zero()));
752    Ok(PrecompileOutput::new(
753        (*gas_used).min(gas_limit),
754        Vec::new().into(),
755    ))
756}
757
758fn handle_set_infra_fee_account(
759    input: &mut PrecompileInput<'_>,
760    gas_used: &mut u64,
761    ctx: &ArbPrecompileCtx,
762) -> PrecompileResult {
763    let data = input.data;
764    if data.len() < 36 {
765        return crate::burn_all_revert(input.gas);
766    }
767    let gas_limit = input.gas;
768    let addr = Address::from_slice(&data[16..36]);
769    load_arbos(input)?;
770    let internals = input.internals_mut();
771    let arb_state = ctx
772        .block
773        .arbos_state(internals)
774        .map_err(ArbPrecompileError::fatal)?;
775    arb_state
776        .set_infra_fee_account(internals, addr)
777        .map_err(ArbPrecompileError::fatal)?;
778    ctx.block
779        .state_params_dirty
780        .store(true, std::sync::atomic::Ordering::Relaxed);
781    crate::charge_precompile_gas(gas_used, write_cost(addr.is_zero()));
782    Ok(PrecompileOutput::new(
783        (*gas_used).min(gas_limit),
784        Vec::new().into(),
785    ))
786}
787
788fn handle_set_brotli_compression_level(
789    input: &mut PrecompileInput<'_>,
790    gas_used: &mut u64,
791    ctx: &ArbPrecompileCtx,
792) -> PrecompileResult {
793    let data = input.data;
794    if data.len() < 36 {
795        return crate::burn_all_revert(input.gas);
796    }
797    let gas_limit = input.gas;
798    let level: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
799    load_arbos(input)?;
800    let internals = input.internals_mut();
801    let arb_state = ctx
802        .block
803        .arbos_state(internals)
804        .map_err(ArbPrecompileError::fatal)?;
805    match arb_state.set_brotli_compression_level(internals, level) {
806        Ok(()) => {
807            ctx.block
808                .state_params_dirty
809                .store(true, std::sync::atomic::Ordering::Relaxed);
810        }
811        Err(arbos::arbos_state::ArbosStateError::InvalidBrotliCompressionLevel) => {
812            return Err(ArbPrecompileError::empty_revert(*gas_used).into());
813        }
814        Err(e) => return Err(ArbPrecompileError::fatal(e).into()),
815    }
816    crate::charge_precompile_gas(gas_used, write_cost(level == 0));
817    Ok(PrecompileOutput::new(
818        (*gas_used).min(gas_limit),
819        Vec::new().into(),
820    ))
821}
822
823fn handle_set_filtered_funds_recipient(
824    input: &mut PrecompileInput<'_>,
825    gas_used: &mut u64,
826    ctx: &ArbPrecompileCtx,
827) -> PrecompileResult {
828    let data = input.data;
829    if data.len() < 36 {
830        return crate::burn_all_revert(input.gas);
831    }
832    let gas_limit = input.gas;
833    let addr = Address::from_slice(&data[16..36]);
834    load_arbos(input)?;
835    let internals = input.internals_mut();
836    let arb_state = ctx
837        .block
838        .arbos_state(internals)
839        .map_err(ArbPrecompileError::fatal)?;
840    arb_state
841        .set_filtered_funds_recipient(internals, addr)
842        .map_err(ArbPrecompileError::fatal)?;
843    emit_address_event(
844        input,
845        IArbOwner::FilteredFundsRecipientSet::SIGNATURE_HASH,
846        addr,
847    );
848    crate::charge_precompile_gas(gas_used, write_cost(addr.is_zero()));
849    Ok(PrecompileOutput::new(
850        (*gas_used).min(gas_limit),
851        Vec::new().into(),
852    ))
853}
854
855fn handle_schedule_upgrade(
856    input: &mut PrecompileInput<'_>,
857    gas_used: &mut u64,
858    ctx: &ArbPrecompileCtx,
859) -> PrecompileResult {
860    let data = input.data;
861    if data.len() < 68 {
862        return crate::burn_all_revert(input.gas);
863    }
864    let gas_limit = input.gas;
865    let new_version: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
866    let timestamp: u64 = U256::from_be_slice(&data[36..68]).try_into().unwrap_or(0);
867    load_arbos(input)?;
868    let internals = input.internals_mut();
869    let arb_state = ctx
870        .block
871        .arbos_state(internals)
872        .map_err(ArbPrecompileError::fatal)?;
873    arb_state
874        .schedule_arbos_upgrade(internals, new_version, timestamp)
875        .map_err(ArbPrecompileError::fatal)?;
876    crate::charge_precompile_gas(
877        gas_used,
878        write_cost(new_version == 0) + write_cost(timestamp == 0),
879    );
880    Ok(PrecompileOutput::new(
881        (*gas_used).min(gas_limit),
882        Vec::new().into(),
883    ))
884}
885
886// L2 pricing setters
887
888fn handle_set_speed_limit(
889    input: &mut PrecompileInput<'_>,
890    gas_used: &mut u64,
891    ctx: &ArbPrecompileCtx,
892) -> PrecompileResult {
893    let data = input.data;
894    if data.len() < 36 {
895        return crate::burn_all_revert(input.gas);
896    }
897    let gas_limit = input.gas;
898    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
899    load_arbos(input)?;
900    let internals = input.internals_mut();
901    let arb_state = ctx
902        .block
903        .arbos_state(internals)
904        .map_err(ArbPrecompileError::fatal)?;
905    arb_state
906        .l2_pricing_state
907        .set_speed_limit_per_second(internals, val)
908        .map_err(ArbPrecompileError::fatal)?;
909    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
910    Ok(PrecompileOutput::new(
911        (*gas_used).min(gas_limit),
912        Vec::new().into(),
913    ))
914}
915
916fn handle_set_l2_base_fee(
917    input: &mut PrecompileInput<'_>,
918    gas_used: &mut u64,
919    ctx: &ArbPrecompileCtx,
920) -> PrecompileResult {
921    let data = input.data;
922    if data.len() < 36 {
923        return crate::burn_all_revert(input.gas);
924    }
925    let gas_limit = input.gas;
926    let val = U256::from_be_slice(&data[4..36]);
927    load_arbos(input)?;
928    let internals = input.internals_mut();
929    let arb_state = ctx
930        .block
931        .arbos_state(internals)
932        .map_err(ArbPrecompileError::fatal)?;
933    arb_state
934        .l2_pricing_state
935        .set_base_fee_wei(internals, val)
936        .map_err(ArbPrecompileError::fatal)?;
937    crate::charge_precompile_gas(gas_used, write_cost(val.is_zero()));
938    Ok(PrecompileOutput::new(
939        (*gas_used).min(gas_limit),
940        Vec::new().into(),
941    ))
942}
943
944fn handle_set_min_l2_base_fee(
945    input: &mut PrecompileInput<'_>,
946    gas_used: &mut u64,
947    ctx: &ArbPrecompileCtx,
948) -> PrecompileResult {
949    let data = input.data;
950    if data.len() < 36 {
951        return crate::burn_all_revert(input.gas);
952    }
953    let gas_limit = input.gas;
954    let val = U256::from_be_slice(&data[4..36]);
955    load_arbos(input)?;
956    let internals = input.internals_mut();
957    let arb_state = ctx
958        .block
959        .arbos_state(internals)
960        .map_err(ArbPrecompileError::fatal)?;
961    arb_state
962        .l2_pricing_state
963        .set_min_base_fee_wei(internals, val)
964        .map_err(ArbPrecompileError::fatal)?;
965    ctx.block
966        .state_params_dirty
967        .store(true, std::sync::atomic::Ordering::Relaxed);
968    crate::charge_precompile_gas(gas_used, write_cost(val.is_zero()));
969    Ok(PrecompileOutput::new(
970        (*gas_used).min(gas_limit),
971        Vec::new().into(),
972    ))
973}
974
975fn handle_set_max_block_gas_limit(
976    input: &mut PrecompileInput<'_>,
977    gas_used: &mut u64,
978    ctx: &ArbPrecompileCtx,
979) -> PrecompileResult {
980    let data = input.data;
981    if data.len() < 36 {
982        return crate::burn_all_revert(input.gas);
983    }
984    let gas_limit = input.gas;
985    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
986    load_arbos(input)?;
987    let internals = input.internals_mut();
988    let arb_state = ctx
989        .block
990        .arbos_state(internals)
991        .map_err(ArbPrecompileError::fatal)?;
992    arb_state
993        .l2_pricing_state
994        .set_max_per_block_gas_limit(internals, val)
995        .map_err(ArbPrecompileError::fatal)?;
996    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
997    Ok(PrecompileOutput::new(
998        (*gas_used).min(gas_limit),
999        Vec::new().into(),
1000    ))
1001}
1002
1003fn handle_set_max_tx_gas_limit(
1004    input: &mut PrecompileInput<'_>,
1005    gas_used: &mut u64,
1006    ctx: &ArbPrecompileCtx,
1007) -> PrecompileResult {
1008    let data = input.data;
1009    if data.len() < 36 {
1010        return crate::burn_all_revert(input.gas);
1011    }
1012    let gas_limit = input.gas;
1013    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1014    load_arbos(input)?;
1015    let internals = input.internals_mut();
1016    let arb_state = ctx
1017        .block
1018        .arbos_state(internals)
1019        .map_err(ArbPrecompileError::fatal)?;
1020    if arb_state.arbos_version() < 50 {
1021        arb_state
1022            .l2_pricing_state
1023            .set_max_per_block_gas_limit(internals, val)
1024            .map_err(ArbPrecompileError::fatal)?;
1025    } else {
1026        arb_state
1027            .l2_pricing_state
1028            .set_max_per_tx_gas_limit(internals, val)
1029            .map_err(ArbPrecompileError::fatal)?;
1030    }
1031    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1032    Ok(PrecompileOutput::new(
1033        (*gas_used).min(gas_limit),
1034        Vec::new().into(),
1035    ))
1036}
1037
1038fn handle_set_l2_pricing_inertia(
1039    input: &mut PrecompileInput<'_>,
1040    gas_used: &mut u64,
1041    ctx: &ArbPrecompileCtx,
1042) -> PrecompileResult {
1043    let data = input.data;
1044    if data.len() < 36 {
1045        return crate::burn_all_revert(input.gas);
1046    }
1047    let gas_limit = input.gas;
1048    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1049    load_arbos(input)?;
1050    let internals = input.internals_mut();
1051    let arb_state = ctx
1052        .block
1053        .arbos_state(internals)
1054        .map_err(ArbPrecompileError::fatal)?;
1055    arb_state
1056        .l2_pricing_state
1057        .set_pricing_inertia(internals, val)
1058        .map_err(ArbPrecompileError::fatal)?;
1059    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1060    Ok(PrecompileOutput::new(
1061        (*gas_used).min(gas_limit),
1062        Vec::new().into(),
1063    ))
1064}
1065
1066fn handle_set_l2_backlog_tolerance(
1067    input: &mut PrecompileInput<'_>,
1068    gas_used: &mut u64,
1069    ctx: &ArbPrecompileCtx,
1070) -> PrecompileResult {
1071    let data = input.data;
1072    if data.len() < 36 {
1073        return crate::burn_all_revert(input.gas);
1074    }
1075    let gas_limit = input.gas;
1076    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1077    load_arbos(input)?;
1078    let internals = input.internals_mut();
1079    let arb_state = ctx
1080        .block
1081        .arbos_state(internals)
1082        .map_err(ArbPrecompileError::fatal)?;
1083    arb_state
1084        .l2_pricing_state
1085        .set_backlog_tolerance(internals, val)
1086        .map_err(ArbPrecompileError::fatal)?;
1087    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1088    Ok(PrecompileOutput::new(
1089        (*gas_used).min(gas_limit),
1090        Vec::new().into(),
1091    ))
1092}
1093
1094fn handle_set_gas_backlog(
1095    input: &mut PrecompileInput<'_>,
1096    gas_used: &mut u64,
1097    ctx: &ArbPrecompileCtx,
1098) -> PrecompileResult {
1099    let data = input.data;
1100    if data.len() < 36 {
1101        return crate::burn_all_revert(input.gas);
1102    }
1103    let gas_limit = input.gas;
1104    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1105    load_arbos(input)?;
1106    let internals = input.internals_mut();
1107    let arb_state = ctx
1108        .block
1109        .arbos_state(internals)
1110        .map_err(ArbPrecompileError::fatal)?;
1111    arb_state
1112        .l2_pricing_state
1113        .set_gas_backlog(internals, val)
1114        .map_err(ArbPrecompileError::fatal)?;
1115    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1116    Ok(PrecompileOutput::new(
1117        (*gas_used).min(gas_limit),
1118        Vec::new().into(),
1119    ))
1120}
1121
1122// L1 pricing setters
1123
1124fn handle_set_l1_equilibration_units(
1125    input: &mut PrecompileInput<'_>,
1126    gas_used: &mut u64,
1127    ctx: &ArbPrecompileCtx,
1128) -> PrecompileResult {
1129    let data = input.data;
1130    if data.len() < 36 {
1131        return crate::burn_all_revert(input.gas);
1132    }
1133    let gas_limit = input.gas;
1134    let val = U256::from_be_slice(&data[4..36]);
1135    load_arbos(input)?;
1136    let internals = input.internals_mut();
1137    let arb_state = ctx
1138        .block
1139        .arbos_state(internals)
1140        .map_err(ArbPrecompileError::fatal)?;
1141    arb_state
1142        .l1_pricing_state
1143        .set_equilibration_units(internals, val)
1144        .map_err(ArbPrecompileError::fatal)?;
1145    crate::charge_precompile_gas(gas_used, write_cost(val.is_zero()));
1146    Ok(PrecompileOutput::new(
1147        (*gas_used).min(gas_limit),
1148        Vec::new().into(),
1149    ))
1150}
1151
1152fn handle_set_l1_inertia(
1153    input: &mut PrecompileInput<'_>,
1154    gas_used: &mut u64,
1155    ctx: &ArbPrecompileCtx,
1156) -> PrecompileResult {
1157    let data = input.data;
1158    if data.len() < 36 {
1159        return crate::burn_all_revert(input.gas);
1160    }
1161    let gas_limit = input.gas;
1162    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1163    load_arbos(input)?;
1164    let internals = input.internals_mut();
1165    let arb_state = ctx
1166        .block
1167        .arbos_state(internals)
1168        .map_err(ArbPrecompileError::fatal)?;
1169    arb_state
1170        .l1_pricing_state
1171        .set_inertia(internals, val)
1172        .map_err(ArbPrecompileError::fatal)?;
1173    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1174    Ok(PrecompileOutput::new(
1175        (*gas_used).min(gas_limit),
1176        Vec::new().into(),
1177    ))
1178}
1179
1180fn handle_set_l1_pay_rewards_to(
1181    input: &mut PrecompileInput<'_>,
1182    gas_used: &mut u64,
1183    ctx: &ArbPrecompileCtx,
1184) -> PrecompileResult {
1185    let data = input.data;
1186    if data.len() < 36 {
1187        return crate::burn_all_revert(input.gas);
1188    }
1189    let gas_limit = input.gas;
1190    let addr = Address::from_slice(&data[16..36]);
1191    load_arbos(input)?;
1192    let internals = input.internals_mut();
1193    let arb_state = ctx
1194        .block
1195        .arbos_state(internals)
1196        .map_err(ArbPrecompileError::fatal)?;
1197    arb_state
1198        .l1_pricing_state
1199        .set_pay_rewards_to(internals, addr)
1200        .map_err(ArbPrecompileError::fatal)?;
1201    crate::charge_precompile_gas(gas_used, write_cost(addr.is_zero()));
1202    Ok(PrecompileOutput::new(
1203        (*gas_used).min(gas_limit),
1204        Vec::new().into(),
1205    ))
1206}
1207
1208fn handle_set_l1_per_unit_reward(
1209    input: &mut PrecompileInput<'_>,
1210    gas_used: &mut u64,
1211    ctx: &ArbPrecompileCtx,
1212) -> PrecompileResult {
1213    let data = input.data;
1214    if data.len() < 36 {
1215        return crate::burn_all_revert(input.gas);
1216    }
1217    let gas_limit = input.gas;
1218    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1219    load_arbos(input)?;
1220    let internals = input.internals_mut();
1221    let arb_state = ctx
1222        .block
1223        .arbos_state(internals)
1224        .map_err(ArbPrecompileError::fatal)?;
1225    arb_state
1226        .l1_pricing_state
1227        .set_per_unit_reward(internals, val)
1228        .map_err(ArbPrecompileError::fatal)?;
1229    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1230    Ok(PrecompileOutput::new(
1231        (*gas_used).min(gas_limit),
1232        Vec::new().into(),
1233    ))
1234}
1235
1236fn handle_set_l1_price_per_unit(
1237    input: &mut PrecompileInput<'_>,
1238    gas_used: &mut u64,
1239    ctx: &ArbPrecompileCtx,
1240) -> PrecompileResult {
1241    let data = input.data;
1242    if data.len() < 36 {
1243        return crate::burn_all_revert(input.gas);
1244    }
1245    let gas_limit = input.gas;
1246    let val = U256::from_be_slice(&data[4..36]);
1247    load_arbos(input)?;
1248    let internals = input.internals_mut();
1249    let arb_state = ctx
1250        .block
1251        .arbos_state(internals)
1252        .map_err(ArbPrecompileError::fatal)?;
1253    arb_state
1254        .l1_pricing_state
1255        .set_price_per_unit(internals, val)
1256        .map_err(ArbPrecompileError::fatal)?;
1257    crate::charge_precompile_gas(gas_used, write_cost(val.is_zero()));
1258    Ok(PrecompileOutput::new(
1259        (*gas_used).min(gas_limit),
1260        Vec::new().into(),
1261    ))
1262}
1263
1264fn handle_set_parent_gas_floor_per_token(
1265    input: &mut PrecompileInput<'_>,
1266    gas_used: &mut u64,
1267    ctx: &ArbPrecompileCtx,
1268) -> PrecompileResult {
1269    let data = input.data;
1270    if data.len() < 36 {
1271        return crate::burn_all_revert(input.gas);
1272    }
1273    let gas_limit = input.gas;
1274    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1275    load_arbos(input)?;
1276    let internals = input.internals_mut();
1277    let arb_state = ctx
1278        .block
1279        .arbos_state(internals)
1280        .map_err(ArbPrecompileError::fatal)?;
1281    arb_state
1282        .l1_pricing_state
1283        .set_parent_gas_floor_per_token(internals, val)
1284        .map_err(ArbPrecompileError::fatal)?;
1285    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1286    Ok(PrecompileOutput::new(
1287        (*gas_used).min(gas_limit),
1288        Vec::new().into(),
1289    ))
1290}
1291
1292fn handle_set_per_batch_gas_cost(
1293    input: &mut PrecompileInput<'_>,
1294    gas_used: &mut u64,
1295    ctx: &ArbPrecompileCtx,
1296) -> PrecompileResult {
1297    let data = input.data;
1298    if data.len() < 36 {
1299        return crate::burn_all_revert(input.gas);
1300    }
1301    let gas_limit = input.gas;
1302    let raw = U256::from_be_slice(&data[4..36]);
1303    // setPerBatchGasCharge accepts an int64; reinterpret the low 64 bits as signed.
1304    let val_u64: u64 = raw.try_into().unwrap_or(u64::MAX);
1305    let val_i64 = val_u64 as i64;
1306    load_arbos(input)?;
1307    let internals = input.internals_mut();
1308    let arb_state = ctx
1309        .block
1310        .arbos_state(internals)
1311        .map_err(ArbPrecompileError::fatal)?;
1312    arb_state
1313        .l1_pricing_state
1314        .set_per_batch_gas_cost(internals, val_i64)
1315        .map_err(ArbPrecompileError::fatal)?;
1316    crate::charge_precompile_gas(gas_used, write_cost(val_i64 == 0));
1317    Ok(PrecompileOutput::new(
1318        (*gas_used).min(gas_limit),
1319        Vec::new().into(),
1320    ))
1321}
1322
1323fn handle_set_amortized_cost_cap_bips(
1324    input: &mut PrecompileInput<'_>,
1325    gas_used: &mut u64,
1326    ctx: &ArbPrecompileCtx,
1327) -> PrecompileResult {
1328    let data = input.data;
1329    if data.len() < 36 {
1330        return crate::burn_all_revert(input.gas);
1331    }
1332    let gas_limit = input.gas;
1333    let val: u64 = U256::from_be_slice(&data[4..36]).try_into().unwrap_or(0);
1334    load_arbos(input)?;
1335    let internals = input.internals_mut();
1336    let arb_state = ctx
1337        .block
1338        .arbos_state(internals)
1339        .map_err(ArbPrecompileError::fatal)?;
1340    arb_state
1341        .l1_pricing_state
1342        .set_amortized_cost_cap_bips(internals, val)
1343        .map_err(ArbPrecompileError::fatal)?;
1344    crate::charge_precompile_gas(gas_used, write_cost(val == 0));
1345    Ok(PrecompileOutput::new(
1346        (*gas_used).min(gas_limit),
1347        Vec::new().into(),
1348    ))
1349}
1350
1351// AddressSet handlers
1352
1353fn handle_is_member(
1354    input: &mut PrecompileInput<'_>,
1355    gas_used: &mut u64,
1356    kind: AddressSetKind,
1357    ctx: &ArbPrecompileCtx,
1358) -> PrecompileResult {
1359    let data = input.data;
1360    if data.len() < 36 {
1361        return crate::burn_all_revert(input.gas);
1362    }
1363    let gas_limit = input.gas;
1364    let addr = Address::from_slice(&data[16..36]);
1365    load_arbos(input)?;
1366    let internals = input.internals_mut();
1367    let arb_state = ctx
1368        .block
1369        .arbos_state(internals)
1370        .map_err(ArbPrecompileError::fatal)?;
1371    let is_member = address_set(arb_state, kind)
1372        .is_member(internals, addr)
1373        .map_err(ArbPrecompileError::fatal)?;
1374    let result = if is_member {
1375        U256::from(1u64)
1376    } else {
1377        U256::ZERO
1378    };
1379    crate::charge_precompile_gas(gas_used, SLOAD_GAS + COPY_GAS);
1380    Ok(PrecompileOutput::new(
1381        (*gas_used).min(gas_limit),
1382        result.to_be_bytes::<32>().to_vec().into(),
1383    ))
1384}
1385
1386fn handle_get_all_members(
1387    input: &mut PrecompileInput<'_>,
1388    gas_used: &mut u64,
1389    kind: AddressSetKind,
1390    ctx: &ArbPrecompileCtx,
1391) -> PrecompileResult {
1392    let gas_limit = input.gas;
1393    const MAX_MEMBERS: u64 = 65_536;
1394    load_arbos(input)?;
1395    let internals = input.internals_mut();
1396    let arb_state = ctx
1397        .block
1398        .arbos_state(internals)
1399        .map_err(ArbPrecompileError::fatal)?;
1400    let members = address_set(arb_state, kind)
1401        .all_members(internals, MAX_MEMBERS)
1402        .map_err(ArbPrecompileError::fatal)?;
1403    let count = members.len() as u64;
1404
1405    let mut out = Vec::with_capacity(64 + 32 * members.len());
1406    out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
1407    out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
1408    for member in &members {
1409        let mut word = [0u8; 32];
1410        word[12..32].copy_from_slice(member.as_slice());
1411        out.extend_from_slice(&word);
1412    }
1413
1414    let extra = (1 + count) * SLOAD_GAS + COPY_GAS;
1415    crate::charge_precompile_gas(gas_used, extra);
1416    Ok(PrecompileOutput::new(
1417        (*gas_used).min(gas_limit),
1418        out.into(),
1419    ))
1420}
1421
1422fn handle_add_chain_owner(
1423    input: &mut PrecompileInput<'_>,
1424    gas_used: &mut u64,
1425    ctx: &ArbPrecompileCtx,
1426) -> PrecompileResult {
1427    let data = input.data;
1428    if data.len() < 36 {
1429        return crate::burn_all_revert(input.gas);
1430    }
1431    let gas_limit = input.gas;
1432    let addr = Address::from_slice(&data[16..36]);
1433    load_arbos(input)?;
1434
1435    let internals = input.internals_mut();
1436    let arb_state = ctx
1437        .block
1438        .arbos_state(internals)
1439        .map_err(ArbPrecompileError::fatal)?;
1440    let arbos_version = arb_state.arbos_version();
1441    arb_state
1442        .chain_owners
1443        .add(internals, addr)
1444        .map_err(ArbPrecompileError::fatal)?;
1445    if arbos_version >= 60 {
1446        emit_address_event(input, IArbOwner::ChainOwnerAdded::SIGNATURE_HASH, addr);
1447    }
1448
1449    crate::charge_precompile_gas(
1450        gas_used,
1451        3 * SLOAD_GAS + 2 * SSTORE_GAS + write_cost(addr.is_zero()),
1452    );
1453    Ok(PrecompileOutput::new(
1454        (*gas_used).min(gas_limit),
1455        Vec::new().into(),
1456    ))
1457}
1458
1459fn handle_remove_chain_owner(
1460    input: &mut PrecompileInput<'_>,
1461    gas_used: &mut u64,
1462    ctx: &ArbPrecompileCtx,
1463) -> PrecompileResult {
1464    let data = input.data;
1465    if data.len() < 36 {
1466        return crate::burn_all_revert(input.gas);
1467    }
1468    let gas_limit = input.gas;
1469    let addr = Address::from_slice(&data[16..36]);
1470    load_arbos(input)?;
1471
1472    let internals = input.internals_mut();
1473    let arb_state = ctx
1474        .block
1475        .arbos_state(internals)
1476        .map_err(ArbPrecompileError::fatal)?;
1477    let arbos_version = arb_state.arbos_version();
1478    if !arb_state
1479        .chain_owners
1480        .is_member(internals, addr)
1481        .map_err(ArbPrecompileError::fatal)?
1482    {
1483        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1484    }
1485    crate::charge_precompile_gas(gas_used, SLOAD_GAS);
1486    arb_state
1487        .chain_owners
1488        .remove(internals, addr, arbos_version, gas_used)
1489        .map_err(ArbPrecompileError::fatal)?;
1490    if arbos_version >= 60 {
1491        emit_address_event(input, IArbOwner::ChainOwnerRemoved::SIGNATURE_HASH, addr);
1492    }
1493
1494    Ok(PrecompileOutput::new(
1495        (*gas_used).min(gas_limit),
1496        Vec::new().into(),
1497    ))
1498}
1499
1500/// surplus = pool_balance - recognized_fees; capped by maxWeiToRelease.
1501/// Adds the released amount to L1FeesAvailable rather than zeroing it.
1502fn handle_release_l1_pricer_surplus_funds(
1503    input: &mut PrecompileInput<'_>,
1504    gas_used: &mut u64,
1505    ctx: &ArbPrecompileCtx,
1506) -> PrecompileResult {
1507    let data = input.data;
1508    if data.len() < 36 {
1509        return crate::burn_all_revert(input.gas);
1510    }
1511    let gas_limit = input.gas;
1512    let max_wei = U256::from_be_slice(&data[4..36]);
1513
1514    let pool_balance = {
1515        let acct = input
1516            .internals_mut()
1517            .load_account(L1_PRICER_FUNDS_POOL_ADDRESS)
1518            .map_err(ArbPrecompileError::fatal)?;
1519        acct.data.info.balance
1520    };
1521
1522    load_arbos(input)?;
1523    let internals = input.internals_mut();
1524    let arb_state = ctx
1525        .block
1526        .arbos_state(internals)
1527        .map_err(ArbPrecompileError::fatal)?;
1528    let recognized = arb_state
1529        .l1_pricing_state
1530        .l1_fees_available(internals)
1531        .map_err(ArbPrecompileError::fatal)?;
1532
1533    if pool_balance <= recognized {
1534        crate::charge_precompile_gas(gas_used, SLOAD_GAS + COPY_GAS + 100);
1535        return Ok(PrecompileOutput::new(
1536            (*gas_used).min(gas_limit),
1537            U256::ZERO.to_be_bytes::<32>().to_vec().into(),
1538        ));
1539    }
1540
1541    let mut wei_to_transfer = pool_balance - recognized;
1542    if wei_to_transfer > max_wei {
1543        wei_to_transfer = max_wei;
1544    }
1545
1546    let new_available = recognized + wei_to_transfer;
1547    arb_state
1548        .l1_pricing_state
1549        .set_l1_fees_available(internals, new_available)
1550        .map_err(ArbPrecompileError::fatal)?;
1551
1552    crate::charge_precompile_gas(gas_used, SLOAD_GAS + SSTORE_GAS + COPY_GAS + 100);
1553    Ok(PrecompileOutput::new(
1554        (*gas_used).min(gas_limit),
1555        wei_to_transfer.to_be_bytes::<32>().to_vec().into(),
1556    ))
1557}
1558
1559// Stylus parameter setters
1560
1561fn write_stylus_param(
1562    input: &mut PrecompileInput<'_>,
1563    gas_used: &mut u64,
1564    mutate: impl FnOnce(&mut StylusParams),
1565    ctx: &ArbPrecompileCtx,
1566) -> PrecompileResult {
1567    let gas_limit = input.gas;
1568    load_arbos(input)?;
1569    let internals = input.internals_mut();
1570    let arb_state = ctx
1571        .block
1572        .arbos_state(internals)
1573        .map_err(ArbPrecompileError::fatal)?;
1574    let mut params = arb_state
1575        .programs
1576        .params(internals)
1577        .map_err(ArbPrecompileError::fatal)?;
1578    mutate(&mut params);
1579    arb_state
1580        .programs
1581        .save_params(internals, &params)
1582        .map_err(ArbPrecompileError::fatal)?;
1583    crate::charge_precompile_gas(gas_used, WARM_SLOAD_GAS + SSTORE_GAS);
1584    Ok(PrecompileOutput::new(
1585        (*gas_used).min(gas_limit),
1586        Vec::new().into(),
1587    ))
1588}
1589
1590fn handle_set_activation_gas(
1591    input: &mut PrecompileInput<'_>,
1592    gas_used: &mut u64,
1593    value: U256,
1594    ctx: &ArbPrecompileCtx,
1595) -> PrecompileResult {
1596    let gas_limit = input.gas;
1597    load_arbos(input)?;
1598    let internals = input.internals_mut();
1599    let arb_state = ctx
1600        .block
1601        .arbos_state(internals)
1602        .map_err(ArbPrecompileError::fatal)?;
1603    let value_u64: u64 = value.try_into().unwrap_or(u64::MAX);
1604    arb_state
1605        .programs
1606        .set_activation_gas(internals, value_u64)
1607        .map_err(ArbPrecompileError::fatal)?;
1608    crate::charge_precompile_gas(gas_used, write_cost(value_u64 == 0));
1609    Ok(PrecompileOutput::new(
1610        (*gas_used).min(gas_limit),
1611        Vec::new().into(),
1612    ))
1613}
1614
1615// The integer readers reject any word that exceeds the declared Solidity
1616// width, matching the ABI decoder bound: a too-large word reverts rather than
1617// silently truncating.
1618
1619fn arg_word(data: &[u8], index: usize, gas_used: u64) -> Result<U256, ArbPrecompileError> {
1620    let start = 4 + index * 32;
1621    data.get(start..start + 32)
1622        .map(U256::from_be_slice)
1623        .ok_or_else(|| ArbPrecompileError::empty_revert(gas_used))
1624}
1625
1626fn read_u8_param(gas_used: u64, data: &[u8], index: usize) -> Result<u8, ArbPrecompileError> {
1627    arg_word(data, index, gas_used)?
1628        .try_into()
1629        .map_err(|_| ArbPrecompileError::empty_revert(gas_used))
1630}
1631
1632fn read_u16_param(gas_used: u64, data: &[u8], index: usize) -> Result<u16, ArbPrecompileError> {
1633    arg_word(data, index, gas_used)?
1634        .try_into()
1635        .map_err(|_| ArbPrecompileError::empty_revert(gas_used))
1636}
1637
1638fn read_u32_param(gas_used: u64, data: &[u8]) -> Result<u32, ArbPrecompileError> {
1639    arg_word(data, 0, gas_used)?
1640        .try_into()
1641        .map_err(|_| ArbPrecompileError::empty_revert(gas_used))
1642}
1643
1644fn read_u64_param(gas_used: u64, data: &[u8]) -> Result<u64, ArbPrecompileError> {
1645    arg_word(data, 0, gas_used)?
1646        .try_into()
1647        .map_err(|_| ArbPrecompileError::empty_revert(gas_used))
1648}
1649
1650/// Ceiling division; the addend saturates so a near-max input cannot wrap.
1651fn div_ceil(value: u64, divisor: u64) -> u64 {
1652    value.saturating_add(divisor - 1) / divisor
1653}
1654
1655/// Narrows to u8, clamping to u8::MAX instead of truncating.
1656fn saturating_u8(value: u64) -> u8 {
1657    value.min(u8::MAX as u64) as u8
1658}
1659
1660// Cache manager helpers
1661
1662fn handle_add_cache_manager(
1663    input: &mut PrecompileInput<'_>,
1664    gas_used: &mut u64,
1665    ctx: &ArbPrecompileCtx,
1666) -> PrecompileResult {
1667    let data = input.data;
1668    if data.len() < 36 {
1669        return crate::burn_all_revert(input.gas);
1670    }
1671    let gas_limit = input.gas;
1672    let addr = Address::from_slice(&data[16..36]);
1673    load_arbos(input)?;
1674    let internals = input.internals_mut();
1675    let arb_state = ctx
1676        .block
1677        .arbos_state(internals)
1678        .map_err(ArbPrecompileError::fatal)?;
1679    arb_state
1680        .programs
1681        .cache_managers
1682        .add(internals, addr)
1683        .map_err(ArbPrecompileError::fatal)?;
1684    crate::charge_precompile_gas(
1685        gas_used,
1686        3 * SLOAD_GAS + 2 * SSTORE_GAS + write_cost(addr.is_zero()),
1687    );
1688    Ok(PrecompileOutput::new(
1689        (*gas_used).min(gas_limit),
1690        Vec::new().into(),
1691    ))
1692}
1693
1694fn handle_remove_cache_manager(
1695    input: &mut PrecompileInput<'_>,
1696    gas_used: &mut u64,
1697    ctx: &ArbPrecompileCtx,
1698) -> PrecompileResult {
1699    let data = input.data;
1700    if data.len() < 36 {
1701        return crate::burn_all_revert(input.gas);
1702    }
1703    let gas_limit = input.gas;
1704    let addr = Address::from_slice(&data[16..36]);
1705    load_arbos(input)?;
1706    let internals = input.internals_mut();
1707    let arb_state = ctx
1708        .block
1709        .arbos_state(internals)
1710        .map_err(ArbPrecompileError::fatal)?;
1711    let arbos_version = arb_state.arbos_version();
1712    if !arb_state
1713        .programs
1714        .cache_managers
1715        .is_member(internals, addr)
1716        .map_err(ArbPrecompileError::fatal)?
1717    {
1718        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1719    }
1720    crate::charge_precompile_gas(gas_used, SLOAD_GAS);
1721    arb_state
1722        .programs
1723        .cache_managers
1724        .remove(internals, addr, arbos_version, gas_used)
1725        .map_err(ArbPrecompileError::fatal)?;
1726    Ok(PrecompileOutput::new(
1727        (*gas_used).min(gas_limit),
1728        Vec::new().into(),
1729    ))
1730}
1731
1732/// One week in seconds.
1733const FEATURE_ENABLE_DELAY: u64 = 7 * 24 * 60 * 60;
1734
1735fn handle_set_feature_time(
1736    input: &mut PrecompileInput<'_>,
1737    gas_used: &mut u64,
1738    kind: FeatureTimeKind,
1739    ctx: &ArbPrecompileCtx,
1740) -> PrecompileResult {
1741    let data = input.data;
1742    if data.len() < 36 {
1743        return crate::burn_all_revert(input.gas);
1744    }
1745    let gas_limit = input.gas;
1746    let timestamp: u64 = U256::from_be_slice(&data[4..36])
1747        .try_into()
1748        .map_err(|_| ArbPrecompileError::empty_revert(*gas_used))?;
1749
1750    load_arbos(input)?;
1751    let now: u64 = input
1752        .internals_mut()
1753        .block_timestamp()
1754        .try_into()
1755        .unwrap_or(0u64);
1756    let internals = input.internals_mut();
1757    let arb_state = ctx
1758        .block
1759        .arbos_state(internals)
1760        .map_err(ArbPrecompileError::fatal)?;
1761
1762    if timestamp == 0 {
1763        write_feature_time(arb_state, internals, kind, 0)?;
1764        crate::charge_precompile_gas(gas_used, write_cost(true));
1765        return Ok(PrecompileOutput::new(
1766            (*gas_used).min(gas_limit),
1767            Vec::new().into(),
1768        ));
1769    }
1770
1771    let stored = read_feature_time(arb_state, internals, kind)?;
1772
1773    if (stored > now + FEATURE_ENABLE_DELAY || stored == 0)
1774        && timestamp < now + FEATURE_ENABLE_DELAY
1775    {
1776        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1777    }
1778    if stored > now && stored <= now + FEATURE_ENABLE_DELAY && timestamp < stored {
1779        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1780    }
1781
1782    write_feature_time(arb_state, internals, kind, timestamp)?;
1783    crate::charge_precompile_gas(gas_used, SLOAD_GAS + SSTORE_GAS);
1784    Ok(PrecompileOutput::new(
1785        (*gas_used).min(gas_limit),
1786        Vec::new().into(),
1787    ))
1788}
1789
1790fn emit_address_event(input: &mut PrecompileInput<'_>, topic0: B256, addr: Address) {
1791    let topic1 = B256::left_padding_from(addr.as_slice());
1792    input.internals_mut().log(Log::new_unchecked(
1793        ARBOWNER_ADDRESS,
1794        vec![topic0, topic1],
1795        alloy_primitives::Bytes::new(),
1796    ));
1797}
1798
1799fn handle_add_to_set_with_feature_check(
1800    input: &mut PrecompileInput<'_>,
1801    gas_used: &mut u64,
1802    set_kind: AddressSetKind,
1803    feature_kind: FeatureTimeKind,
1804    event_topic: Option<B256>,
1805    ctx: &ArbPrecompileCtx,
1806) -> PrecompileResult {
1807    let data = input.data;
1808    if data.len() < 36 {
1809        return crate::burn_all_revert(input.gas);
1810    }
1811    let gas_limit = input.gas;
1812    let addr = Address::from_slice(&data[16..36]);
1813    load_arbos(input)?;
1814    let now: u64 = input
1815        .internals_mut()
1816        .block_timestamp()
1817        .try_into()
1818        .unwrap_or(0u64);
1819    let internals = input.internals_mut();
1820    let arb_state = ctx
1821        .block
1822        .arbos_state(internals)
1823        .map_err(ArbPrecompileError::fatal)?;
1824
1825    let enabled_time = read_feature_time(arb_state, internals, feature_kind)?;
1826    if enabled_time == 0 || enabled_time > now {
1827        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1828    }
1829
1830    address_set(arb_state, set_kind)
1831        .add(internals, addr)
1832        .map_err(ArbPrecompileError::fatal)?;
1833
1834    if let Some(topic0) = event_topic {
1835        emit_address_event(input, topic0, addr);
1836    }
1837
1838    // feature-time read + Add's 3 reads/3 writes.
1839    crate::charge_precompile_gas(
1840        gas_used,
1841        4 * SLOAD_GAS + 2 * SSTORE_GAS + write_cost(addr.is_zero()),
1842    );
1843    Ok(PrecompileOutput::new(
1844        (*gas_used).min(gas_limit),
1845        Vec::new().into(),
1846    ))
1847}
1848
1849fn handle_remove_from_set(
1850    input: &mut PrecompileInput<'_>,
1851    gas_used: &mut u64,
1852    set_kind: AddressSetKind,
1853    event_topic: Option<B256>,
1854    ctx: &ArbPrecompileCtx,
1855) -> PrecompileResult {
1856    let data = input.data;
1857    if data.len() < 36 {
1858        return crate::burn_all_revert(input.gas);
1859    }
1860    let gas_limit = input.gas;
1861    let addr = Address::from_slice(&data[16..36]);
1862    load_arbos(input)?;
1863
1864    let internals = input.internals_mut();
1865    let arb_state = ctx
1866        .block
1867        .arbos_state(internals)
1868        .map_err(ArbPrecompileError::fatal)?;
1869    let arbos_version = arb_state.arbos_version();
1870
1871    let set = address_set(arb_state, set_kind);
1872    if !set
1873        .is_member(internals, addr)
1874        .map_err(ArbPrecompileError::fatal)?
1875    {
1876        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1877    }
1878    crate::charge_precompile_gas(gas_used, SLOAD_GAS);
1879    set.remove(internals, addr, arbos_version, gas_used)
1880        .map_err(ArbPrecompileError::fatal)?;
1881
1882    if let Some(topic0) = event_topic {
1883        emit_address_event(input, topic0, addr);
1884    }
1885
1886    Ok(PrecompileOutput::new(
1887        (*gas_used).min(gas_limit),
1888        Vec::new().into(),
1889    ))
1890}
1891
1892// Gas constraint helpers
1893
1894const GAS_CONSTRAINTS_MAX_NUM: usize = 20;
1895const MAX_PRICING_EXPONENT_BIPS: u64 = 85_000;
1896
1897fn handle_set_gas_pricing_constraints(
1898    input: &mut PrecompileInput<'_>,
1899    gas_used: &mut u64,
1900    ctx: &ArbPrecompileCtx,
1901) -> PrecompileResult {
1902    let data = input.data;
1903    // Minimum: selector(4) + offset(32) + length(32) = 68 bytes
1904    if data.len() < 68 {
1905        return crate::burn_all_revert(input.gas);
1906    }
1907    let gas_limit = input.gas;
1908
1909    let count: u64 = U256::from_be_slice(&data[36..68])
1910        .try_into()
1911        .map_err(|_| ArbPrecompileError::empty_revert(*gas_used))?;
1912
1913    let expected_len = 68 + (count as usize) * 96;
1914    if data.len() < expected_len {
1915        return crate::burn_all_revert(gas_limit);
1916    }
1917
1918    load_arbos(input)?;
1919    let internals = input.internals_mut();
1920    let arb_state = ctx
1921        .block
1922        .arbos_state(internals)
1923        .map_err(ArbPrecompileError::fatal)?;
1924
1925    arb_state
1926        .l2_pricing_state
1927        .clear_gas_constraints(internals)
1928        .map_err(ArbPrecompileError::fatal)?;
1929
1930    let arbos_version = arb_state.arbos_version();
1931    use arb_chainspec::arbos_version as arb_ver;
1932    if (arb_ver::ARBOS_VERSION_MULTI_CONSTRAINT_FIX..arb_ver::ARBOS_VERSION_MULTI_GAS_CONSTRAINTS)
1933        .contains(&arbos_version)
1934        && (count as usize) > GAS_CONSTRAINTS_MAX_NUM
1935    {
1936        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1937    }
1938
1939    for i in 0..count {
1940        let base = 68 + (i as usize) * 96;
1941        let target: u64 = U256::from_be_slice(&data[base..base + 32])
1942            .try_into()
1943            .unwrap_or(0);
1944        let window: u64 = U256::from_be_slice(&data[base + 32..base + 64])
1945            .try_into()
1946            .unwrap_or(0);
1947        let backlog: u64 = U256::from_be_slice(&data[base + 64..base + 96])
1948            .try_into()
1949            .unwrap_or(0);
1950
1951        if target == 0 || window == 0 {
1952            return Err(ArbPrecompileError::empty_revert(*gas_used).into());
1953        }
1954
1955        arb_state
1956            .l2_pricing_state
1957            .add_gas_constraint(internals, target, window, backlog)
1958            .map_err(ArbPrecompileError::fatal)?;
1959    }
1960
1961    // The constraint storage is written through the system burner, so it costs
1962    // no EVM gas to the transaction, matching `setMultiGasPricingConstraints`.
1963    Ok(PrecompileOutput::new(
1964        (*gas_used).min(gas_limit),
1965        Vec::new().into(),
1966    ))
1967}
1968
1969/// ABI: `setMultiGasPricingConstraints(((uint8,uint64)[],uint32,uint64,uint64)[])`.
1970/// Each struct has head layout: [resources offset, window_secs, target, backlog].
1971fn handle_set_multi_gas_pricing_constraints(
1972    input: &mut PrecompileInput<'_>,
1973    gas_used: &mut u64,
1974    ctx: &ArbPrecompileCtx,
1975) -> PrecompileResult {
1976    let data = input.data;
1977    if data.len() < 68 {
1978        return crate::burn_all_revert(input.gas);
1979    }
1980    let gas_limit = input.gas;
1981
1982    let _outer_offset: usize = U256::from_be_slice(&data[4..36])
1983        .try_into()
1984        .unwrap_or(0usize);
1985    let count: u64 = U256::from_be_slice(&data[36..68])
1986        .try_into()
1987        .map_err(|_| ArbPrecompileError::empty_revert(*gas_used))?;
1988
1989    let array_data_start = 68;
1990
1991    let mut struct_offsets = Vec::with_capacity(count as usize);
1992    for i in 0..count as usize {
1993        let offset_pos = array_data_start + i * 32;
1994        if data.len() < offset_pos + 32 {
1995            return crate::burn_all_revert(gas_limit);
1996        }
1997        let offset: usize = U256::from_be_slice(&data[offset_pos..offset_pos + 32])
1998            .try_into()
1999            .unwrap_or(0);
2000        struct_offsets.push(array_data_start + offset);
2001    }
2002
2003    load_arbos(input)?;
2004    let internals = input.internals_mut();
2005    let arb_state = ctx
2006        .block
2007        .arbos_state(internals)
2008        .map_err(ArbPrecompileError::fatal)?;
2009
2010    arb_state
2011        .l2_pricing_state
2012        .clear_multi_gas_constraints(internals)
2013        .map_err(ArbPrecompileError::fatal)?;
2014
2015    for (i, &struct_start) in struct_offsets.iter().enumerate() {
2016        if data.len() < struct_start + 128 {
2017            return crate::burn_all_revert(gas_limit);
2018        }
2019
2020        let resources_offset: usize = U256::from_be_slice(&data[struct_start..struct_start + 32])
2021            .try_into()
2022            .unwrap_or(0);
2023        let window: u32 = U256::from_be_slice(&data[struct_start + 32..struct_start + 64])
2024            .try_into()
2025            .unwrap_or(0);
2026        let target: u64 = U256::from_be_slice(&data[struct_start + 64..struct_start + 96])
2027            .try_into()
2028            .unwrap_or(0);
2029        let backlog: u64 = U256::from_be_slice(&data[struct_start + 96..struct_start + 128])
2030            .try_into()
2031            .unwrap_or(0);
2032
2033        if target == 0 || window == 0 {
2034            return Err(ArbPrecompileError::empty_revert(*gas_used).into());
2035        }
2036        let resources_start = struct_start + resources_offset;
2037
2038        if data.len() < resources_start + 32 {
2039            return crate::burn_all_revert(gas_limit);
2040        }
2041
2042        let num_resources: usize =
2043            U256::from_be_slice(&data[resources_start..resources_start + 32])
2044                .try_into()
2045                .unwrap_or(0);
2046
2047        let mut weights = [0u64; NUM_RESOURCE_KIND];
2048        for r in 0..num_resources {
2049            let r_start = resources_start + 32 + r * 64;
2050            if data.len() < r_start + 64 {
2051                return crate::burn_all_revert(gas_limit);
2052            }
2053            let resource: u8 = U256::from_be_slice(&data[r_start..r_start + 32])
2054                .try_into()
2055                .unwrap_or(0);
2056            let weight: u64 = U256::from_be_slice(&data[r_start + 32..r_start + 64])
2057                .try_into()
2058                .unwrap_or(0);
2059
2060            if !arb_primitives::multigas::ResourceKind::is_valid_id(resource) {
2061                return Err(ArbPrecompileError::empty_revert(*gas_used).into());
2062            }
2063            weights[resource as usize] = weight;
2064        }
2065
2066        arb_state
2067            .l2_pricing_state
2068            .add_multi_gas_constraint(internals, target, window, backlog, &weights)
2069            .map_err(ArbPrecompileError::fatal)?;
2070
2071        validate_multi_gas_exponents(internals, arb_state, (i as u64) + 1, *gas_used)?;
2072    }
2073
2074    // The constraint storage is written through the system burner, so it costs
2075    // no EVM gas to the transaction.
2076    Ok(PrecompileOutput::new(
2077        (*gas_used).min(gas_limit),
2078        Vec::new().into(),
2079    ))
2080}
2081
2082fn validate_multi_gas_exponents<D, B, C>(
2083    backend: &mut C,
2084    arb_state: &arbos::arbos_state::ArbosState<'_, D, B>,
2085    count: u64,
2086    gas_used: u64,
2087) -> Result<(), ArbPrecompileError>
2088where
2089    B: arbos::burn::Burner,
2090    C: arb_storage::StorageBackend,
2091{
2092    use arb_primitives::multigas::ResourceKind;
2093    let mut exponents = [0u64; NUM_RESOURCE_KIND];
2094
2095    for i in 0..count {
2096        let constraint = arb_state.l2_pricing_state.open_multi_gas_constraint_at(i);
2097        let target = constraint
2098            .target(backend)
2099            .map_err(ArbPrecompileError::fatal)?;
2100        let backlog = constraint
2101            .backlog(backend)
2102            .map_err(ArbPrecompileError::fatal)?;
2103
2104        if backlog == 0 {
2105            continue;
2106        }
2107
2108        let window = constraint
2109            .adjustment_window(backend)
2110            .map_err(ArbPrecompileError::fatal)? as u64;
2111        let max_weight = constraint
2112            .max_weight(backend)
2113            .map_err(ArbPrecompileError::fatal)?;
2114
2115        if max_weight == 0 || target == 0 || window == 0 {
2116            continue;
2117        }
2118
2119        let divisor = (window as u128)
2120            .saturating_mul(target as u128)
2121            .saturating_mul(max_weight as u128);
2122
2123        for (r, exponent) in exponents.iter_mut().enumerate().take(NUM_RESOURCE_KIND) {
2124            let kind = ResourceKind::ALL[r];
2125            if kind == ResourceKind::SingleDim {
2126                continue;
2127            }
2128            let weight = constraint
2129                .resource_weight(backend, kind)
2130                .map_err(ArbPrecompileError::fatal)?;
2131            if weight == 0 {
2132                continue;
2133            }
2134
2135            let dividend = (backlog as u128)
2136                .saturating_mul(weight as u128)
2137                .saturating_mul(10_000);
2138            let exp = dividend.checked_div(divisor).unwrap_or(0) as u64;
2139            *exponent = exponent.saturating_add(exp);
2140        }
2141    }
2142
2143    for &exp in &exponents {
2144        if exp > MAX_PRICING_EXPONENT_BIPS {
2145            return Err(ArbPrecompileError::empty_revert(gas_used));
2146        }
2147    }
2148
2149    Ok(())
2150}
2151
2152// SetChainConfig
2153
2154fn handle_set_chain_config(
2155    input: &mut PrecompileInput<'_>,
2156    gas_used: &mut u64,
2157    ctx: &ArbPrecompileCtx,
2158) -> PrecompileResult {
2159    let data = input.data;
2160    if data.len() < 68 {
2161        return crate::burn_all_revert(input.gas);
2162    }
2163    let gas_limit = input.gas;
2164
2165    let bytes_len: usize = U256::from_be_slice(&data[36..68])
2166        .try_into()
2167        .map_err(|_| ArbPrecompileError::empty_revert(*gas_used))?;
2168
2169    if data.len() < 68 + bytes_len {
2170        return crate::burn_all_revert(gas_limit);
2171    }
2172    let config_bytes = data[68..68 + bytes_len].to_vec();
2173
2174    load_arbos(input)?;
2175    let internals = input.internals_mut();
2176    let arb_state = ctx
2177        .block
2178        .arbos_state(internals)
2179        .map_err(ArbPrecompileError::fatal)?;
2180
2181    let old_len = arb_state
2182        .chain_config(internals)
2183        .map_err(ArbPrecompileError::fatal)?
2184        .len() as u64;
2185    arb_state
2186        .set_chain_config(internals, &config_bytes)
2187        .map_err(ArbPrecompileError::fatal)?;
2188
2189    // Bytes store: clear old slots (resets), then write new length, full words, and a trailing
2190    // word.
2191    let bytes_len = bytes_len as u64;
2192    let old_data_slots = old_len.div_ceil(32);
2193    let new_full_words = bytes_len / 32;
2194    let len_write = write_cost(bytes_len == 0);
2195    let tail_write = write_cost(bytes_len.is_multiple_of(32));
2196    let extra = SLOAD_GAS
2197        + (old_data_slots + 1) * SSTORE_ZERO_GAS
2198        + len_write
2199        + new_full_words * SSTORE_GAS
2200        + tail_write;
2201    crate::charge_precompile_gas(gas_used, extra);
2202    Ok(PrecompileOutput::new(
2203        (*gas_used).min(gas_limit),
2204        Vec::new().into(),
2205    ))
2206}
2207
2208fn handle_set_calldata_price_increase(
2209    input: &mut PrecompileInput<'_>,
2210    gas_used: &mut u64,
2211    ctx: &ArbPrecompileCtx,
2212) -> PrecompileResult {
2213    let data = input.data;
2214    if data.len() < 36 {
2215        return crate::burn_all_revert(input.gas);
2216    }
2217    let gas_limit = input.gas;
2218    let enabled = U256::from_be_slice(&data[4..36]) != U256::ZERO;
2219
2220    load_arbos(input)?;
2221    let internals = input.internals_mut();
2222    let arb_state = ctx
2223        .block
2224        .arbos_state(internals)
2225        .map_err(ArbPrecompileError::fatal)?;
2226    let new_features = arb_state
2227        .features
2228        .set_calldata_price_increase(internals, enabled)
2229        .map_err(ArbPrecompileError::fatal)?;
2230    ctx.block
2231        .state_params_dirty
2232        .store(true, std::sync::atomic::Ordering::Relaxed);
2233
2234    crate::charge_precompile_gas(gas_used, SLOAD_GAS + write_cost(new_features == U256::ZERO));
2235    Ok(PrecompileOutput::new(
2236        (*gas_used).min(gas_limit),
2237        Vec::new().into(),
2238    ))
2239}
2240
2241fn handle_set_collect_tips(
2242    input: &mut PrecompileInput<'_>,
2243    gas_used: &mut u64,
2244    ctx: &ArbPrecompileCtx,
2245) -> PrecompileResult {
2246    let data = input.data;
2247    if data.len() < 36 {
2248        return crate::burn_all_revert(input.gas);
2249    }
2250    let gas_limit = input.gas;
2251    let enabled = U256::from_be_slice(&data[4..36]) != U256::ZERO;
2252
2253    load_arbos(input)?;
2254    let internals = input.internals_mut();
2255    let arb_state = ctx
2256        .block
2257        .arbos_state(internals)
2258        .map_err(ArbPrecompileError::fatal)?;
2259    arb_state
2260        .set_collect_tips(internals, enabled)
2261        .map_err(ArbPrecompileError::fatal)?;
2262    ctx.block
2263        .state_params_dirty
2264        .store(true, std::sync::atomic::Ordering::Relaxed);
2265    crate::charge_precompile_gas(gas_used, write_cost(!enabled));
2266    Ok(PrecompileOutput::new(
2267        (*gas_used).min(gas_limit),
2268        Vec::new().into(),
2269    ))
2270}
2271
2272/// Emit the OwnerActs event: OwnerActs(bytes4 method, address owner, bytes data).
2273fn emit_owner_acts(input: &mut PrecompileInput<'_>, selector: &[u8; 4], calldata: &[u8]) {
2274    let topic0 = IArbOwner::OwnerActs::SIGNATURE_HASH;
2275    let mut method_topic = [0u8; 32];
2276    method_topic[..4].copy_from_slice(selector);
2277    let topic1 = B256::from(method_topic);
2278    let topic2 = B256::left_padding_from(input.caller.as_slice());
2279
2280    let mut log_data = Vec::with_capacity(64 + calldata.len().div_ceil(32) * 32);
2281    log_data.extend_from_slice(&U256::from(32).to_be_bytes::<32>());
2282    log_data.extend_from_slice(&U256::from(calldata.len()).to_be_bytes::<32>());
2283    log_data.extend_from_slice(calldata);
2284    let pad = (32 - (calldata.len() % 32)) % 32;
2285    log_data.extend(std::iter::repeat_n(0u8, pad));
2286
2287    input.internals_mut().log(Log::new_unchecked(
2288        ARBOWNER_ADDRESS,
2289        vec![topic0, topic1, topic2],
2290        log_data.into(),
2291    ));
2292}