Skip to main content

foundry_evm_networks/
arbitrum.rs

1//! Minimal Arbitrum system contract compatibility helpers.
2
3use std::borrow::Cow;
4
5use alloy_chains::Chain;
6use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
7use alloy_primitives::{Address, Bytes, U256, hex};
8use revm::precompile::{PrecompileHalt, PrecompileId, PrecompileOutput, PrecompileResult};
9
10/// ArbSys system contract address.
11pub const ARB_SYS_ADDRESS: Address = Address::with_last_byte(0x64);
12
13/// `ArbSys.arbBlockNumber()` selector.
14pub const ARB_BLOCK_NUMBER_SELECTOR: [u8; 4] = hex!("a3b1b31d");
15
16/// Gas Nitro charges inside `ArbSys.arbBlockNumber()`: opening the ArbOS state reads its version
17/// from storage (800, the EIP-2200 `SLOAD` cost), and copying the 32-byte result costs 3.
18pub const ARB_BLOCK_NUMBER_GAS_COST: u64 = 803;
19
20/// ID for the ArbSys precompile.
21pub static PRECOMPILE_ID_ARB_SYS: PrecompileId = PrecompileId::Custom(Cow::Borrowed("ArbSys"));
22
23/// Returns whether `chain_id` is an Arbitrum chain.
24pub fn is_arbitrum_chain(chain_id: u64) -> bool {
25    Chain::from_id(chain_id).is_arbitrum()
26}
27
28/// Returns the ABI-encoded result for `ArbSys.arbBlockNumber()`.
29pub fn arb_block_number_output(block_number: u64) -> Bytes {
30    U256::from(block_number).to_be_bytes::<32>().into()
31}
32
33/// Returns the gas cost and ABI-encoded result for `ArbSys.arbBlockNumber()`.
34pub fn arb_block_number_call(gas_limit: u64, block_number: u64) -> Option<(u64, Bytes)> {
35    (gas_limit >= ARB_BLOCK_NUMBER_GAS_COST)
36        .then(|| (ARB_BLOCK_NUMBER_GAS_COST, arb_block_number_output(block_number)))
37}
38
39/// Returns an ArbSys precompile for the provided L2 block number.
40pub fn arb_sys_precompile(block_number: u64) -> DynPrecompile {
41    DynPrecompile::new_stateful(PRECOMPILE_ID_ARB_SYS.clone(), move |input| {
42        arb_sys_precompile_call(input, block_number)
43    })
44}
45
46fn arb_sys_precompile_call(input: PrecompileInput<'_>, block_number: u64) -> PrecompileResult {
47    if input.data.get(..4) != Some(&ARB_BLOCK_NUMBER_SELECTOR) {
48        return Ok(PrecompileOutput::halt(
49            PrecompileHalt::Other("unsupported ArbSys selector".into()),
50            input.reservoir,
51        ));
52    }
53
54    let Some((gas_cost, output)) = arb_block_number_call(input.gas(), block_number) else {
55        return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, input.reservoir));
56    };
57
58    Ok(PrecompileOutput::new(gas_cost, output, input.reservoir))
59}