arb_precompiles/
lib.rs

1//! Arbitrum precompile contracts.
2//!
3//! Implements the system contracts at addresses `0x64`+ that provide
4//! on-chain access to ArbOS state, gas pricing, retryable tickets,
5//! Stylus WASM management, and node interface queries.
6
7mod interfaces;
8
9mod arbaddresstable;
10mod arbaggregator;
11mod arbbls;
12mod arbdebug;
13mod arbfilteredtxmanager;
14mod arbfunctiontable;
15mod arbgasinfo;
16mod arbinfo;
17mod arbnativetokenmanager;
18mod arbosacts;
19mod arbostest;
20mod arbowner;
21mod arbownerpublic;
22mod arbretryabletx;
23mod arbstatistics;
24pub mod arbsys;
25mod arbwasm;
26mod arbwasmcache;
27mod nodeinterface;
28mod nodeinterface_debug;
29pub mod storage_slot;
30
31pub use arbaddresstable::{create_arbaddresstable_precompile, ARBADDRESSTABLE_ADDRESS};
32pub use arbaggregator::{create_arbaggregator_precompile, ARBAGGREGATOR_ADDRESS};
33pub use arbbls::{create_arbbls_precompile, ARBBLS_ADDRESS};
34pub use arbdebug::{create_arbdebug_precompile, ARBDEBUG_ADDRESS};
35pub use arbfilteredtxmanager::{
36    create_arbfilteredtxmanager_precompile, ARBFILTEREDTXMANAGER_ADDRESS,
37};
38pub use arbfunctiontable::{create_arbfunctiontable_precompile, ARBFUNCTIONTABLE_ADDRESS};
39pub use arbgasinfo::{create_arbgasinfo_precompile, ARBGASINFO_ADDRESS};
40pub use arbinfo::{create_arbinfo_precompile, ARBINFO_ADDRESS};
41pub use arbnativetokenmanager::{
42    create_arbnativetokenmanager_precompile, ARBNATIVETOKENMANAGER_ADDRESS,
43};
44pub use arbosacts::{create_arbosacts_precompile, ARBOSACTS_ADDRESS};
45pub use arbostest::{create_arbostest_precompile, ARBOSTEST_ADDRESS};
46pub use arbowner::{create_arbowner_precompile, ARBOWNER_ADDRESS};
47pub use arbownerpublic::{create_arbownerpublic_precompile, ARBOWNERPUBLIC_ADDRESS};
48pub use arbretryabletx::{
49    create_arbretryabletx_precompile, redeem_scheduled_topic, ticket_created_topic,
50    ARBRETRYABLETX_ADDRESS,
51};
52pub use arbstatistics::{create_arbstatistics_precompile, ARBSTATISTICS_ADDRESS};
53pub use arbsys::{
54    create_arbsys_precompile, get_cached_l1_block_number, get_current_l2_block, get_tx_is_aliased,
55    set_cached_l1_block_number, set_current_l2_block, set_tx_is_aliased, store_arbsys_state,
56    take_arbsys_state, ArbSysMerkleState, ARBSYS_ADDRESS,
57};
58pub use arbwasm::{create_arbwasm_precompile, ARBWASM_ADDRESS};
59pub use arbwasmcache::{create_arbwasmcache_precompile, ARBWASMCACHE_ADDRESS};
60pub use nodeinterface::{
61    build_fake_tx_bytes, compute_l1_gas_for_estimate, create_nodeinterface_precompile,
62    decode_estimate_args, NODE_INTERFACE_ADDRESS,
63};
64pub use nodeinterface_debug::{
65    create_nodeinterface_debug_precompile, NODE_INTERFACE_DEBUG_ADDRESS,
66};
67pub use storage_slot::ARBOS_STATE_ADDRESS;
68
69use alloy_evm::precompiles::{DynPrecompile, PrecompileInput, PrecompilesMap};
70use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
71use std::cell::Cell;
72
73/// RIP-7212 P256VERIFY precompile address (ArbOS v30+).
74pub const P256VERIFY_ADDRESS: alloy_primitives::Address =
75    alloy_primitives::address!("0000000000000000000000000000000000000100");
76
77/// modexp precompile address (0x05).
78const MODEXP_ADDRESS: alloy_primitives::Address =
79    alloy_primitives::address!("0000000000000000000000000000000000000005");
80
81/// BLS12-381 precompile addresses (EIP-2537), enabled from ArbOS v50.
82const BLS12_381_ADDRESSES: [alloy_primitives::Address; 7] = [
83    alloy_primitives::address!("000000000000000000000000000000000000000b"),
84    alloy_primitives::address!("000000000000000000000000000000000000000c"),
85    alloy_primitives::address!("000000000000000000000000000000000000000d"),
86    alloy_primitives::address!("000000000000000000000000000000000000000e"),
87    alloy_primitives::address!("000000000000000000000000000000000000000f"),
88    alloy_primitives::address!("0000000000000000000000000000000000000010"),
89    alloy_primitives::address!("0000000000000000000000000000000000000011"),
90];
91
92fn create_p256verify_precompile() -> DynPrecompile {
93    DynPrecompile::new(PrecompileId::P256Verify, |input: PrecompileInput<'_>| {
94        revm::precompile::secp256r1::p256_verify(input.data, input.gas)
95    })
96}
97
98fn create_modexp_osaka_precompile() -> DynPrecompile {
99    DynPrecompile::new(PrecompileId::ModExp, |input: PrecompileInput<'_>| {
100        revm::precompile::modexp::osaka_run(input.data, input.gas)
101    })
102}
103
104// ── ArbOS version (process-wide) ────────────────────────────────────
105// Process-wide because tokio offloads EVM execution onto a blocking thread
106// pool; thread-locals set on the reactor thread don't propagate.
107
108static GLOBAL_ARBOS_VERSION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
109
110thread_local! {
111    /// Per-thread fast-path mirror for ArbOS version (kept in sync via set_arbos_version).
112    static ARBOS_VERSION: Cell<u64> = const { Cell::new(0) };
113    /// L1 block number for the NUMBER opcode, from ArbOS state after StartBlock.
114    static L1_BLOCK_NUMBER_FOR_EVM: Cell<u64> = const { Cell::new(0) };
115    /// Current EVM call depth, incremented on each CALL/CREATE frame.
116    /// Used by precompiles (e.g., ArbSys.isTopLevelCall) to determine
117    /// the call stack position. Reset to 0 at transaction start.
118    static EVM_CALL_DEPTH: Cell<usize> = const { Cell::new(0) };
119    /// msg.sender per call frame, indexed by depth-1. Pushed on
120    /// frame_init, popped on frame_return_result. Used by ArbSys to
121    /// resolve `Contracts[depth-2].Caller()`.
122    static CALLER_STACK: std::cell::RefCell<Vec<alloy_primitives::Address>> =
123        const { std::cell::RefCell::new(Vec::new()) };
124    /// Current block timestamp, set before transaction execution.
125    /// Used by ArbWasm to compute program age for expiry checks.
126    static BLOCK_TIMESTAMP: Cell<u64> = const { Cell::new(0) };
127    /// Current gas backlog value, set by executor before each tx.
128    /// Used by Redeem precompile to determine ShrinkBacklog write cost.
129    static CURRENT_GAS_BACKLOG: Cell<u64> = const { Cell::new(0) };
130    /// Gas consumed by precompile operations before an error.
131    static PRECOMPILE_GAS_USED: Cell<u64> = const { Cell::new(0) };
132    /// Current tx poster fee (wei), set by executor before each tx.
133    /// Used by ArbGasInfo.getCurrentTxL1GasFees to avoid storage reads.
134    static CURRENT_TX_POSTER_FEE: Cell<u128> = const { Cell::new(0) };
135    /// Currently-executing retryable ticket ID (zero when not inside a retry tx).
136    static CURRENT_RETRYABLE_ID: Cell<[u8; 32]> = const { Cell::new([0u8; 32]) };
137    /// Currently-executing redeemer (refund_to) address, left-padded into 32 bytes.
138    static CURRENT_REDEEMER: Cell<[u8; 32]> = const { Cell::new([0u8; 32]) };
139    /// Poster fee balance correction for BALANCE opcode.
140    /// The canonical implementation charges gas_limit * baseFee, but our reduced
141    /// gas_limit charges less by posterGas * baseFee. The BALANCE opcode handler
142    /// subtracts this amount when checking the sender's balance.
143    static POSTER_BALANCE_CORRECTION: Cell<u128> = const { Cell::new(0) };
144    /// Current transaction sender address (first 20 bytes as u128 + extra Cell).
145    static TX_SENDER_LO: Cell<u128> = const { Cell::new(0) };
146    static TX_SENDER_HI: Cell<u32> = const { Cell::new(0) };
147    static STYLUS_ACTIVATION_ADDR: Cell<Option<[u8; 20]>> = const { Cell::new(None) };
148    static STYLUS_KEEPALIVE_HASH: Cell<Option<[u8; 32]>> = const { Cell::new(None) };
149    static STYLUS_ACTIVATION_DATA_FEE: Cell<u128> = const { Cell::new(0) };
150}
151
152use std::cell::RefCell;
153
154thread_local! {
155    static PENDING_PRECOMPILE_LOGS: RefCell<Vec<(alloy_primitives::Address, Vec<alloy_primitives::B256>, Vec<u8>)>> = const { RefCell::new(Vec::new()) };
156    /// Per-block LRU of recently invoked Stylus program codehashes. Used by
157    /// ArbOS v60+ pricing; capacity set per-block from `params.BlockCacheSize`.
158    static RECENT_WASMS: RefCell<(Vec<alloy_primitives::B256>, usize)> = const { RefCell::new((Vec::new(), 0)) };
159}
160
161/// Reset the recent WASMs cache for a new block, with the given capacity.
162pub fn reset_recent_wasms(capacity: usize) {
163    RECENT_WASMS.with(|c| {
164        let mut cache = c.borrow_mut();
165        cache.0.clear();
166        cache.1 = capacity;
167    });
168}
169
170/// Insert a Stylus program codehash into the recent WASMs cache.
171/// Returns `true` if the codehash was already present (cache hit).
172pub fn insert_recent_wasm(hash: alloy_primitives::B256) -> bool {
173    RECENT_WASMS.with(|c| {
174        let mut cache = c.borrow_mut();
175        let was_present = if let Some(pos) = cache.0.iter().position(|h| *h == hash) {
176            cache.0.remove(pos);
177            true
178        } else {
179            false
180        };
181        cache.0.push(hash);
182        let max = cache.1;
183        if max > 0 && cache.0.len() > max {
184            cache.0.remove(0);
185        }
186        was_present
187    })
188}
189
190use std::sync::Mutex as StdMutex;
191
192/// Cache of L2 block hashes for the arbBlockHash() precompile.
193/// Populated from the header chain during apply_pre_execution_changes.
194/// Separate from the journal's block_hashes (which holds L1 hashes for BLOCKHASH opcode).
195static L2_BLOCKHASH_CACHE: StdMutex<
196    Option<std::collections::HashMap<u64, alloy_primitives::B256>>,
197> = StdMutex::new(None);
198
199/// Set an L2 block hash in the arbBlockHash cache.
200pub fn set_l2_block_hash(l2_block_number: u64, hash: alloy_primitives::B256) {
201    let mut cache = L2_BLOCKHASH_CACHE
202        .lock()
203        .expect("L2 blockhash cache lock poisoned");
204    let map = cache.get_or_insert_with(std::collections::HashMap::new);
205    map.insert(l2_block_number, hash);
206}
207
208/// Get an L2 block hash from the arbBlockHash cache.
209pub fn get_l2_block_hash(l2_block_number: u64) -> Option<alloy_primitives::B256> {
210    let cache = L2_BLOCKHASH_CACHE
211        .lock()
212        .expect("L2 blockhash cache lock poisoned");
213    cache.as_ref()?.get(&l2_block_number).copied()
214}
215
216/// Set the current ArbOS version for precompile version gating.
217pub fn set_arbos_version(version: u64) {
218    GLOBAL_ARBOS_VERSION.store(version, std::sync::atomic::Ordering::Relaxed);
219    ARBOS_VERSION.with(|v| v.set(version));
220}
221
222/// Get the current ArbOS version.
223pub fn get_arbos_version() -> u64 {
224    let local = ARBOS_VERSION.with(|v| v.get());
225    if local != 0 {
226        return local;
227    }
228    let global = GLOBAL_ARBOS_VERSION.load(std::sync::atomic::Ordering::Relaxed);
229    if global != 0 {
230        ARBOS_VERSION.with(|v| v.set(global));
231    }
232    global
233}
234
235static ALLOW_DEBUG_PRECOMPILES: std::sync::atomic::AtomicBool =
236    std::sync::atomic::AtomicBool::new(false);
237
238/// Set whether ArbDebug / ArbosTest debug precompiles are callable. Driven
239/// by the chain spec's `AllowDebugPrecompiles` flag.
240pub fn set_allow_debug_precompiles(allow: bool) {
241    ALLOW_DEBUG_PRECOMPILES.store(allow, std::sync::atomic::Ordering::Relaxed);
242}
243
244pub fn allow_debug_precompiles() -> bool {
245    ALLOW_DEBUG_PRECOMPILES.load(std::sync::atomic::Ordering::Relaxed)
246}
247
248/// Set the L1 block number for the NUMBER opcode.
249pub fn set_l1_block_number_for_evm(number: u64) {
250    L1_BLOCK_NUMBER_FOR_EVM.with(|v| v.set(number));
251}
252
253/// Get the L1 block number for the NUMBER opcode.
254pub fn get_l1_block_number_for_evm() -> u64 {
255    L1_BLOCK_NUMBER_FOR_EVM.with(|v| v.get())
256}
257
258/// Set the current gas backlog value for the Redeem precompile.
259pub fn set_current_gas_backlog(backlog: u64) {
260    CURRENT_GAS_BACKLOG.with(|v| v.set(backlog));
261}
262
263/// Get the current gas backlog value.
264pub fn get_current_gas_backlog() -> u64 {
265    CURRENT_GAS_BACKLOG.with(|v| v.get())
266}
267
268pub fn reset_precompile_gas() {
269    PRECOMPILE_GAS_USED.with(|v| v.set(0));
270}
271
272pub fn charge_precompile_gas(gas: u64) {
273    PRECOMPILE_GAS_USED.with(|v| v.set(v.get() + gas));
274}
275
276pub fn get_precompile_gas() -> u64 {
277    PRECOMPILE_GAS_USED.with(|v| v.get())
278}
279
280/// Initialize gas tracking for a precompile call: reset accumulator, charge
281/// argsCost (CopyGas * input words) and OpenArbosState (1 SLOAD = 800).
282pub fn init_precompile_gas(input_len: usize) {
283    reset_precompile_gas();
284    let args_cost = 3u64 * (input_len as u64).saturating_sub(4).div_ceil(32);
285    charge_precompile_gas(args_cost + 800);
286}
287
288/// Set the current tx poster fee for ArbGasInfo.getCurrentTxL1GasFees.
289pub fn set_current_tx_poster_fee(fee_wei: u128) {
290    CURRENT_TX_POSTER_FEE.with(|v| v.set(fee_wei));
291}
292
293/// Get the current tx poster fee.
294pub fn get_current_tx_poster_fee() -> u128 {
295    CURRENT_TX_POSTER_FEE.with(|v| v.get())
296}
297
298pub fn set_current_retryable_id(id: alloy_primitives::B256) {
299    CURRENT_RETRYABLE_ID.with(|v| v.set(id.0));
300}
301
302pub fn get_current_retryable_id() -> alloy_primitives::U256 {
303    alloy_primitives::U256::from_be_bytes(CURRENT_RETRYABLE_ID.with(|v| v.get()))
304}
305
306pub fn set_current_redeemer(addr: alloy_primitives::Address) {
307    let padded = alloy_primitives::B256::left_padding_from(addr.as_slice());
308    CURRENT_REDEEMER.with(|v| v.set(padded.0));
309}
310
311pub fn get_current_redeemer() -> alloy_primitives::U256 {
312    alloy_primitives::U256::from_be_bytes(CURRENT_REDEEMER.with(|v| v.get()))
313}
314
315pub fn clear_tx_scratch() {
316    CURRENT_TX_POSTER_FEE.with(|v| v.set(0));
317    CURRENT_RETRYABLE_ID.with(|v| v.set([0u8; 32]));
318    CURRENT_REDEEMER.with(|v| v.set([0u8; 32]));
319}
320
321/// Set the poster balance correction for BALANCE opcode adjustment.
322pub fn set_poster_balance_correction(correction: alloy_primitives::U256) {
323    let val: u128 = correction.try_into().unwrap_or(u128::MAX);
324    POSTER_BALANCE_CORRECTION.with(|v| v.set(val));
325}
326
327/// Get the poster balance correction.
328pub fn get_poster_balance_correction() -> alloy_primitives::U256 {
329    alloy_primitives::U256::from(POSTER_BALANCE_CORRECTION.with(|v| v.get()))
330}
331
332/// Set the current tx sender for BALANCE correction.
333pub fn set_current_tx_sender(addr: alloy_primitives::Address) {
334    let bytes = addr.as_slice();
335    let lo = u128::from_be_bytes(bytes[4..20].try_into().unwrap_or([0u8; 16]));
336    let hi = u32::from_be_bytes(bytes[0..4].try_into().unwrap_or([0u8; 4]));
337    TX_SENDER_LO.with(|v| v.set(lo));
338    TX_SENDER_HI.with(|v| v.set(hi));
339}
340
341/// Get the current tx sender.
342pub fn get_current_tx_sender() -> alloy_primitives::Address {
343    let lo = TX_SENDER_LO.with(|v| v.get());
344    let hi = TX_SENDER_HI.with(|v| v.get());
345    let mut bytes = [0u8; 20];
346    bytes[0..4].copy_from_slice(&hi.to_be_bytes());
347    bytes[4..20].copy_from_slice(&lo.to_be_bytes());
348    alloy_primitives::Address::new(bytes)
349}
350
351/// Set the EVM call depth to a specific value.
352/// Called by the precompile provider which reads the depth from revm's journal.
353pub fn set_evm_depth(depth: usize) {
354    EVM_CALL_DEPTH.with(|v| v.set(depth));
355}
356
357/// Get the current EVM call depth.
358pub fn get_evm_depth() -> usize {
359    EVM_CALL_DEPTH.with(|v| v.get())
360}
361
362pub fn push_caller_frame(caller: alloy_primitives::Address) {
363    CALLER_STACK.with(|s| s.borrow_mut().push(caller));
364}
365
366pub fn pop_caller_frame() {
367    CALLER_STACK.with(|s| {
368        s.borrow_mut().pop();
369    });
370}
371
372pub fn reset_caller_stack() {
373    CALLER_STACK.with(|s| s.borrow_mut().clear());
374}
375
376/// msg.sender of the frame at `depth` (1-indexed). `None` outside range.
377pub fn caller_at_depth(depth: usize) -> Option<alloy_primitives::Address> {
378    if depth == 0 {
379        return None;
380    }
381    CALLER_STACK.with(|s| s.borrow().get(depth - 1).copied())
382}
383
384/// Set the current block timestamp for precompile queries.
385pub fn set_block_timestamp(timestamp: u64) {
386    BLOCK_TIMESTAMP.with(|v| v.set(timestamp));
387}
388
389/// Get the current block timestamp.
390pub fn get_block_timestamp() -> u64 {
391    BLOCK_TIMESTAMP.with(|v| v.get())
392}
393
394pub fn set_stylus_activation_request(addr: Option<alloy_primitives::Address>) {
395    STYLUS_ACTIVATION_ADDR.with(|v| v.set(addr.map(|a| *a.as_ref())));
396}
397
398pub fn take_stylus_activation_request() -> Option<alloy_primitives::Address> {
399    STYLUS_ACTIVATION_ADDR.with(|v| {
400        let val = v.get();
401        v.set(None);
402        val.map(alloy_primitives::Address::from)
403    })
404}
405
406pub fn set_stylus_keepalive_request(hash: Option<alloy_primitives::B256>) {
407    STYLUS_KEEPALIVE_HASH.with(|v| v.set(hash.map(|h| h.0)));
408}
409
410pub fn take_stylus_keepalive_request() -> Option<alloy_primitives::B256> {
411    STYLUS_KEEPALIVE_HASH.with(|v| {
412        let val = v.get();
413        v.set(None);
414        val.map(alloy_primitives::B256::from)
415    })
416}
417
418pub fn set_stylus_activation_data_fee(fee: alloy_primitives::U256) {
419    STYLUS_ACTIVATION_DATA_FEE.with(|v| v.set(fee.try_into().unwrap_or(u128::MAX)));
420}
421
422pub fn take_stylus_activation_data_fee() -> alloy_primitives::U256 {
423    STYLUS_ACTIVATION_DATA_FEE.with(|v| {
424        let val = v.get();
425        v.set(0);
426        alloy_primitives::U256::from(val)
427    })
428}
429
430pub fn emit_log(
431    address: alloy_primitives::Address,
432    topics: &[alloy_primitives::B256],
433    data: &[u8],
434) {
435    PENDING_PRECOMPILE_LOGS.with(|logs| {
436        logs.borrow_mut()
437            .push((address, topics.to_vec(), data.to_vec()));
438    });
439}
440
441pub fn take_pending_precompile_logs() -> Vec<(
442    alloy_primitives::Address,
443    Vec<alloy_primitives::B256>,
444    Vec<u8>,
445)> {
446    PENDING_PRECOMPILE_LOGS.with(|logs| std::mem::take(&mut *logs.borrow_mut()))
447}
448
449fn check_precompile_version(min_version: u64) -> Option<PrecompileResult> {
450    if get_arbos_version() < min_version {
451        Some(Ok(PrecompileOutput::new(0, Default::default())))
452    } else {
453        None
454    }
455}
456
457/// Pre-dispatch error: consumes all supplied gas and reverts.
458fn burn_all_revert(gas_limit: u64) -> PrecompileResult {
459    Ok(PrecompileOutput::new_reverted(
460        gas_limit,
461        Default::default(),
462    ))
463}
464
465/// Emit a pre-encoded Solidity custom-error payload (selector + ABI args)
466/// as a revert. Adds the copy cost for the payload to the accumulated gas.
467pub fn sol_error_revert(payload: Vec<u8>, gas_limit: u64) -> PrecompileResult {
468    let result_cost = 3u64 * (payload.len() as u64).div_ceil(32); // CopyGas * words
469    charge_precompile_gas(result_cost);
470    let gas = get_precompile_gas();
471    Ok(PrecompileOutput::new_reverted(
472        gas.min(gas_limit),
473        payload.into(),
474    ))
475}
476
477fn gas_check(gas_limit: u64, result: PrecompileResult) -> PrecompileResult {
478    let accumulated_gas = get_precompile_gas();
479    reset_precompile_gas();
480    match result {
481        Ok(ref output) if output.gas_used > gas_limit => Err(PrecompileError::OutOfGas),
482        Err(PrecompileError::Other(_)) if get_arbos_version() >= 11 => Ok(
483            PrecompileOutput::new_reverted(accumulated_gas.min(gas_limit), Default::default()),
484        ),
485        other => other,
486    }
487}
488
489/// Returns a revert that consumes the full `gas_limit` if the current ArbOS
490/// version is outside `[min_version, max_version]`. `max_version == 0` is
491/// unbounded.
492fn check_method_version(
493    gas_limit: u64,
494    min_version: u64,
495    max_version: u64,
496) -> Option<PrecompileResult> {
497    let v = get_arbos_version();
498    if v < min_version || (max_version > 0 && v > max_version) {
499        Some(burn_all_revert(gas_limit))
500    } else {
501        None
502    }
503}
504
505const KZG_POINT_EVALUATION_ADDRESS: alloy_primitives::Address =
506    alloy_primitives::address!("000000000000000000000000000000000000000a");
507
508/// Registers Arbitrum precompiles into `map` and applies the per-ArbOS-version
509/// adjustments to the standard Ethereum precompile set.
510pub fn register_arb_precompiles(map: &mut PrecompilesMap, arbos_version: u64) {
511    map.extend_precompiles([
512        (ARBSYS_ADDRESS, create_arbsys_precompile()),
513        (ARBGASINFO_ADDRESS, create_arbgasinfo_precompile()),
514        (ARBINFO_ADDRESS, create_arbinfo_precompile()),
515        (ARBSTATISTICS_ADDRESS, create_arbstatistics_precompile()),
516        (
517            ARBFUNCTIONTABLE_ADDRESS,
518            create_arbfunctiontable_precompile(),
519        ),
520        (ARBOSACTS_ADDRESS, create_arbosacts_precompile()),
521        (ARBOSTEST_ADDRESS, create_arbostest_precompile()),
522        (ARBOWNERPUBLIC_ADDRESS, create_arbownerpublic_precompile()),
523        (ARBADDRESSTABLE_ADDRESS, create_arbaddresstable_precompile()),
524        (ARBAGGREGATOR_ADDRESS, create_arbaggregator_precompile()),
525        (ARBRETRYABLETX_ADDRESS, create_arbretryabletx_precompile()),
526        (ARBOWNER_ADDRESS, create_arbowner_precompile()),
527        (ARBBLS_ADDRESS, create_arbbls_precompile()),
528        (ARBDEBUG_ADDRESS, create_arbdebug_precompile()),
529        (ARBWASM_ADDRESS, create_arbwasm_precompile()),
530        (ARBWASMCACHE_ADDRESS, create_arbwasmcache_precompile()),
531        (
532            ARBFILTEREDTXMANAGER_ADDRESS,
533            create_arbfilteredtxmanager_precompile(),
534        ),
535        (
536            ARBNATIVETOKENMANAGER_ADDRESS,
537            create_arbnativetokenmanager_precompile(),
538        ),
539        (NODE_INTERFACE_ADDRESS, create_nodeinterface_precompile()),
540        (
541            NODE_INTERFACE_DEBUG_ADDRESS,
542            create_nodeinterface_debug_precompile(),
543        ),
544    ]);
545
546    if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_30 {
547        // P256VERIFY stays at 3450 gas on Arbitrum for all ArbOS >= 30,
548        // regardless of the underlying EVM spec's Osaka rules.
549        map.extend_precompiles([(P256VERIFY_ADDRESS, create_p256verify_precompile())]);
550    } else {
551        map.apply_precompile(&KZG_POINT_EVALUATION_ADDRESS, |_| None);
552        map.apply_precompile(&P256VERIFY_ADDRESS, |_| None);
553    }
554
555    if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_50 {
556        // ArbOS 50+ switches modexp to the EIP-7823 + EIP-7883 gas schedule.
557        map.extend_precompiles([(MODEXP_ADDRESS, create_modexp_osaka_precompile())]);
558    } else {
559        // BLS12-381 precompiles are not available before ArbOS 50.
560        for addr in &BLS12_381_ADDRESSES {
561            map.apply_precompile(addr, |_| None);
562        }
563    }
564}