1pub mod initialize;
2
3use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
4use revm::Database;
5use std::sync::OnceLock;
6
7use arb_primitives::arbos_versions::{
8 HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE_ARBITRUM, PRECOMPILE_MIN_ARBOS_VERSIONS,
9};
10use arb_storage::{
11 get_account_balance, set_account_code, set_account_nonce, Storage, StorageBackedAddress,
12 StorageBackedBigUint, StorageBackedBytes, StorageBackedUint64, FILTERED_TX_STATE_ADDRESS,
13};
14
15use crate::{
16 address_set::{self, AddressSet},
17 address_table::{self, AddressTable},
18 blockhash::{self, Blockhashes},
19 burn::Burner,
20 features::{self, Features},
21 filtered_transactions::FilteredTransactionsState,
22 l1_pricing::{self, L1PricingState},
23 l2_pricing::{self, L2PricingState},
24 merkle_accumulator::{self, MerkleAccumulator},
25 programs::Programs,
26 retryables::RetryableState,
27};
28
29const VERSION_OFFSET: u64 = 0;
31const UPGRADE_VERSION_OFFSET: u64 = 1;
32const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
33const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
34const CHAIN_ID_OFFSET: u64 = 4;
35const GENESIS_BLOCK_NUM_OFFSET: u64 = 5;
36const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
37const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
38const NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET: u64 = 8;
39const TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET: u64 = 9;
40const FILTERED_FUNDS_RECIPIENT_OFFSET: u64 = 10;
41const COLLECT_TIPS_OFFSET: u64 = 11;
42
43pub(crate) const L1_PRICING_SUBSPACE: &[u8] = &[0];
45pub(crate) const L2_PRICING_SUBSPACE: &[u8] = &[1];
46pub(crate) const RETRYABLES_SUBSPACE: &[u8] = &[2];
47const ADDRESS_TABLE_SUBSPACE: &[u8] = &[3];
48const CHAIN_OWNER_SUBSPACE: &[u8] = &[4];
49const SEND_MERKLE_SUBSPACE: &[u8] = &[5];
50const BLOCKHASHES_SUBSPACE: &[u8] = &[6];
51const CHAIN_CONFIG_SUBSPACE: &[u8] = &[7];
52pub(crate) const PROGRAMS_SUBSPACE: &[u8] = &[8];
53const FEATURES_SUBSPACE: &[u8] = &[9];
54const NATIVE_TOKEN_OWNER_SUBSPACE: &[u8] = &[10];
55const TRANSACTION_FILTERING_SUBSPACE: &[u8] = &[11];
56
57macro_rules! cached_root_key {
59 ($name:ident, $sub:expr) => {
60 fn $name() -> B256 {
61 static KEY: OnceLock<B256> = OnceLock::new();
62 *KEY.get_or_init(|| keccak256($sub))
63 }
64 };
65}
66
67cached_root_key!(l1_pricing_root_key, L1_PRICING_SUBSPACE);
68cached_root_key!(l2_pricing_root_key, L2_PRICING_SUBSPACE);
69cached_root_key!(retryables_root_key, RETRYABLES_SUBSPACE);
70cached_root_key!(address_table_root_key, ADDRESS_TABLE_SUBSPACE);
71cached_root_key!(chain_owner_root_key, CHAIN_OWNER_SUBSPACE);
72cached_root_key!(send_merkle_root_key, SEND_MERKLE_SUBSPACE);
73cached_root_key!(blockhashes_root_key, BLOCKHASHES_SUBSPACE);
74cached_root_key!(chain_config_root_key, CHAIN_CONFIG_SUBSPACE);
75cached_root_key!(programs_root_key, PROGRAMS_SUBSPACE);
76cached_root_key!(features_root_key, FEATURES_SUBSPACE);
77cached_root_key!(native_token_owner_root_key, NATIVE_TOKEN_OWNER_SUBSPACE);
78cached_root_key!(
79 transaction_filtering_root_key,
80 TRANSACTION_FILTERING_SUBSPACE
81);
82
83pub const MAX_ARBOS_VERSION_SUPPORTED: u64 = 60;
85
86pub struct ArbosState<D, B: Burner> {
88 pub arbos_version: u64,
89 pub max_arbos_version_supported: u64,
90 pub upgrade_version: StorageBackedUint64<D>,
91 pub upgrade_timestamp: StorageBackedUint64<D>,
92 pub network_fee_account: StorageBackedAddress<D>,
93 pub l1_pricing_state: L1PricingState<D>,
94 pub l2_pricing_state: L2PricingState<D>,
95 pub retryable_state: RetryableState<D>,
96 pub address_table: AddressTable<D>,
97 pub chain_owners: AddressSet<D>,
98 pub send_merkle_accumulator: MerkleAccumulator<D>,
99 pub programs: Programs<D>,
100 pub blockhashes: Blockhashes<D>,
101 pub chain_id: StorageBackedBigUint<D>,
102 pub chain_config: StorageBackedBytes<D>,
103 pub genesis_block_num: StorageBackedUint64<D>,
104 pub infra_fee_account: StorageBackedAddress<D>,
105 pub brotli_compression_level: StorageBackedUint64<D>,
106 pub backing_storage: Storage<D>,
107 pub burner: B,
108 pub native_token_enabled_from_time: StorageBackedUint64<D>,
109 pub native_token_owners: AddressSet<D>,
110 pub transaction_filtering_enabled_from_time: StorageBackedUint64<D>,
111 pub transaction_filterers: AddressSet<D>,
112 pub features: Features<D>,
113 pub filtered_funds_recipient: StorageBackedAddress<D>,
114 pub filtered_transactions: FilteredTransactionsState<D>,
115 pub collect_tips: StorageBackedUint64<D>,
116}
117
118impl<D: Database, B: Burner> ArbosState<D, B> {
119 pub fn open(state: *mut revm::database::State<D>, burner: B) -> Result<Self, ()> {
121 let backing_storage = Storage::new(state, B256::ZERO);
122
123 let arbos_version = backing_storage.get_uint64_by_uint64(VERSION_OFFSET)?;
124 if arbos_version == 0 {
125 return Err(());
126 }
127
128 let chain_config_sto = backing_storage.open_sub_storage_with_key(chain_config_root_key());
129 let features_sto = backing_storage.open_sub_storage_with_key(features_root_key());
130
131 Ok(Self {
132 arbos_version,
133 max_arbos_version_supported: MAX_ARBOS_VERSION_SUPPORTED,
134 upgrade_version: StorageBackedUint64::new(state, B256::ZERO, UPGRADE_VERSION_OFFSET),
135 upgrade_timestamp: StorageBackedUint64::new(
136 state,
137 B256::ZERO,
138 UPGRADE_TIMESTAMP_OFFSET,
139 ),
140 network_fee_account: StorageBackedAddress::new(
141 state,
142 B256::ZERO,
143 NETWORK_FEE_ACCOUNT_OFFSET,
144 ),
145 l1_pricing_state: L1PricingState::open(
146 backing_storage.open_sub_storage_with_key(l1_pricing_root_key()),
147 arbos_version,
148 ),
149 l2_pricing_state: L2PricingState::open(
150 backing_storage.open_sub_storage_with_key(l2_pricing_root_key()),
151 arbos_version,
152 ),
153 retryable_state: RetryableState::open(
154 backing_storage.open_sub_storage_with_key(retryables_root_key()),
155 ),
156 address_table: address_table::open_address_table(
157 backing_storage.open_sub_storage_with_key(address_table_root_key()),
158 ),
159 chain_owners: address_set::open_address_set(
160 backing_storage.open_sub_storage_with_key(chain_owner_root_key()),
161 ),
162 send_merkle_accumulator: merkle_accumulator::open_merkle_accumulator(
163 backing_storage.open_sub_storage_with_key(send_merkle_root_key()),
164 ),
165 programs: Programs::open(
166 arbos_version,
167 backing_storage.open_sub_storage_with_key(programs_root_key()),
168 ),
169 blockhashes: blockhash::open_blockhashes(
170 backing_storage.open_sub_storage_with_key(blockhashes_root_key()),
171 ),
172 chain_id: StorageBackedBigUint::new(state, B256::ZERO, CHAIN_ID_OFFSET),
173 chain_config: StorageBackedBytes::new(chain_config_sto),
174 genesis_block_num: StorageBackedUint64::new(
175 state,
176 B256::ZERO,
177 GENESIS_BLOCK_NUM_OFFSET,
178 ),
179 infra_fee_account: StorageBackedAddress::new(
180 state,
181 B256::ZERO,
182 INFRA_FEE_ACCOUNT_OFFSET,
183 ),
184 brotli_compression_level: StorageBackedUint64::new(
185 state,
186 B256::ZERO,
187 BROTLI_COMPRESSION_LEVEL_OFFSET,
188 ),
189 native_token_enabled_from_time: StorageBackedUint64::new(
190 state,
191 B256::ZERO,
192 NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET,
193 ),
194 native_token_owners: address_set::open_address_set(
195 backing_storage.open_sub_storage_with_key(native_token_owner_root_key()),
196 ),
197 transaction_filtering_enabled_from_time: StorageBackedUint64::new(
198 state,
199 B256::ZERO,
200 TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET,
201 ),
202 transaction_filterers: address_set::open_address_set(
203 backing_storage.open_sub_storage_with_key(transaction_filtering_root_key()),
204 ),
205 features: features::open_features(state, features_sto.base_key(), 0),
206 filtered_funds_recipient: StorageBackedAddress::new(
207 state,
208 B256::ZERO,
209 FILTERED_FUNDS_RECIPIENT_OFFSET,
210 ),
211 filtered_transactions: FilteredTransactionsState::open(Storage::new_with_account(
212 state,
213 B256::ZERO,
214 FILTERED_TX_STATE_ADDRESS,
215 )),
216 collect_tips: StorageBackedUint64::new(state, B256::ZERO, COLLECT_TIPS_OFFSET),
217 backing_storage,
218 burner,
219 })
220 }
221
222 pub fn arbos_version(&self) -> u64 {
225 self.arbos_version
226 }
227
228 pub fn backing_storage(&self) -> &Storage<D> {
229 &self.backing_storage
230 }
231
232 pub fn set_format_version(&mut self, version: u64) -> Result<(), ()> {
233 self.arbos_version = version;
234 self.backing_storage
235 .set_by_uint64(VERSION_OFFSET, B256::from(U256::from(version)))
236 }
237
238 pub fn brotli_compression_level(&self) -> Result<u64, ()> {
239 self.brotli_compression_level.get()
240 }
241
242 pub fn set_brotli_compression_level(&self, level: u64) -> Result<(), ()> {
243 self.brotli_compression_level.set(level)
244 }
245
246 pub fn collect_tips(&self) -> Result<bool, ()> {
248 if self.arbos_version < 60 {
249 return Ok(false);
250 }
251 Ok(self.collect_tips.get()? != 0)
252 }
253
254 pub fn chain_id(&self) -> Result<U256, ()> {
255 self.chain_id.get()
256 }
257
258 pub fn chain_config(&self) -> Result<Vec<u8>, ()> {
259 self.chain_config.get()
260 }
261
262 pub fn set_chain_config(&self, config: &[u8]) -> Result<(), ()> {
263 self.chain_config.set(config)
264 }
265
266 pub fn genesis_block_num(&self) -> Result<u64, ()> {
267 self.genesis_block_num.get()
268 }
269
270 pub fn network_fee_account(&self) -> Result<Address, ()> {
271 self.network_fee_account.get()
272 }
273
274 pub fn set_network_fee_account(&self, account: Address) -> Result<(), ()> {
275 self.network_fee_account.set(account)
276 }
277
278 pub fn infra_fee_account(&self) -> Result<Address, ()> {
279 self.infra_fee_account.get()
280 }
281
282 pub fn set_infra_fee_account(&self, account: Address) -> Result<(), ()> {
283 self.infra_fee_account.set(account)
284 }
285
286 pub fn filtered_funds_recipient(&self) -> Result<Address, ()> {
287 self.filtered_funds_recipient.get()
288 }
289
290 pub fn filtered_funds_recipient_or_default(&self) -> Result<Address, ()> {
291 let addr = self.filtered_funds_recipient.get()?;
292 if addr == Address::ZERO {
293 self.network_fee_account()
294 } else {
295 Ok(addr)
296 }
297 }
298
299 pub fn set_filtered_funds_recipient(&self, addr: Address) -> Result<(), ()> {
300 self.filtered_funds_recipient.set(addr)
301 }
302
303 pub fn native_token_management_from_time(&self) -> Result<u64, ()> {
304 self.native_token_enabled_from_time.get()
305 }
306
307 pub fn set_native_token_management_from_time(&self, time: u64) -> Result<(), ()> {
308 self.native_token_enabled_from_time.set(time)
309 }
310
311 pub fn transaction_filtering_from_time(&self) -> Result<u64, ()> {
312 self.transaction_filtering_enabled_from_time.get()
313 }
314
315 pub fn set_transaction_filtering_from_time(&self, time: u64) -> Result<(), ()> {
316 self.transaction_filtering_enabled_from_time.set(time)
317 }
318
319 pub fn get_scheduled_upgrade(&self) -> Result<(u64, u64), ()> {
320 let version = self.upgrade_version.get()?;
321 let timestamp = self.upgrade_timestamp.get()?;
322 Ok((version, timestamp))
323 }
324
325 pub fn schedule_arbos_upgrade(&self, version: u64, timestamp: u64) -> Result<(), ()> {
326 self.upgrade_version.set(version)?;
327 self.upgrade_timestamp.set(timestamp)
328 }
329
330 pub fn upgrade_arbos_version_if_necessary(&mut self, current_timestamp: u64) -> Result<(), ()> {
332 let scheduled_version = self.upgrade_version.get()?;
333 let scheduled_timestamp = self.upgrade_timestamp.get()?;
334
335 if scheduled_version == 0
337 || self.arbos_version >= scheduled_version
338 || current_timestamp < scheduled_timestamp
339 {
340 return Ok(());
341 }
342
343 if scheduled_version > MAX_ARBOS_VERSION_SUPPORTED {
344 return Err(());
345 }
346
347 let old_version = self.arbos_version;
348 self.upgrade_arbos_version(scheduled_version, false)?;
349
350 if old_version != self.arbos_version {
355 tracing::info!(
356 old_version,
357 new_version = self.arbos_version,
358 "ArbOS version upgraded"
359 );
360 }
361
362 Ok(())
363 }
364
365 pub fn upgrade_arbos_version(&mut self, upgrade_to: u64, first_time: bool) -> Result<(), ()> {
370 while self.arbos_version < upgrade_to {
371 let next = self.arbos_version + 1;
372
373 match next {
374 2 => {
375 self.l1_pricing_state.set_last_surplus(U256::ZERO, false)?;
376 }
377 3 => {
378 self.l1_pricing_state.set_per_batch_gas_cost(0)?;
379 self.l1_pricing_state
380 .set_amortized_cost_cap_bips(u64::MAX)?;
381 }
382 4..=9 => {
383 }
385 10 => {
386 let state = unsafe { &mut *self.backing_storage.state };
387 let pool_balance =
388 get_account_balance(state, l1_pricing::L1_PRICER_FUNDS_POOL_ADDRESS);
389 self.l1_pricing_state.set_l1_fees_available(pool_balance)?;
390 }
391 11 => {
392 self.l1_pricing_state
393 .set_per_batch_gas_cost(l1_pricing::INITIAL_PER_BATCH_GAS_COST_V12)?;
394
395 let old_cap = self.l1_pricing_state.amortized_cost_cap_bips()?;
398 if old_cap == u64::MAX {
399 self.l1_pricing_state.set_amortized_cost_cap_bips(0)?;
400 }
401
402 if !first_time {
403 self.chain_owners.clear_list()?;
404 }
405 }
406 12..=19 => {}
408 20 => {
409 self.set_brotli_compression_level(1)?;
410 }
411 21..=29 => {}
413 30 => {
414 Programs::initialize(
415 next,
416 &self.backing_storage.open_sub_storage(PROGRAMS_SUBSPACE),
417 );
418 }
419 31 => {
420 let mut params = self.programs.params()?;
421 params.upgrade_to_version(2).map_err(|_| ())?;
422 params
423 .save(&self.programs.backing_storage.open_sub_storage(&[0]))
424 .map_err(|_| ())?;
425 }
426 32 => {
427 }
429 33..=39 => {}
431 40 => {
432 let state = unsafe { &mut *self.backing_storage.state };
434 set_account_nonce(state, HISTORY_STORAGE_ADDRESS, 1);
435 set_account_code(
436 state,
437 HISTORY_STORAGE_ADDRESS,
438 HISTORY_STORAGE_CODE_ARBITRUM.clone(),
439 );
440 let mut params = self.programs.params()?;
442 params.upgrade_to_arbos_version(next).map_err(|_| ())?;
443 params
444 .save(&self.programs.backing_storage.open_sub_storage(&[0]))
445 .map_err(|_| ())?;
446 }
447 41 => {
448 }
450 42..=49 => {}
452 50 => {
453 let mut params = self.programs.params()?;
454 params.upgrade_to_arbos_version(next).map_err(|_| ())?;
455 params
456 .save(&self.programs.backing_storage.open_sub_storage(&[0]))
457 .map_err(|_| ())?;
458 self.l2_pricing_state
459 .set_max_per_tx_gas_limit(l2_pricing::INITIAL_PER_TX_GAS_LIMIT_V50)?;
460 }
461 51 => {
462 }
464 52..=59 => {}
466 60 => {
467 let mut params = self.programs.params()?;
468 params.upgrade_to_arbos_version(next).map_err(|_| ())?;
469 params
470 .save(&self.programs.backing_storage.open_sub_storage(&[0]))
471 .map_err(|_| ())?;
472 crate::address_set::initialize_address_set(
474 &self
475 .backing_storage
476 .open_sub_storage(TRANSACTION_FILTERING_SUBSPACE),
477 )?;
478 }
479 _ => {
480 tracing::error!(version = next, "unsupported ArbOS version");
481 return Err(());
482 }
483 }
484
485 for &(addr, version) in PRECOMPILE_MIN_ARBOS_VERSIONS {
487 if version == next {
488 let state = unsafe { &mut *self.backing_storage.state };
489 set_account_code(state, addr, Bytes::from_static(&[0xFE])); }
491 }
492
493 self.arbos_version = next;
494 self.programs.arbos_version = next;
495 self.l1_pricing_state.arbos_version = next;
496 self.l2_pricing_state.arbos_version = next;
497 }
498
499 if first_time && upgrade_to >= 6 {
501 if upgrade_to < 11 {
502 self.l1_pricing_state
503 .set_per_batch_gas_cost(l1_pricing::INITIAL_PER_BATCH_GAS_COST_V6)?;
504 }
505 self.l1_pricing_state
506 .set_equilibration_units(U256::from(l1_pricing::INITIAL_EQUILIBRATION_UNITS_V6))?;
507 self.l2_pricing_state
508 .set_speed_limit_per_second(l2_pricing::INITIAL_SPEED_LIMIT_PER_SECOND_V6)?;
509 self.l2_pricing_state
510 .set_max_per_block_gas_limit(l2_pricing::INITIAL_PER_BLOCK_GAS_LIMIT_V6)?;
511 }
512
513 self.set_format_version(self.arbos_version)?;
515
516 Ok(())
517 }
518}