arbos/arbos_state/
mod.rs

1pub mod initialize;
2
3use alloy_primitives::{Address, Bytes, B256, U256};
4use revm::Database;
5
6use arb_primitives::arbos_versions::{
7    HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE_ARBITRUM, PRECOMPILE_MIN_ARBOS_VERSIONS,
8};
9use arb_storage::{
10    get_account_balance, set_account_code, set_account_nonce, Storage, StorageBackedAddress,
11    StorageBackedBigUint, StorageBackedBytes, StorageBackedUint64, FILTERED_TX_STATE_ADDRESS,
12};
13
14use crate::{
15    address_set::{self, AddressSet},
16    address_table::{self, AddressTable},
17    blockhash::{self, Blockhashes},
18    burn::Burner,
19    features::{self, Features},
20    filtered_transactions::FilteredTransactionsState,
21    l1_pricing::{self, L1PricingState},
22    l2_pricing::{self, L2PricingState},
23    merkle_accumulator::{self, MerkleAccumulator},
24    programs::Programs,
25    retryables::RetryableState,
26};
27
28// Storage offsets for root-level ArbOS state values.
29const VERSION_OFFSET: u64 = 0;
30const UPGRADE_VERSION_OFFSET: u64 = 1;
31const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
32const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
33const CHAIN_ID_OFFSET: u64 = 4;
34const GENESIS_BLOCK_NUM_OFFSET: u64 = 5;
35const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
36const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
37const NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET: u64 = 8;
38const TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET: u64 = 9;
39const FILTERED_FUNDS_RECIPIENT_OFFSET: u64 = 10;
40
41// Subspace IDs for partitioned storage.
42const L1_PRICING_SUBSPACE: &[u8] = &[0];
43const L2_PRICING_SUBSPACE: &[u8] = &[1];
44const RETRYABLES_SUBSPACE: &[u8] = &[2];
45const ADDRESS_TABLE_SUBSPACE: &[u8] = &[3];
46const CHAIN_OWNER_SUBSPACE: &[u8] = &[4];
47const SEND_MERKLE_SUBSPACE: &[u8] = &[5];
48const BLOCKHASHES_SUBSPACE: &[u8] = &[6];
49const CHAIN_CONFIG_SUBSPACE: &[u8] = &[7];
50const PROGRAMS_SUBSPACE: &[u8] = &[8];
51const FEATURES_SUBSPACE: &[u8] = &[9];
52const NATIVE_TOKEN_OWNER_SUBSPACE: &[u8] = &[10];
53const TRANSACTION_FILTERING_SUBSPACE: &[u8] = &[11];
54
55/// The maximum ArbOS version supported by this node.
56pub const MAX_ARBOS_VERSION_SUPPORTED: u64 = 60;
57
58/// Central ArbOS state aggregating all subsystem states.
59pub struct ArbosState<D, B: Burner> {
60    pub arbos_version: u64,
61    pub max_arbos_version_supported: u64,
62    pub upgrade_version: StorageBackedUint64<D>,
63    pub upgrade_timestamp: StorageBackedUint64<D>,
64    pub network_fee_account: StorageBackedAddress<D>,
65    pub l1_pricing_state: L1PricingState<D>,
66    pub l2_pricing_state: L2PricingState<D>,
67    pub retryable_state: RetryableState<D>,
68    pub address_table: AddressTable<D>,
69    pub chain_owners: AddressSet<D>,
70    pub send_merkle_accumulator: MerkleAccumulator<D>,
71    pub programs: Programs<D>,
72    pub blockhashes: Blockhashes<D>,
73    pub chain_id: StorageBackedBigUint<D>,
74    pub chain_config: StorageBackedBytes<D>,
75    pub genesis_block_num: StorageBackedUint64<D>,
76    pub infra_fee_account: StorageBackedAddress<D>,
77    pub brotli_compression_level: StorageBackedUint64<D>,
78    pub backing_storage: Storage<D>,
79    pub burner: B,
80    pub native_token_enabled_from_time: StorageBackedUint64<D>,
81    pub native_token_owners: AddressSet<D>,
82    pub transaction_filtering_enabled_from_time: StorageBackedUint64<D>,
83    pub transaction_filterers: AddressSet<D>,
84    pub features: Features<D>,
85    pub filtered_funds_recipient: StorageBackedAddress<D>,
86    pub filtered_transactions: FilteredTransactionsState<D>,
87}
88
89impl<D: Database, B: Burner> ArbosState<D, B> {
90    /// Open existing ArbOS state from storage.
91    pub fn open(state: *mut revm::database::State<D>, burner: B) -> Result<Self, ()> {
92        let backing_storage = Storage::new(state, B256::ZERO);
93
94        let arbos_version = backing_storage.get_uint64_by_uint64(VERSION_OFFSET)?;
95        if arbos_version == 0 {
96            return Err(());
97        }
98
99        let chain_config_sto = backing_storage.open_sub_storage(CHAIN_CONFIG_SUBSPACE);
100        let features_sto = backing_storage.open_sub_storage(FEATURES_SUBSPACE);
101
102        Ok(Self {
103            arbos_version,
104            max_arbos_version_supported: MAX_ARBOS_VERSION_SUPPORTED,
105            upgrade_version: StorageBackedUint64::new(state, B256::ZERO, UPGRADE_VERSION_OFFSET),
106            upgrade_timestamp: StorageBackedUint64::new(
107                state,
108                B256::ZERO,
109                UPGRADE_TIMESTAMP_OFFSET,
110            ),
111            network_fee_account: StorageBackedAddress::new(
112                state,
113                B256::ZERO,
114                NETWORK_FEE_ACCOUNT_OFFSET,
115            ),
116            l1_pricing_state: L1PricingState::open(
117                backing_storage.open_sub_storage(L1_PRICING_SUBSPACE),
118                arbos_version,
119            ),
120            l2_pricing_state: L2PricingState::open(
121                backing_storage.open_sub_storage(L2_PRICING_SUBSPACE),
122                arbos_version,
123            ),
124            retryable_state: RetryableState::open(
125                backing_storage.open_sub_storage(RETRYABLES_SUBSPACE),
126            ),
127            address_table: address_table::open_address_table(
128                backing_storage.open_sub_storage(ADDRESS_TABLE_SUBSPACE),
129            ),
130            chain_owners: address_set::open_address_set(
131                backing_storage.open_sub_storage(CHAIN_OWNER_SUBSPACE),
132            ),
133            send_merkle_accumulator: merkle_accumulator::open_merkle_accumulator(
134                backing_storage.open_sub_storage(SEND_MERKLE_SUBSPACE),
135            ),
136            programs: Programs::open(
137                arbos_version,
138                backing_storage.open_sub_storage(PROGRAMS_SUBSPACE),
139            ),
140            blockhashes: blockhash::open_blockhashes(
141                backing_storage.open_sub_storage(BLOCKHASHES_SUBSPACE),
142            ),
143            chain_id: StorageBackedBigUint::new(state, B256::ZERO, CHAIN_ID_OFFSET),
144            chain_config: StorageBackedBytes::new(chain_config_sto),
145            genesis_block_num: StorageBackedUint64::new(
146                state,
147                B256::ZERO,
148                GENESIS_BLOCK_NUM_OFFSET,
149            ),
150            infra_fee_account: StorageBackedAddress::new(
151                state,
152                B256::ZERO,
153                INFRA_FEE_ACCOUNT_OFFSET,
154            ),
155            brotli_compression_level: StorageBackedUint64::new(
156                state,
157                B256::ZERO,
158                BROTLI_COMPRESSION_LEVEL_OFFSET,
159            ),
160            native_token_enabled_from_time: StorageBackedUint64::new(
161                state,
162                B256::ZERO,
163                NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET,
164            ),
165            native_token_owners: address_set::open_address_set(
166                backing_storage.open_sub_storage(NATIVE_TOKEN_OWNER_SUBSPACE),
167            ),
168            transaction_filtering_enabled_from_time: StorageBackedUint64::new(
169                state,
170                B256::ZERO,
171                TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET,
172            ),
173            transaction_filterers: address_set::open_address_set(
174                backing_storage.open_sub_storage(TRANSACTION_FILTERING_SUBSPACE),
175            ),
176            features: features::open_features(state, features_sto.base_key(), 0),
177            filtered_funds_recipient: StorageBackedAddress::new(
178                state,
179                B256::ZERO,
180                FILTERED_FUNDS_RECIPIENT_OFFSET,
181            ),
182            filtered_transactions: FilteredTransactionsState::open(Storage::new_with_account(
183                state,
184                B256::ZERO,
185                FILTERED_TX_STATE_ADDRESS,
186            )),
187            backing_storage,
188            burner,
189        })
190    }
191
192    // --- Accessor methods ---
193
194    pub fn arbos_version(&self) -> u64 {
195        self.arbos_version
196    }
197
198    pub fn backing_storage(&self) -> &Storage<D> {
199        &self.backing_storage
200    }
201
202    pub fn set_format_version(&mut self, version: u64) -> Result<(), ()> {
203        self.arbos_version = version;
204        self.backing_storage
205            .set_by_uint64(VERSION_OFFSET, B256::from(U256::from(version)))
206    }
207
208    pub fn brotli_compression_level(&self) -> Result<u64, ()> {
209        self.brotli_compression_level.get()
210    }
211
212    pub fn set_brotli_compression_level(&self, level: u64) -> Result<(), ()> {
213        self.brotli_compression_level.set(level)
214    }
215
216    pub fn chain_id(&self) -> Result<U256, ()> {
217        self.chain_id.get()
218    }
219
220    pub fn chain_config(&self) -> Result<Vec<u8>, ()> {
221        self.chain_config.get()
222    }
223
224    pub fn set_chain_config(&self, config: &[u8]) -> Result<(), ()> {
225        self.chain_config.set(config)
226    }
227
228    pub fn genesis_block_num(&self) -> Result<u64, ()> {
229        self.genesis_block_num.get()
230    }
231
232    pub fn network_fee_account(&self) -> Result<Address, ()> {
233        self.network_fee_account.get()
234    }
235
236    pub fn set_network_fee_account(&self, account: Address) -> Result<(), ()> {
237        self.network_fee_account.set(account)
238    }
239
240    pub fn infra_fee_account(&self) -> Result<Address, ()> {
241        self.infra_fee_account.get()
242    }
243
244    pub fn set_infra_fee_account(&self, account: Address) -> Result<(), ()> {
245        self.infra_fee_account.set(account)
246    }
247
248    pub fn filtered_funds_recipient(&self) -> Result<Address, ()> {
249        self.filtered_funds_recipient.get()
250    }
251
252    pub fn filtered_funds_recipient_or_default(&self) -> Result<Address, ()> {
253        let addr = self.filtered_funds_recipient.get()?;
254        if addr == Address::ZERO {
255            self.network_fee_account()
256        } else {
257            Ok(addr)
258        }
259    }
260
261    pub fn set_filtered_funds_recipient(&self, addr: Address) -> Result<(), ()> {
262        self.filtered_funds_recipient.set(addr)
263    }
264
265    pub fn native_token_management_from_time(&self) -> Result<u64, ()> {
266        self.native_token_enabled_from_time.get()
267    }
268
269    pub fn set_native_token_management_from_time(&self, time: u64) -> Result<(), ()> {
270        self.native_token_enabled_from_time.set(time)
271    }
272
273    pub fn transaction_filtering_from_time(&self) -> Result<u64, ()> {
274        self.transaction_filtering_enabled_from_time.get()
275    }
276
277    pub fn set_transaction_filtering_from_time(&self, time: u64) -> Result<(), ()> {
278        self.transaction_filtering_enabled_from_time.set(time)
279    }
280
281    pub fn get_scheduled_upgrade(&self) -> Result<(u64, u64), ()> {
282        let version = self.upgrade_version.get()?;
283        let timestamp = self.upgrade_timestamp.get()?;
284        Ok((version, timestamp))
285    }
286
287    pub fn schedule_arbos_upgrade(&self, version: u64, timestamp: u64) -> Result<(), ()> {
288        self.upgrade_version.set(version)?;
289        self.upgrade_timestamp.set(timestamp)
290    }
291
292    /// Checks and performs a scheduled ArbOS version upgrade if due.
293    pub fn upgrade_arbos_version_if_necessary(&mut self, current_timestamp: u64) -> Result<(), ()> {
294        let scheduled_version = self.upgrade_version.get()?;
295        let scheduled_timestamp = self.upgrade_timestamp.get()?;
296
297        // Check: arbosVersion < upgradeTo && currentTimestamp >= flagday
298        if scheduled_version == 0
299            || self.arbos_version >= scheduled_version
300            || current_timestamp < scheduled_timestamp
301        {
302            return Ok(());
303        }
304
305        if scheduled_version > MAX_ARBOS_VERSION_SUPPORTED {
306            return Err(());
307        }
308
309        let old_version = self.arbos_version;
310        self.upgrade_arbos_version(scheduled_version, false)?;
311
312        // The scheduled upgrade fields are NOT cleared after upgrade.
313        // They remain in storage and are simply ignored because the
314        // arbos_version >= scheduled_version check prevents re-upgrade.
315
316        if old_version != self.arbos_version {
317            tracing::info!(
318                old_version,
319                new_version = self.arbos_version,
320                "ArbOS version upgraded"
321            );
322        }
323
324        Ok(())
325    }
326
327    /// Performs version upgrade steps from current version up to `upgrade_to`.
328    ///
329    /// `first_time` is true during genesis initialization, which affects
330    /// some initial parameter values.
331    pub fn upgrade_arbos_version(&mut self, upgrade_to: u64, first_time: bool) -> Result<(), ()> {
332        while self.arbos_version < upgrade_to {
333            let next = self.arbos_version + 1;
334
335            match next {
336                2 => {
337                    self.l1_pricing_state.set_last_surplus(U256::ZERO, false)?;
338                }
339                3 => {
340                    self.l1_pricing_state.set_per_batch_gas_cost(0)?;
341                    self.l1_pricing_state
342                        .set_amortized_cost_cap_bips(u64::MAX)?;
343                }
344                4..=9 => {
345                    // No state changes needed
346                }
347                10 => {
348                    let state = unsafe { &mut *self.backing_storage.state };
349                    let pool_balance =
350                        get_account_balance(state, l1_pricing::L1_PRICER_FUNDS_POOL_ADDRESS);
351                    self.l1_pricing_state.set_l1_fees_available(pool_balance)?;
352                }
353                11 => {
354                    self.l1_pricing_state
355                        .set_per_batch_gas_cost(l1_pricing::INITIAL_PER_BATCH_GAS_COST_V12)?;
356
357                    // Fix: math.MaxUint64 was incorrectly used for "disabled";
358                    // the correct disable value is 0.
359                    let old_cap = self.l1_pricing_state.amortized_cost_cap_bips()?;
360                    if old_cap == u64::MAX {
361                        self.l1_pricing_state.set_amortized_cost_cap_bips(0)?;
362                    }
363
364                    if !first_time {
365                        self.chain_owners.clear_list()?;
366                    }
367                }
368                // 12..=19: reserved for Orbit chains
369                12..=19 => {}
370                20 => {
371                    self.set_brotli_compression_level(1)?;
372                }
373                // 21..=29: reserved for Orbit chains
374                21..=29 => {}
375                30 => {
376                    Programs::initialize(
377                        next,
378                        &self.backing_storage.open_sub_storage(PROGRAMS_SUBSPACE),
379                    );
380                }
381                31 => {
382                    let mut params = self.programs.params()?;
383                    params.upgrade_to_version(2).map_err(|_| ())?;
384                    params
385                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
386                        .map_err(|_| ())?;
387                }
388                32 => {
389                    // No state changes needed
390                }
391                // 33..=39: reserved for Orbit chains
392                33..=39 => {}
393                40 => {
394                    // EIP-2935: historical block hashes
395                    let state = unsafe { &mut *self.backing_storage.state };
396                    set_account_nonce(state, HISTORY_STORAGE_ADDRESS, 1);
397                    set_account_code(
398                        state,
399                        HISTORY_STORAGE_ADDRESS,
400                        HISTORY_STORAGE_CODE_ARBITRUM.clone(),
401                    );
402                    // Upgrade Stylus params for version 40
403                    let mut params = self.programs.params()?;
404                    params.upgrade_to_arbos_version(next).map_err(|_| ())?;
405                    params
406                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
407                        .map_err(|_| ())?;
408                }
409                41 => {
410                    // No state changes needed
411                }
412                // 42..=49: reserved for Orbit chains
413                42..=49 => {}
414                50 => {
415                    let mut params = self.programs.params()?;
416                    params.upgrade_to_arbos_version(next).map_err(|_| ())?;
417                    params
418                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
419                        .map_err(|_| ())?;
420                    self.l2_pricing_state
421                        .set_max_per_tx_gas_limit(l2_pricing::INITIAL_PER_TX_GAS_LIMIT_V50)?;
422                }
423                51 => {
424                    // No state changes needed
425                }
426                // 52..=59: reserved for Orbit chains
427                52..=59 => {}
428                60 => {
429                    let mut params = self.programs.params()?;
430                    params.upgrade_to_arbos_version(next).map_err(|_| ())?;
431                    params
432                        .save(&self.programs.backing_storage.open_sub_storage(&[0]))
433                        .map_err(|_| ())?;
434                    // Initialize transaction filtering address set
435                    crate::address_set::initialize_address_set(
436                        &self
437                            .backing_storage
438                            .open_sub_storage(TRANSACTION_FILTERING_SUBSPACE),
439                    )?;
440                }
441                _ => {
442                    tracing::error!(version = next, "unsupported ArbOS version");
443                    return Err(());
444                }
445            }
446
447            // Install precompile code for newly introduced precompiles
448            for &(addr, version) in PRECOMPILE_MIN_ARBOS_VERSIONS {
449                if version == next {
450                    let state = unsafe { &mut *self.backing_storage.state };
451                    set_account_code(state, addr, Bytes::from_static(&[0xFE])); // INVALID opcode
452                }
453            }
454
455            self.arbos_version = next;
456            self.programs.arbos_version = next;
457            self.l1_pricing_state.arbos_version = next;
458            self.l2_pricing_state.arbos_version = next;
459        }
460
461        // First-time initialization overrides
462        if first_time && upgrade_to >= 6 {
463            if upgrade_to < 11 {
464                self.l1_pricing_state
465                    .set_per_batch_gas_cost(l1_pricing::INITIAL_PER_BATCH_GAS_COST_V6)?;
466            }
467            self.l1_pricing_state
468                .set_equilibration_units(U256::from(l1_pricing::INITIAL_EQUILIBRATION_UNITS_V6))?;
469            self.l2_pricing_state
470                .set_speed_limit_per_second(l2_pricing::INITIAL_SPEED_LIMIT_PER_SECOND_V6)?;
471            self.l2_pricing_state
472                .set_max_per_block_gas_limit(l2_pricing::INITIAL_PER_BLOCK_GAS_LIMIT_V6)?;
473        }
474
475        // Persist the final version
476        self.set_format_version(self.arbos_version)?;
477
478        Ok(())
479    }
480}