arbos/arbos_state/
mod.rs

1mod error;
2pub mod initialize;
3
4use std::sync::OnceLock;
5
6use alloy_primitives::{Address, B256, Bytes, U256, keccak256};
7use arb_primitives::arbos_versions::{
8    HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE_ARBITRUM, PRECOMPILE_MIN_ARBOS_VERSIONS,
9};
10// Root-level field offsets and subspace IDs are defined once in the storage
11// layout module; re-export the offsets that callers reference by name.
12use arb_storage::layout::{
13    ADDRESS_TABLE_SUBSPACE, BLOCKHASHES_SUBSPACE, CHAIN_CONFIG_SUBSPACE, CHAIN_OWNER_SUBSPACE,
14    FEATURES_SUBSPACE, L1_PRICING_SUBSPACE, L2_PRICING_SUBSPACE, NATIVE_TOKEN_SUBSPACE,
15    PROGRAMS_SUBSPACE, RETRYABLES_SUBSPACE, SEND_MERKLE_SUBSPACE, TRANSACTION_FILTERER_SUBSPACE,
16};
17pub use arb_storage::layout::{
18    BROTLI_COMPRESSION_LEVEL_OFFSET, CHAIN_ID_OFFSET, COLLECT_TIPS_OFFSET,
19    FILTERED_FUNDS_RECIPIENT_OFFSET, GENESIS_BLOCK_NUM_OFFSET, INFRA_FEE_ACCOUNT_OFFSET,
20    NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET, NETWORK_FEE_ACCOUNT_OFFSET,
21    TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET, UPGRADE_TIMESTAMP_OFFSET,
22    UPGRADE_VERSION_OFFSET, VERSION_OFFSET,
23};
24use arb_storage::{
25    ARBOS_STATE_ADDRESS, Detached, FILTERED_TX_STATE_ADDRESS, Storage, StorageBackedAddress,
26    StorageBackedBigUint, StorageBackedBytes, StorageBackedUint64, StorageBackend,
27    SystemStateBackend, get_account_balance, set_account_code, set_account_nonce, storage_key_map,
28};
29pub use error::ArbosStateError;
30use revm::Database;
31
32use crate::{
33    address_set::{self, AddressSet},
34    address_table::{self, AddressTable},
35    blockhash::{self, Blockhashes},
36    burn::Burner,
37    features::{self, Features},
38    filtered_transactions::FilteredTransactionsState,
39    l1_pricing::{self, L1PricingState},
40    l2_pricing::{self, L2PricingState},
41    merkle_accumulator::{self, MerkleAccumulator},
42    programs::Programs,
43    retryables::RetryableState,
44};
45
46/// Cached root→subspace derivations: `keccak256(sub_key)` for each static child.
47macro_rules! cached_root_key {
48    ($name:ident, $sub:expr_2021) => {
49        fn $name() -> B256 {
50            static KEY: OnceLock<B256> = OnceLock::new();
51            *KEY.get_or_init(|| keccak256($sub))
52        }
53    };
54}
55
56cached_root_key!(l1_pricing_root_key, L1_PRICING_SUBSPACE);
57cached_root_key!(l2_pricing_root_key, L2_PRICING_SUBSPACE);
58cached_root_key!(retryables_root_key, RETRYABLES_SUBSPACE);
59cached_root_key!(address_table_root_key, ADDRESS_TABLE_SUBSPACE);
60cached_root_key!(chain_owner_root_key, CHAIN_OWNER_SUBSPACE);
61cached_root_key!(send_merkle_root_key, SEND_MERKLE_SUBSPACE);
62cached_root_key!(blockhashes_root_key, BLOCKHASHES_SUBSPACE);
63cached_root_key!(chain_config_root_key, CHAIN_CONFIG_SUBSPACE);
64cached_root_key!(programs_root_key, PROGRAMS_SUBSPACE);
65cached_root_key!(features_root_key, FEATURES_SUBSPACE);
66cached_root_key!(native_token_owner_root_key, NATIVE_TOKEN_SUBSPACE);
67cached_root_key!(
68    transaction_filtering_root_key,
69    TRANSACTION_FILTERER_SUBSPACE
70);
71
72/// The maximum ArbOS version supported by this node.
73pub const MAX_ARBOS_VERSION_SUPPORTED: u64 = 60;
74
75/// Central ArbOS state aggregating all subsystem states.
76pub struct ArbosState<'a, D, B: Burner> {
77    pub arbos_version: u64,
78    pub max_arbos_version_supported: u64,
79    pub upgrade_version: StorageBackedUint64,
80    pub upgrade_timestamp: StorageBackedUint64,
81    pub network_fee_account: StorageBackedAddress,
82    pub l1_pricing_state: L1PricingState<'a, D>,
83    pub l2_pricing_state: L2PricingState<'a, D>,
84    pub retryable_state: RetryableState<'a, D>,
85    pub address_table: AddressTable<'a, D>,
86    pub chain_owners: AddressSet<'a, D>,
87    pub send_merkle_accumulator: MerkleAccumulator<'a, D>,
88    pub programs: Programs<'a, D>,
89    pub blockhashes: Blockhashes<'a, D>,
90    pub chain_id: StorageBackedBigUint,
91    pub chain_config: StorageBackedBytes,
92    pub genesis_block_num: StorageBackedUint64,
93    pub infra_fee_account: StorageBackedAddress,
94    pub brotli_compression_level: StorageBackedUint64,
95    pub backing_storage: Storage<'a, D>,
96    pub burner: B,
97    pub native_token_enabled_from_time: StorageBackedUint64,
98    pub native_token_owners: AddressSet<'a, D>,
99    pub transaction_filtering_enabled_from_time: StorageBackedUint64,
100    pub transaction_filterers: AddressSet<'a, D>,
101    pub features: Features<'a, D>,
102    pub filtered_funds_recipient: StorageBackedAddress,
103    pub filtered_transactions: FilteredTransactionsState<'a, D>,
104    pub collect_tips: StorageBackedUint64,
105}
106
107impl<'a, D, B: Burner> ArbosState<'a, D, B> {
108    // --- Accessor methods ---
109
110    pub fn arbos_version(&self) -> u64 {
111        self.arbos_version
112    }
113
114    pub fn backing_storage(&self) -> &Storage<'a, D> {
115        &self.backing_storage
116    }
117
118    pub fn brotli_compression_level<C: SystemStateBackend>(
119        &self,
120        backend: &mut C,
121    ) -> Result<u64, ArbosStateError> {
122        Ok(self.brotli_compression_level.get(backend)?)
123    }
124
125    pub fn set_brotli_compression_level<C: StorageBackend>(
126        &self,
127        backend: &mut C,
128        level: u64,
129    ) -> Result<(), ArbosStateError> {
130        const LEVEL_WELL: u64 = 11;
131        if level > LEVEL_WELL {
132            return Err(ArbosStateError::InvalidBrotliCompressionLevel);
133        }
134        Ok(self.brotli_compression_level.set(backend, level)?)
135    }
136
137    /// Whether tip collection is enabled. Always false before ArbOS 60.
138    pub fn collect_tips<C: SystemStateBackend>(
139        &self,
140        backend: &mut C,
141    ) -> Result<bool, ArbosStateError> {
142        if self.arbos_version < 60 {
143            return Ok(false);
144        }
145        Ok(self.collect_tips.get(backend)? != 0)
146    }
147
148    pub fn set_collect_tips<C: StorageBackend>(
149        &self,
150        backend: &mut C,
151        enabled: bool,
152    ) -> Result<(), ArbosStateError> {
153        Ok(self.collect_tips.set(backend, u64::from(enabled))?)
154    }
155
156    pub fn chain_id<C: SystemStateBackend>(
157        &self,
158        backend: &mut C,
159    ) -> Result<U256, ArbosStateError> {
160        Ok(self.chain_id.get(backend)?)
161    }
162
163    pub fn chain_config<C: SystemStateBackend>(
164        &self,
165        backend: &mut C,
166    ) -> Result<Vec<u8>, ArbosStateError> {
167        Ok(self.chain_config.get(backend)?)
168    }
169
170    pub fn set_chain_config<C: StorageBackend>(
171        &self,
172        backend: &mut C,
173        config: &[u8],
174    ) -> Result<(), ArbosStateError> {
175        Ok(self.chain_config.set(backend, config)?)
176    }
177
178    pub fn genesis_block_num<C: SystemStateBackend>(
179        &self,
180        backend: &mut C,
181    ) -> Result<u64, ArbosStateError> {
182        Ok(self.genesis_block_num.get(backend)?)
183    }
184
185    pub fn network_fee_account<C: SystemStateBackend>(
186        &self,
187        backend: &mut C,
188    ) -> Result<Address, ArbosStateError> {
189        Ok(self.network_fee_account.get(backend)?)
190    }
191
192    pub fn set_network_fee_account<C: StorageBackend>(
193        &self,
194        backend: &mut C,
195        account: Address,
196    ) -> Result<(), ArbosStateError> {
197        Ok(self.network_fee_account.set(backend, account)?)
198    }
199
200    pub fn infra_fee_account<C: SystemStateBackend>(
201        &self,
202        backend: &mut C,
203    ) -> Result<Address, ArbosStateError> {
204        Ok(self.infra_fee_account.get(backend)?)
205    }
206
207    pub fn set_infra_fee_account<C: StorageBackend>(
208        &self,
209        backend: &mut C,
210        account: Address,
211    ) -> Result<(), ArbosStateError> {
212        Ok(self.infra_fee_account.set(backend, account)?)
213    }
214
215    pub fn filtered_funds_recipient<C: SystemStateBackend>(
216        &self,
217        backend: &mut C,
218    ) -> Result<Address, ArbosStateError> {
219        Ok(self.filtered_funds_recipient.get(backend)?)
220    }
221
222    pub fn filtered_funds_recipient_or_default<C: SystemStateBackend>(
223        &self,
224        backend: &mut C,
225    ) -> Result<Address, ArbosStateError> {
226        let addr = self.filtered_funds_recipient.get(backend)?;
227        if addr == Address::ZERO {
228            self.network_fee_account(backend)
229        } else {
230            Ok(addr)
231        }
232    }
233
234    pub fn set_filtered_funds_recipient<C: StorageBackend>(
235        &self,
236        backend: &mut C,
237        addr: Address,
238    ) -> Result<(), ArbosStateError> {
239        Ok(self.filtered_funds_recipient.set(backend, addr)?)
240    }
241
242    pub fn native_token_management_from_time<C: SystemStateBackend>(
243        &self,
244        backend: &mut C,
245    ) -> Result<u64, ArbosStateError> {
246        Ok(self.native_token_enabled_from_time.get(backend)?)
247    }
248
249    pub fn set_native_token_management_from_time<C: StorageBackend>(
250        &self,
251        backend: &mut C,
252        time: u64,
253    ) -> Result<(), ArbosStateError> {
254        Ok(self.native_token_enabled_from_time.set(backend, time)?)
255    }
256
257    pub fn transaction_filtering_from_time<C: SystemStateBackend>(
258        &self,
259        backend: &mut C,
260    ) -> Result<u64, ArbosStateError> {
261        Ok(self.transaction_filtering_enabled_from_time.get(backend)?)
262    }
263
264    pub fn set_transaction_filtering_from_time<C: StorageBackend>(
265        &self,
266        backend: &mut C,
267        time: u64,
268    ) -> Result<(), ArbosStateError> {
269        Ok(self
270            .transaction_filtering_enabled_from_time
271            .set(backend, time)?)
272    }
273
274    pub fn get_scheduled_upgrade<C: SystemStateBackend>(
275        &self,
276        backend: &mut C,
277    ) -> Result<(u64, u64), ArbosStateError> {
278        let version = self.upgrade_version.get(backend)?;
279        let timestamp = self.upgrade_timestamp.get(backend)?;
280        Ok((version, timestamp))
281    }
282
283    pub fn schedule_arbos_upgrade<C: StorageBackend>(
284        &self,
285        backend: &mut C,
286        version: u64,
287        timestamp: u64,
288    ) -> Result<(), ArbosStateError> {
289        self.upgrade_version.set(backend, version)?;
290        Ok(self.upgrade_timestamp.set(backend, timestamp)?)
291    }
292}
293
294impl<'a, D: Database, B: Burner> ArbosState<'a, D, B> {
295    pub fn set_format_version(&mut self, version: u64) -> Result<(), ArbosStateError> {
296        self.arbos_version = version;
297        Ok(self
298            .backing_storage
299            .set_by_uint64(VERSION_OFFSET, B256::from(U256::from(version)))?)
300    }
301
302    /// Open existing ArbOS state from storage.
303    ///
304    /// Returns [`ArbosStateError::Uninitialised`] when the version slot reads
305    /// back as zero (expected only at genesis), [`ArbosStateError::Storage`]
306    /// when the backing storage layer fails, and
307    /// [`ArbosStateError::UnsupportedVersion`] when the stored version is
308    /// outside the range this build recognises.
309    pub fn open(
310        state: &'a mut revm::database::State<D>,
311        burner: B,
312    ) -> Result<Self, ArbosStateError> {
313        let backing_storage = Storage::new(state, B256::ZERO);
314
315        let arbos_version = backing_storage.get_uint64_by_uint64(VERSION_OFFSET)?;
316        if arbos_version == 0 {
317            return Err(ArbosStateError::Uninitialised);
318        }
319        if arbos_version > MAX_ARBOS_VERSION_SUPPORTED {
320            return Err(ArbosStateError::UnsupportedVersion(arbos_version));
321        }
322
323        if arbos_version >= 60 {
324            // SAFETY: see `Storage` struct-level invariant. The transient
325            // `&mut State` produced by `state_mut()` is dropped at end of
326            // statement before any live borrow into `backing_storage` is used
327            // again.
328            let state_ref = unsafe { backing_storage.state_mut() };
329            set_account_nonce(state_ref, FILTERED_TX_STATE_ADDRESS, 1);
330        }
331
332        let chain_config_key = chain_config_root_key();
333        let features_sto = backing_storage.open_sub_storage_with_key(features_root_key());
334
335        Ok(Self {
336            arbos_version,
337            max_arbos_version_supported: MAX_ARBOS_VERSION_SUPPORTED,
338            upgrade_version: StorageBackedUint64::new(B256::ZERO, UPGRADE_VERSION_OFFSET),
339            upgrade_timestamp: StorageBackedUint64::new(B256::ZERO, UPGRADE_TIMESTAMP_OFFSET),
340            network_fee_account: StorageBackedAddress::new(B256::ZERO, NETWORK_FEE_ACCOUNT_OFFSET),
341            l1_pricing_state: L1PricingState::open(
342                backing_storage.open_sub_storage_with_key(l1_pricing_root_key()),
343                arbos_version,
344            ),
345            l2_pricing_state: L2PricingState::open(
346                backing_storage.open_sub_storage_with_key(l2_pricing_root_key()),
347                arbos_version,
348            ),
349            retryable_state: RetryableState::open(
350                backing_storage.open_sub_storage_with_key(retryables_root_key()),
351                arbos_version,
352            ),
353            address_table: address_table::open_address_table(
354                backing_storage.open_sub_storage_with_key(address_table_root_key()),
355            ),
356            chain_owners: address_set::open_address_set(
357                backing_storage.open_sub_storage_with_key(chain_owner_root_key()),
358            ),
359            send_merkle_accumulator: merkle_accumulator::open_merkle_accumulator(
360                backing_storage.open_sub_storage_with_key(send_merkle_root_key()),
361            ),
362            programs: Programs::open(
363                arbos_version,
364                backing_storage.open_sub_storage_with_key(programs_root_key()),
365            ),
366            blockhashes: blockhash::open_blockhashes(
367                backing_storage.open_sub_storage_with_key(blockhashes_root_key()),
368            ),
369            chain_id: StorageBackedBigUint::new(B256::ZERO, CHAIN_ID_OFFSET),
370            chain_config: StorageBackedBytes::new(chain_config_key),
371            genesis_block_num: StorageBackedUint64::new(B256::ZERO, GENESIS_BLOCK_NUM_OFFSET),
372            infra_fee_account: StorageBackedAddress::new(B256::ZERO, INFRA_FEE_ACCOUNT_OFFSET),
373            brotli_compression_level: StorageBackedUint64::new(
374                B256::ZERO,
375                BROTLI_COMPRESSION_LEVEL_OFFSET,
376            ),
377            native_token_enabled_from_time: StorageBackedUint64::new(
378                B256::ZERO,
379                NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET,
380            ),
381            native_token_owners: address_set::open_address_set(
382                backing_storage.open_sub_storage_with_key(native_token_owner_root_key()),
383            ),
384            transaction_filtering_enabled_from_time: StorageBackedUint64::new(
385                B256::ZERO,
386                TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET,
387            ),
388            transaction_filterers: address_set::open_address_set(
389                backing_storage.open_sub_storage_with_key(transaction_filtering_root_key()),
390            ),
391            features: features::open_features(features_sto.base_key(), 0),
392            filtered_funds_recipient: StorageBackedAddress::new(
393                B256::ZERO,
394                FILTERED_FUNDS_RECIPIENT_OFFSET,
395            ),
396            filtered_transactions: FilteredTransactionsState::open(
397                backing_storage.open_account(FILTERED_TX_STATE_ADDRESS, B256::ZERO),
398            ),
399            collect_tips: StorageBackedUint64::new(B256::ZERO, COLLECT_TIPS_OFFSET),
400            backing_storage,
401            burner,
402        })
403    }
404
405    /// Checks and performs a scheduled ArbOS version upgrade if due.
406    pub fn upgrade_arbos_version_if_necessary<C: StorageBackend>(
407        &mut self,
408        backend: &mut C,
409        current_timestamp: u64,
410    ) -> Result<(), ArbosStateError> {
411        let scheduled_version = self.upgrade_version.get(backend)?;
412        let scheduled_timestamp = self.upgrade_timestamp.get(backend)?;
413
414        if scheduled_version == 0
415            || self.arbos_version >= scheduled_version
416            || current_timestamp < scheduled_timestamp
417        {
418            return Ok(());
419        }
420
421        if scheduled_version > MAX_ARBOS_VERSION_SUPPORTED {
422            return Err(ArbosStateError::UnsupportedScheduledVersion {
423                version: scheduled_version,
424                max: MAX_ARBOS_VERSION_SUPPORTED,
425            });
426        }
427
428        let old_version = self.arbos_version;
429        self.upgrade_arbos_version(backend, scheduled_version, false)?;
430
431        if old_version != self.arbos_version {
432            tracing::info!(
433                old_version,
434                new_version = self.arbos_version,
435                "ArbOS version upgraded"
436            );
437        }
438
439        Ok(())
440    }
441
442    /// Performs version upgrade steps from current version up to `upgrade_to`.
443    pub fn upgrade_arbos_version<C: StorageBackend>(
444        &mut self,
445        backend: &mut C,
446        upgrade_to: u64,
447        first_time: bool,
448    ) -> Result<(), ArbosStateError> {
449        while self.arbos_version < upgrade_to {
450            let next = self.arbos_version + 1;
451
452            match next {
453                2 => {
454                    self.l1_pricing_state
455                        .set_last_surplus(backend, U256::ZERO, false)?;
456                }
457                3 => {
458                    self.l1_pricing_state.set_per_batch_gas_cost(backend, 0)?;
459                    self.l1_pricing_state
460                        .set_amortized_cost_cap_bips(backend, u64::MAX)?;
461                }
462                4..=9 => {}
463                10 => {
464                    // SAFETY: see `Storage` struct-level invariant.
465                    let state = unsafe { self.backing_storage.state_mut() };
466                    let pool_balance =
467                        get_account_balance(state, l1_pricing::L1_PRICER_FUNDS_POOL_ADDRESS);
468                    self.l1_pricing_state
469                        .set_l1_fees_available(backend, pool_balance)?;
470                }
471                11 => {
472                    self.l1_pricing_state.set_per_batch_gas_cost(
473                        backend,
474                        l1_pricing::INITIAL_PER_BATCH_GAS_COST_V12,
475                    )?;
476
477                    let old_cap = self.l1_pricing_state.amortized_cost_cap_bips(backend)?;
478                    if old_cap == u64::MAX {
479                        self.l1_pricing_state
480                            .set_amortized_cost_cap_bips(backend, 0)?;
481                    }
482
483                    if !first_time {
484                        self.chain_owners.clear_list(backend)?;
485                    }
486                }
487                12..=19 => {}
488                20 => {
489                    self.set_brotli_compression_level(backend, 1)?;
490                }
491                21..=29 => {}
492                30 => {
493                    Programs::initialize(
494                        next,
495                        &self.backing_storage.open_sub_storage(PROGRAMS_SUBSPACE),
496                        backend,
497                    )?;
498                }
499                31 => {
500                    let mut params = self.programs.params(backend)?;
501                    params.upgrade_to_version(2)?;
502                    params.save(
503                        &self.programs.backing_storage.open_sub_storage(&[0]),
504                        backend,
505                    )?;
506                }
507                32 => {}
508                33..=39 => {}
509                40 => {
510                    // SAFETY: see `Storage` struct-level invariant.
511                    let state = unsafe { self.backing_storage.state_mut() };
512                    set_account_nonce(state, HISTORY_STORAGE_ADDRESS, 1);
513                    set_account_code(
514                        state,
515                        HISTORY_STORAGE_ADDRESS,
516                        HISTORY_STORAGE_CODE_ARBITRUM.clone(),
517                    );
518                    let mut params = self.programs.params(backend)?;
519                    params.upgrade_to_arbos_version(next)?;
520                    params.save(
521                        &self.programs.backing_storage.open_sub_storage(&[0]),
522                        backend,
523                    )?;
524                }
525                41 => {}
526                42..=49 => {}
527                50 => {
528                    let mut params = self.programs.params(backend)?;
529                    params.upgrade_to_arbos_version(next)?;
530                    params.save(
531                        &self.programs.backing_storage.open_sub_storage(&[0]),
532                        backend,
533                    )?;
534                    self.l2_pricing_state.set_max_per_tx_gas_limit(
535                        backend,
536                        l2_pricing::INITIAL_PER_TX_GAS_LIMIT_V50,
537                    )?;
538                }
539                51 => {}
540                52..=58 => {}
541                59 => {
542                    let mut params = self.programs.params(backend)?;
543                    params.upgrade_to_version(3)?;
544                    params.save(
545                        &self.programs.backing_storage.open_sub_storage(&[0]),
546                        backend,
547                    )?;
548                }
549                60 => {
550                    let mut params = self.programs.params(backend)?;
551                    params.upgrade_to_arbos_version(next)?;
552                    params.save(
553                        &self.programs.backing_storage.open_sub_storage(&[0]),
554                        backend,
555                    )?;
556                    crate::address_set::initialize_address_set(
557                        &self
558                            .backing_storage
559                            .open_sub_storage(TRANSACTION_FILTERER_SUBSPACE),
560                    )?;
561                }
562                _ => {
563                    tracing::error!(version = next, "unsupported ArbOS version");
564                    return Err(ArbosStateError::UnsupportedRunningVersion(next));
565                }
566            }
567
568            for &(addr, version) in PRECOMPILE_MIN_ARBOS_VERSIONS {
569                if version == next {
570                    // SAFETY: see `Storage` struct-level invariant.
571                    let state = unsafe { self.backing_storage.state_mut() };
572                    set_account_code(state, addr, Bytes::from_static(&[0xFE]));
573                }
574            }
575
576            self.arbos_version = next;
577            self.programs.arbos_version = next;
578            self.l1_pricing_state.arbos_version = next;
579            self.l2_pricing_state.arbos_version = next;
580            self.retryable_state.arbos_version = next;
581        }
582
583        if first_time && upgrade_to >= 6 {
584            if upgrade_to < 11 {
585                self.l1_pricing_state
586                    .set_per_batch_gas_cost(backend, l1_pricing::INITIAL_PER_BATCH_GAS_COST_V6)?;
587            }
588            self.l1_pricing_state.set_equilibration_units(
589                backend,
590                U256::from(l1_pricing::INITIAL_EQUILIBRATION_UNITS_V6),
591            )?;
592            self.l2_pricing_state.set_speed_limit_per_second(
593                backend,
594                l2_pricing::INITIAL_SPEED_LIMIT_PER_SECOND_V6,
595            )?;
596            self.l2_pricing_state
597                .set_max_per_block_gas_limit(backend, l2_pricing::INITIAL_PER_BLOCK_GAS_LIMIT_V6)?;
598        }
599
600        self.set_format_version(self.arbos_version)?;
601
602        Ok(())
603    }
604}
605
606/// Open a detached [`ArbosState`] backed by any [`StorageBackend`].
607///
608/// Reads only the version slot up front; all subsequent access goes through
609/// `backend`. Intended for callers that have a storage view but no executor
610/// state pointer — precompile handlers via `EvmInternals`, RPC tools reading
611/// historical state through a `StateProvider`, etc. The version slot is read
612/// up front so a mismatch between the pinned build and the stored ArbOS
613/// version surfaces as [`ArbosStateError::UnsupportedVersion`] instead of
614/// silently reading the wrong slot.
615pub fn arbos_from_input<S: StorageBackend, B: Burner>(
616    backend: &mut S,
617    burner: B,
618) -> Result<ArbosState<'static, Detached, B>, ArbosStateError> {
619    let version_slot = storage_key_map(&[], VERSION_OFFSET);
620    let raw_version =
621        StorageBackend::sload(backend, ARBOS_STATE_ADDRESS, version_slot).map_err(Into::into)?;
622    open_detached(raw_version, burner)
623}
624
625/// Open a detached [`ArbosState`] backed by any [`SystemStateBackend`].
626///
627/// Behaviourally identical to [`arbos_from_input`] but reads the version slot
628/// via the non-journaled [`SystemStateBackend::sload_system`] path. Intended
629/// for callers that want to skip the EVM journal — typically precompile
630/// handlers caching the ArbosState across calls within a single block.
631pub fn arbos_from_input_system<S: SystemStateBackend, B: Burner>(
632    backend: &mut S,
633    burner: B,
634) -> Result<ArbosState<'static, Detached, B>, ArbosStateError> {
635    let version_slot = storage_key_map(&[], VERSION_OFFSET);
636    let raw_version = SystemStateBackend::sload_system(backend, ARBOS_STATE_ADDRESS, version_slot)
637        .map_err(Into::into)?;
638    open_detached(raw_version, burner)
639}
640
641fn open_detached<B: Burner>(
642    raw_version: U256,
643    burner: B,
644) -> Result<ArbosState<'static, Detached, B>, ArbosStateError> {
645    let arbos_version = u64::try_from(raw_version).unwrap_or(0);
646    if arbos_version == 0 {
647        return Err(ArbosStateError::Uninitialised);
648    }
649    if arbos_version > MAX_ARBOS_VERSION_SUPPORTED {
650        return Err(ArbosStateError::UnsupportedVersion(arbos_version));
651    }
652
653    let backing_storage = Storage::<Detached>::detached(ARBOS_STATE_ADDRESS, B256::ZERO);
654    let chain_config_key = chain_config_root_key();
655    let features_sto = backing_storage.open_sub_storage_with_key(features_root_key());
656
657    Ok(ArbosState {
658        arbos_version,
659        max_arbos_version_supported: MAX_ARBOS_VERSION_SUPPORTED,
660        upgrade_version: StorageBackedUint64::new(B256::ZERO, UPGRADE_VERSION_OFFSET),
661        upgrade_timestamp: StorageBackedUint64::new(B256::ZERO, UPGRADE_TIMESTAMP_OFFSET),
662        network_fee_account: StorageBackedAddress::new(B256::ZERO, NETWORK_FEE_ACCOUNT_OFFSET),
663        l1_pricing_state: L1PricingState::open(
664            backing_storage.open_sub_storage_with_key(l1_pricing_root_key()),
665            arbos_version,
666        ),
667        l2_pricing_state: L2PricingState::open(
668            backing_storage.open_sub_storage_with_key(l2_pricing_root_key()),
669            arbos_version,
670        ),
671        retryable_state: RetryableState::open(
672            backing_storage.open_sub_storage_with_key(retryables_root_key()),
673            arbos_version,
674        ),
675        address_table: address_table::open_address_table(
676            backing_storage.open_sub_storage_with_key(address_table_root_key()),
677        ),
678        chain_owners: address_set::open_address_set(
679            backing_storage.open_sub_storage_with_key(chain_owner_root_key()),
680        ),
681        send_merkle_accumulator: merkle_accumulator::open_merkle_accumulator(
682            backing_storage.open_sub_storage_with_key(send_merkle_root_key()),
683        ),
684        programs: Programs::open(
685            arbos_version,
686            backing_storage.open_sub_storage_with_key(programs_root_key()),
687        ),
688        blockhashes: blockhash::open_blockhashes(
689            backing_storage.open_sub_storage_with_key(blockhashes_root_key()),
690        ),
691        chain_id: StorageBackedBigUint::new(B256::ZERO, CHAIN_ID_OFFSET),
692        chain_config: StorageBackedBytes::new(chain_config_key),
693        genesis_block_num: StorageBackedUint64::new(B256::ZERO, GENESIS_BLOCK_NUM_OFFSET),
694        infra_fee_account: StorageBackedAddress::new(B256::ZERO, INFRA_FEE_ACCOUNT_OFFSET),
695        brotli_compression_level: StorageBackedUint64::new(
696            B256::ZERO,
697            BROTLI_COMPRESSION_LEVEL_OFFSET,
698        ),
699        native_token_enabled_from_time: StorageBackedUint64::new(
700            B256::ZERO,
701            NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET,
702        ),
703        native_token_owners: address_set::open_address_set(
704            backing_storage.open_sub_storage_with_key(native_token_owner_root_key()),
705        ),
706        transaction_filtering_enabled_from_time: StorageBackedUint64::new(
707            B256::ZERO,
708            TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET,
709        ),
710        transaction_filterers: address_set::open_address_set(
711            backing_storage.open_sub_storage_with_key(transaction_filtering_root_key()),
712        ),
713        features: features::open_features(features_sto.base_key(), 0),
714        filtered_funds_recipient: StorageBackedAddress::new(
715            B256::ZERO,
716            FILTERED_FUNDS_RECIPIENT_OFFSET,
717        ),
718        filtered_transactions: FilteredTransactionsState::open(Storage::<Detached>::detached(
719            FILTERED_TX_STATE_ADDRESS,
720            B256::ZERO,
721        )),
722        collect_tips: StorageBackedUint64::new(B256::ZERO, COLLECT_TIPS_OFFSET),
723        backing_storage,
724        burner,
725    })
726}