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 arbaddresstable;
8mod arbaggregator;
9mod arbbls;
10mod arbdebug;
11mod arbfilteredtxmanager;
12mod arbfunctiontable;
13mod arbgasinfo;
14mod arbinfo;
15mod arbnativetokenmanager;
16mod arbosacts;
17mod arbowner;
18mod arbownerpublic;
19mod arbretryabletx;
20mod arbstatistics;
21mod arbsys;
22mod arbwasm;
23mod arbwasmcache;
24mod nodeinterface;
25pub mod storage_slot;
26
27pub use arbaddresstable::{create_arbaddresstable_precompile, ARBADDRESSTABLE_ADDRESS};
28pub use arbaggregator::{create_arbaggregator_precompile, ARBAGGREGATOR_ADDRESS};
29pub use arbbls::{create_arbbls_precompile, ARBBLS_ADDRESS};
30pub use arbdebug::{create_arbdebug_precompile, ARBDEBUG_ADDRESS};
31pub use arbfilteredtxmanager::{
32    create_arbfilteredtxmanager_precompile, ARBFILTEREDTXMANAGER_ADDRESS,
33};
34pub use arbfunctiontable::{create_arbfunctiontable_precompile, ARBFUNCTIONTABLE_ADDRESS};
35pub use arbgasinfo::{create_arbgasinfo_precompile, ARBGASINFO_ADDRESS};
36pub use arbinfo::{create_arbinfo_precompile, ARBINFO_ADDRESS};
37pub use arbnativetokenmanager::{
38    create_arbnativetokenmanager_precompile, ARBNATIVETOKENMANAGER_ADDRESS,
39};
40pub use arbosacts::{create_arbosacts_precompile, ARBOSACTS_ADDRESS};
41pub use arbowner::{create_arbowner_precompile, ARBOWNER_ADDRESS};
42pub use arbownerpublic::{create_arbownerpublic_precompile, ARBOWNERPUBLIC_ADDRESS};
43pub use arbretryabletx::{
44    create_arbretryabletx_precompile, redeem_scheduled_topic, ticket_created_topic,
45    ARBRETRYABLETX_ADDRESS,
46};
47pub use arbstatistics::{create_arbstatistics_precompile, ARBSTATISTICS_ADDRESS};
48pub use arbsys::{
49    create_arbsys_precompile, get_cached_l1_block_number, get_current_l2_block, get_tx_is_aliased,
50    set_cached_l1_block_number, set_current_l2_block, set_tx_is_aliased, store_arbsys_state,
51    take_arbsys_state, ArbSysMerkleState, ARBSYS_ADDRESS,
52};
53pub use arbwasm::{create_arbwasm_precompile, ARBWASM_ADDRESS};
54pub use arbwasmcache::{create_arbwasmcache_precompile, ARBWASMCACHE_ADDRESS};
55pub use nodeinterface::{create_nodeinterface_precompile, NODE_INTERFACE_ADDRESS};
56pub use storage_slot::ARBOS_STATE_ADDRESS;
57
58use alloy_evm::precompiles::PrecompilesMap;
59use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult};
60use std::cell::Cell;
61
62// ── ArbOS version thread-local ──────────────────────────────────────
63
64thread_local! {
65    /// Current ArbOS version, set by the block executor before transaction execution.
66    static ARBOS_VERSION: Cell<u64> = const { Cell::new(0) };
67    /// L1 block number for the NUMBER opcode, from ArbOS state after StartBlock.
68    static L1_BLOCK_NUMBER_FOR_EVM: Cell<u64> = const { Cell::new(0) };
69    /// Current EVM call depth, incremented on each CALL/CREATE frame.
70    /// Used by precompiles (e.g., ArbSys.isTopLevelCall) to determine
71    /// the call stack position. Reset to 0 at transaction start.
72    static EVM_CALL_DEPTH: Cell<usize> = const { Cell::new(0) };
73    /// Current block timestamp, set before transaction execution.
74    /// Used by ArbWasm to compute program age for expiry checks.
75    static BLOCK_TIMESTAMP: Cell<u64> = const { Cell::new(0) };
76    /// Current gas backlog value, set by executor before each tx.
77    /// Used by Redeem precompile to determine ShrinkBacklog write cost.
78    static CURRENT_GAS_BACKLOG: Cell<u64> = const { Cell::new(0) };
79    /// Current tx poster fee (wei), set by executor before each tx.
80    /// Used by ArbGasInfo.getCurrentTxL1GasFees to avoid storage reads.
81    static CURRENT_TX_POSTER_FEE: Cell<u128> = const { Cell::new(0) };
82    /// Poster fee balance correction for BALANCE opcode.
83    /// The canonical implementation charges gas_limit * baseFee, but our reduced
84    /// gas_limit charges less by posterGas * baseFee. The BALANCE opcode handler
85    /// subtracts this amount when checking the sender's balance.
86    static POSTER_BALANCE_CORRECTION: Cell<u128> = const { Cell::new(0) };
87    /// Current transaction sender address (first 20 bytes as u128 + extra Cell).
88    static TX_SENDER_LO: Cell<u128> = const { Cell::new(0) };
89    static TX_SENDER_HI: Cell<u32> = const { Cell::new(0) };
90}
91
92use std::sync::Mutex as StdMutex;
93
94/// Cache of L2 block hashes for the arbBlockHash() precompile.
95/// Populated from the header chain during apply_pre_execution_changes.
96/// Separate from the journal's block_hashes (which holds L1 hashes for BLOCKHASH opcode).
97static L2_BLOCKHASH_CACHE: StdMutex<
98    Option<std::collections::HashMap<u64, alloy_primitives::B256>>,
99> = StdMutex::new(None);
100
101/// Set an L2 block hash in the arbBlockHash cache.
102pub fn set_l2_block_hash(l2_block_number: u64, hash: alloy_primitives::B256) {
103    let mut cache = L2_BLOCKHASH_CACHE
104        .lock()
105        .expect("L2 blockhash cache lock poisoned");
106    let map = cache.get_or_insert_with(std::collections::HashMap::new);
107    map.insert(l2_block_number, hash);
108}
109
110/// Get an L2 block hash from the arbBlockHash cache.
111pub fn get_l2_block_hash(l2_block_number: u64) -> Option<alloy_primitives::B256> {
112    let cache = L2_BLOCKHASH_CACHE
113        .lock()
114        .expect("L2 blockhash cache lock poisoned");
115    cache.as_ref()?.get(&l2_block_number).copied()
116}
117
118/// Set the current ArbOS version for precompile version gating.
119pub fn set_arbos_version(version: u64) {
120    ARBOS_VERSION.with(|v| v.set(version));
121}
122
123/// Get the current ArbOS version.
124pub fn get_arbos_version() -> u64 {
125    ARBOS_VERSION.with(|v| v.get())
126}
127
128/// Set the L1 block number for the NUMBER opcode.
129pub fn set_l1_block_number_for_evm(number: u64) {
130    L1_BLOCK_NUMBER_FOR_EVM.with(|v| v.set(number));
131}
132
133/// Get the L1 block number for the NUMBER opcode.
134pub fn get_l1_block_number_for_evm() -> u64 {
135    L1_BLOCK_NUMBER_FOR_EVM.with(|v| v.get())
136}
137
138/// Set the current gas backlog value for the Redeem precompile.
139pub fn set_current_gas_backlog(backlog: u64) {
140    CURRENT_GAS_BACKLOG.with(|v| v.set(backlog));
141}
142
143/// Get the current gas backlog value.
144pub fn get_current_gas_backlog() -> u64 {
145    CURRENT_GAS_BACKLOG.with(|v| v.get())
146}
147
148/// Set the current tx poster fee for ArbGasInfo.getCurrentTxL1GasFees.
149pub fn set_current_tx_poster_fee(fee_wei: u128) {
150    CURRENT_TX_POSTER_FEE.with(|v| v.set(fee_wei));
151}
152
153/// Get the current tx poster fee.
154pub fn get_current_tx_poster_fee() -> u128 {
155    CURRENT_TX_POSTER_FEE.with(|v| v.get())
156}
157
158/// Set the poster balance correction for BALANCE opcode adjustment.
159pub fn set_poster_balance_correction(correction: alloy_primitives::U256) {
160    let val: u128 = correction.try_into().unwrap_or(u128::MAX);
161    POSTER_BALANCE_CORRECTION.with(|v| v.set(val));
162}
163
164/// Get the poster balance correction.
165pub fn get_poster_balance_correction() -> alloy_primitives::U256 {
166    alloy_primitives::U256::from(POSTER_BALANCE_CORRECTION.with(|v| v.get()))
167}
168
169/// Set the current tx sender for BALANCE correction.
170pub fn set_current_tx_sender(addr: alloy_primitives::Address) {
171    let bytes = addr.as_slice();
172    let lo = u128::from_be_bytes(bytes[4..20].try_into().unwrap_or([0u8; 16]));
173    let hi = u32::from_be_bytes(bytes[0..4].try_into().unwrap_or([0u8; 4]));
174    TX_SENDER_LO.with(|v| v.set(lo));
175    TX_SENDER_HI.with(|v| v.set(hi));
176}
177
178/// Get the current tx sender.
179pub fn get_current_tx_sender() -> alloy_primitives::Address {
180    let lo = TX_SENDER_LO.with(|v| v.get());
181    let hi = TX_SENDER_HI.with(|v| v.get());
182    let mut bytes = [0u8; 20];
183    bytes[0..4].copy_from_slice(&hi.to_be_bytes());
184    bytes[4..20].copy_from_slice(&lo.to_be_bytes());
185    alloy_primitives::Address::new(bytes)
186}
187
188/// Set the EVM call depth to a specific value.
189/// Called by the precompile provider which reads the depth from revm's journal.
190pub fn set_evm_depth(depth: usize) {
191    EVM_CALL_DEPTH.with(|v| v.set(depth));
192}
193
194/// Get the current EVM call depth.
195pub fn get_evm_depth() -> usize {
196    EVM_CALL_DEPTH.with(|v| v.get())
197}
198
199/// Set the current block timestamp for precompile queries.
200pub fn set_block_timestamp(timestamp: u64) {
201    BLOCK_TIMESTAMP.with(|v| v.set(timestamp));
202}
203
204/// Get the current block timestamp.
205pub fn get_block_timestamp() -> u64 {
206    BLOCK_TIMESTAMP.with(|v| v.get())
207}
208
209/// Check precompile-level version gate. If the current ArbOS version is below
210/// `min_version`, the precompile is not yet active and we return success with
211/// empty bytes (as if calling a contract that doesn't exist).
212fn check_precompile_version(min_version: u64) -> Option<PrecompileResult> {
213    if get_arbos_version() < min_version {
214        Some(Ok(PrecompileOutput::new(0, Default::default())))
215    } else {
216        None
217    }
218}
219
220/// Ensure precompile gas_used does not exceed the gas limit.
221/// Returns `OutOfGas` if it does, preventing an assertion panic in alloy-evm.
222fn gas_check(gas_limit: u64, result: PrecompileResult) -> PrecompileResult {
223    match result {
224        Ok(ref output) if output.gas_used > gas_limit => Err(PrecompileError::OutOfGas),
225        other => other,
226    }
227}
228
229/// Check method-level version gate. If the current ArbOS version is below
230/// `min_version` or above `max_version` (when non-zero), the method reverts.
231fn check_method_version(min_version: u64, max_version: u64) -> Option<PrecompileResult> {
232    let v = get_arbos_version();
233    if v < min_version || (max_version > 0 && v > max_version) {
234        Some(Err(PrecompileError::other(
235            "method not available at this ArbOS version",
236        )))
237    } else {
238        None
239    }
240}
241
242/// Register all Arbitrum precompiles into a [`PrecompilesMap`].
243pub fn register_arb_precompiles(map: &mut PrecompilesMap) {
244    map.extend_precompiles([
245        (ARBSYS_ADDRESS, create_arbsys_precompile()),
246        (ARBGASINFO_ADDRESS, create_arbgasinfo_precompile()),
247        (ARBINFO_ADDRESS, create_arbinfo_precompile()),
248        (ARBSTATISTICS_ADDRESS, create_arbstatistics_precompile()),
249        (
250            ARBFUNCTIONTABLE_ADDRESS,
251            create_arbfunctiontable_precompile(),
252        ),
253        (ARBOSACTS_ADDRESS, create_arbosacts_precompile()),
254        (ARBOWNERPUBLIC_ADDRESS, create_arbownerpublic_precompile()),
255        (ARBADDRESSTABLE_ADDRESS, create_arbaddresstable_precompile()),
256        (ARBAGGREGATOR_ADDRESS, create_arbaggregator_precompile()),
257        (ARBRETRYABLETX_ADDRESS, create_arbretryabletx_precompile()),
258        (ARBOWNER_ADDRESS, create_arbowner_precompile()),
259        (ARBBLS_ADDRESS, create_arbbls_precompile()),
260        (ARBDEBUG_ADDRESS, create_arbdebug_precompile()),
261        (ARBWASM_ADDRESS, create_arbwasm_precompile()),
262        (ARBWASMCACHE_ADDRESS, create_arbwasmcache_precompile()),
263        (
264            ARBFILTEREDTXMANAGER_ADDRESS,
265            create_arbfilteredtxmanager_precompile(),
266        ),
267        (
268            ARBNATIVETOKENMANAGER_ADDRESS,
269            create_arbnativetokenmanager_precompile(),
270        ),
271        (NODE_INTERFACE_ADDRESS, create_nodeinterface_precompile()),
272    ]);
273}