arb_storage/
layout.rs

1//! ArbOS storage layout primitives — subspace keys and slot derivation.
2
3use alloy_primitives::{B256, U256, keccak256};
4
5// Root subspace IDs partitioning the ArbOS state trie.
6pub const L1_PRICING_SUBSPACE: &[u8] = &[0];
7pub const L2_PRICING_SUBSPACE: &[u8] = &[1];
8pub const RETRYABLES_SUBSPACE: &[u8] = &[2];
9pub const ADDRESS_TABLE_SUBSPACE: &[u8] = &[3];
10pub const CHAIN_OWNER_SUBSPACE: &[u8] = &[4];
11pub const SEND_MERKLE_SUBSPACE: &[u8] = &[5];
12pub const BLOCKHASHES_SUBSPACE: &[u8] = &[6];
13pub const CHAIN_CONFIG_SUBSPACE: &[u8] = &[7];
14pub const PROGRAMS_SUBSPACE: &[u8] = &[8];
15pub const FEATURES_SUBSPACE: &[u8] = &[9];
16pub const NATIVE_TOKEN_SUBSPACE: &[u8] = &[10];
17pub const TRANSACTION_FILTERER_SUBSPACE: &[u8] = &[11];
18
19// Root-level field offsets.
20pub const VERSION_OFFSET: u64 = 0;
21pub const UPGRADE_VERSION_OFFSET: u64 = 1;
22pub const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
23pub const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
24pub const CHAIN_ID_OFFSET: u64 = 4;
25pub const GENESIS_BLOCK_NUM_OFFSET: u64 = 5;
26pub const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
27pub const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
28pub const NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET: u64 = 8;
29pub const TRANSACTION_FILTERING_ENABLED_FROM_TIME_OFFSET: u64 = 9;
30pub const FILTERED_FUNDS_RECIPIENT_OFFSET: u64 = 10;
31pub const COLLECT_TIPS_OFFSET: u64 = 11;
32
33/// Partition keys within the programs subspace.
34pub mod programs {
35    pub const PARAMS_KEY: &[u8] = &[0];
36    pub const PROGRAM_DATA_KEY: &[u8] = &[1];
37    pub const MODULE_HASHES_KEY: &[u8] = &[2];
38    pub const DATA_PRICER_KEY: &[u8] = &[3];
39    pub const CACHE_MANAGERS_KEY: &[u8] = &[4];
40    pub const ACTIVATION_GAS_KEY: &[u8] = &[5];
41}
42
43pub const ROOT_STORAGE_KEY: &[u8] = &[];
44
45/// Compute the EVM storage slot for an ArbOS field at a given offset
46/// within a storage scope defined by `storage_key`.
47///
48/// Computes `keccak256(storage_key || key[0..31]) || key[31]`.
49pub fn map_slot(storage_key: &[u8], offset: u64) -> U256 {
50    const BOUNDARY: usize = 31;
51
52    let mut key_bytes = [0u8; 32];
53    key_bytes[24..32].copy_from_slice(&offset.to_be_bytes());
54
55    let mut buf = [0u8; 64];
56    let sk_len = storage_key.len();
57    buf[..sk_len].copy_from_slice(storage_key);
58    buf[sk_len..sk_len + BOUNDARY].copy_from_slice(&key_bytes[..BOUNDARY]);
59    let h = keccak256(&buf[..sk_len + BOUNDARY]);
60
61    let mut mapped = [0u8; 32];
62    mapped[..BOUNDARY].copy_from_slice(&h.0[..BOUNDARY]);
63    mapped[BOUNDARY] = key_bytes[BOUNDARY];
64    U256::from_be_bytes(mapped)
65}
66
67/// Compute the EVM storage slot for a B256 key within a storage scope.
68pub fn map_slot_b256(storage_key: &[u8], key: &B256) -> U256 {
69    const BOUNDARY: usize = 31;
70
71    let mut buf = [0u8; 64];
72    let sk_len = storage_key.len();
73    buf[..sk_len].copy_from_slice(storage_key);
74    buf[sk_len..sk_len + BOUNDARY].copy_from_slice(&key.0[..BOUNDARY]);
75    let h = keccak256(&buf[..sk_len + BOUNDARY]);
76
77    let mut mapped = [0u8; 32];
78    mapped[..BOUNDARY].copy_from_slice(&h.0[..BOUNDARY]);
79    mapped[BOUNDARY] = key.0[BOUNDARY];
80    U256::from_be_bytes(mapped)
81}
82
83/// Derive a subspace storage key from a parent key and child key bytes.
84///
85/// Computes `keccak256(parent_key || sub_key)`.
86pub fn derive_subspace_key(parent_key: &[u8], sub_key: &[u8]) -> B256 {
87    let p_len = parent_key.len();
88    let s_len = sub_key.len();
89    if p_len + s_len <= 64 {
90        let mut buf = [0u8; 64];
91        buf[..p_len].copy_from_slice(parent_key);
92        buf[p_len..p_len + s_len].copy_from_slice(sub_key);
93        keccak256(&buf[..p_len + s_len])
94    } else {
95        let mut v = Vec::with_capacity(p_len + s_len);
96        v.extend_from_slice(parent_key);
97        v.extend_from_slice(sub_key);
98        keccak256(&v)
99    }
100}
101
102/// Compute a root-level ArbOS state slot.
103#[inline]
104pub fn root_slot(offset: u64) -> U256 {
105    map_slot(ROOT_STORAGE_KEY, offset)
106}
107
108/// Compute a slot within a subspace of the root ArbOS state.
109pub fn subspace_slot(subspace_key: &[u8], offset: u64) -> U256 {
110    let sub_storage_key = derive_subspace_key(ROOT_STORAGE_KEY, subspace_key);
111    map_slot(sub_storage_key.as_slice(), offset)
112}
113
114fn l2_pricing_subspace() -> B256 {
115    derive_subspace_key(ROOT_STORAGE_KEY, L2_PRICING_SUBSPACE)
116}
117
118const GAS_CONSTRAINTS_SUBKEY: &[u8] = &[0];
119
120fn l2_vector_key(sub_key: &[u8]) -> B256 {
121    derive_subspace_key(l2_pricing_subspace().as_slice(), sub_key)
122}
123
124fn vector_element_key(vector_key: &B256, index: u64) -> B256 {
125    derive_subspace_key(vector_key.as_slice(), &index.to_be_bytes())
126}
127
128/// Slot for field `offset` within element `index` of a vector.
129pub fn vector_element_field(vector_key: &B256, index: u64, offset: u64) -> U256 {
130    let elem = vector_element_key(vector_key, index);
131    map_slot(elem.as_slice(), offset)
132}
133
134/// Gas constraints vector key (L2 pricing subspace).
135pub fn gas_constraints_vec_key() -> B256 {
136    l2_vector_key(GAS_CONSTRAINTS_SUBKEY)
137}