arb_precompiles/
storage_slot.rs

1use alloy_primitives::{keccak256, B256, U256};
2
3/// ArbOS state storage address (NOT the ArbOS sender address 0xa4b05).
4pub const ARBOS_STATE_ADDRESS: alloy_primitives::Address = alloy_primitives::Address::new([
5    0xa4, 0xb0, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
6    0xff, 0xff, 0xff, 0xff,
7]);
8
9/// Subspace keys for ArbOS partitioned storage (matching arbos_state constants).
10pub const L1_PRICING_SUBSPACE: &[u8] = &[0];
11pub const L2_PRICING_SUBSPACE: &[u8] = &[1];
12pub const RETRYABLES_SUBSPACE: &[u8] = &[2];
13pub const ADDRESS_TABLE_SUBSPACE: &[u8] = &[3];
14pub const CHAIN_OWNER_SUBSPACE: &[u8] = &[4];
15pub const SEND_MERKLE_SUBSPACE: &[u8] = &[5];
16pub const BLOCKHASHES_SUBSPACE: &[u8] = &[6];
17pub const CHAIN_CONFIG_SUBSPACE: &[u8] = &[7];
18pub const PROGRAMS_SUBSPACE: &[u8] = &[8];
19pub const FEATURES_SUBSPACE: &[u8] = &[9];
20pub const NATIVE_TOKEN_SUBSPACE: &[u8] = &[10];
21pub const TRANSACTION_FILTERER_SUBSPACE: &[u8] = &[11];
22
23/// Subspace keys within the PROGRAMS subspace.
24pub const PROGRAMS_PARAMS_KEY: &[u8] = &[0];
25pub const PROGRAMS_DATA_KEY: &[u8] = &[1];
26pub const CACHE_MANAGERS_KEY: &[u8] = &[4];
27
28/// Cache managers subspace within ArbOS (PROGRAMS → CACHE_MANAGERS).
29/// Not a direct root subspace; derive at runtime via `programs_cache_managers_key()`.
30pub const CACHE_MANAGERS_SUBSPACE: &[u8] = CACHE_MANAGERS_KEY;
31
32/// Filtered transactions backing storage account (separate from ArbOS state).
33pub const FILTERED_TX_STATE_ADDRESS: alloy_primitives::Address = alloy_primitives::Address::new([
34    0xa4, 0xb0, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35    0x00, 0x00, 0x00, 0x01,
36]);
37
38/// Root-level ArbOS state field offsets.
39pub const VERSION_OFFSET: u64 = 0;
40pub const UPGRADE_VERSION_OFFSET: u64 = 1;
41pub const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
42pub const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
43pub const CHAIN_ID_OFFSET: u64 = 4;
44pub const GENESIS_BLOCK_NUM_OFFSET: u64 = 5;
45pub const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
46pub const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
47pub const NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET: u64 = 8;
48pub const TX_FILTERING_ENABLED_FROM_TIME_OFFSET: u64 = 9;
49pub const FILTERED_FUNDS_RECIPIENT_OFFSET: u64 = 10;
50
51/// Compute the EVM storage slot for an ArbOS field at a given offset
52/// within a storage scope defined by `storage_key`.
53///
54/// Computes `keccak256(storage_key || key[0..31]) || key[31]`.
55pub fn map_slot(storage_key: &[u8], offset: u64) -> U256 {
56    const BOUNDARY: usize = 31;
57
58    let mut key_bytes = [0u8; 32];
59    key_bytes[24..32].copy_from_slice(&offset.to_be_bytes());
60
61    let mut buf = [0u8; 64];
62    let sk_len = storage_key.len();
63    buf[..sk_len].copy_from_slice(storage_key);
64    buf[sk_len..sk_len + BOUNDARY].copy_from_slice(&key_bytes[..BOUNDARY]);
65    let h = keccak256(&buf[..sk_len + BOUNDARY]);
66
67    let mut mapped = [0u8; 32];
68    mapped[..BOUNDARY].copy_from_slice(&h.0[..BOUNDARY]);
69    mapped[BOUNDARY] = key_bytes[BOUNDARY];
70    U256::from_be_bytes(mapped)
71}
72
73/// Compute the EVM storage slot for a B256 key within a storage scope.
74pub fn map_slot_b256(storage_key: &[u8], key: &B256) -> U256 {
75    const BOUNDARY: usize = 31;
76
77    let mut buf = [0u8; 64];
78    let sk_len = storage_key.len();
79    buf[..sk_len].copy_from_slice(storage_key);
80    buf[sk_len..sk_len + BOUNDARY].copy_from_slice(&key.0[..BOUNDARY]);
81    let h = keccak256(&buf[..sk_len + BOUNDARY]);
82
83    let mut mapped = [0u8; 32];
84    mapped[..BOUNDARY].copy_from_slice(&h.0[..BOUNDARY]);
85    mapped[BOUNDARY] = key.0[BOUNDARY];
86    U256::from_be_bytes(mapped)
87}
88
89/// Derive a subspace storage key from a parent key and child key bytes.
90///
91/// Computes `keccak256(parent_key || sub_key)`.
92pub fn derive_subspace_key(parent_key: &[u8], sub_key: &[u8]) -> B256 {
93    let p_len = parent_key.len();
94    let s_len = sub_key.len();
95    if p_len + s_len <= 64 {
96        let mut buf = [0u8; 64];
97        buf[..p_len].copy_from_slice(parent_key);
98        buf[p_len..p_len + s_len].copy_from_slice(sub_key);
99        keccak256(&buf[..p_len + s_len])
100    } else {
101        let mut v = Vec::with_capacity(p_len + s_len);
102        v.extend_from_slice(parent_key);
103        v.extend_from_slice(sub_key);
104        keccak256(&v)
105    }
106}
107
108/// The root storage key for ArbOS state (empty, since base_key is B256::ZERO).
109pub const ROOT_STORAGE_KEY: &[u8] = &[];
110
111/// Compute a root-level ArbOS state slot.
112#[inline]
113pub fn root_slot(offset: u64) -> U256 {
114    map_slot(ROOT_STORAGE_KEY, offset)
115}
116
117/// Compute a slot within a subspace of the root ArbOS state.
118///
119/// E.g., `subspace_slot(L1_PRICING_SUBSPACE, field_offset)` for an L1 pricing field.
120pub fn subspace_slot(subspace_key: &[u8], offset: u64) -> U256 {
121    let sub_storage_key = derive_subspace_key(ROOT_STORAGE_KEY, subspace_key);
122    map_slot(sub_storage_key.as_slice(), offset)
123}
124
125// ── Per-tx scratch slot ──────────────────────────────────────────────
126
127/// Scratch slot used to pass per-transaction L1 poster fee from the
128/// executor into the EVM where the ArbGasInfo precompile can read it.
129/// The value is written before EVM execution and has no long-term
130/// significance — it's overwritten every transaction.
131pub const CURRENT_TX_POSTER_FEE_OFFSET: u64 = 255;
132
133/// Compute the storage slot for the per-tx poster fee.
134pub fn current_tx_poster_fee_slot() -> U256 {
135    map_slot(ROOT_STORAGE_KEY, CURRENT_TX_POSTER_FEE_OFFSET)
136}
137
138/// Scratch slot for the currently-executing retryable ticket ID.
139/// Written by the executor before retry tx EVM execution so the Redeem
140/// precompile can reject self-modification attempts.
141/// Zero means no retryable is executing.
142pub const CURRENT_RETRYABLE_OFFSET: u64 = 254;
143
144/// Compute the storage slot for the current retryable ticket ID.
145pub fn current_retryable_slot() -> U256 {
146    map_slot(ROOT_STORAGE_KEY, CURRENT_RETRYABLE_OFFSET)
147}
148
149/// Scratch slot for the current redeemer (refund_to address) during retry tx.
150/// Written by the executor before retry tx EVM execution so GetCurrentRedeemer
151/// can return the correct address.
152pub const CURRENT_REDEEMER_OFFSET: u64 = 253;
153
154/// Compute the storage slot for the current redeemer address.
155pub fn current_redeemer_slot() -> U256 {
156    map_slot(ROOT_STORAGE_KEY, CURRENT_REDEEMER_OFFSET)
157}
158
159// ── L2 pricing vector helpers ────────────────────────────────────────
160
161/// L2 pricing subspace key (root → L2_PRICING_SUBSPACE).
162pub fn l2_pricing_subspace() -> B256 {
163    derive_subspace_key(ROOT_STORAGE_KEY, L2_PRICING_SUBSPACE)
164}
165
166/// Subspace keys within L2 pricing.
167const GAS_CONSTRAINTS_SUBKEY: &[u8] = &[0];
168const MULTI_GAS_CONSTRAINTS_SUBKEY: &[u8] = &[1];
169const MULTI_GAS_BASE_FEES_SUBKEY: &[u8] = &[2];
170
171/// Derive a sub-storage vector key under L2 pricing.
172fn l2_vector_key(sub_key: &[u8]) -> B256 {
173    derive_subspace_key(l2_pricing_subspace().as_slice(), sub_key)
174}
175
176/// Slot for the length of a sub-storage vector.
177pub fn vector_length_slot(vector_key: &B256) -> U256 {
178    map_slot(vector_key.as_slice(), 0)
179}
180
181/// Subspace key for element `index` within a vector.
182pub fn vector_element_key(vector_key: &B256, index: u64) -> B256 {
183    derive_subspace_key(vector_key.as_slice(), &index.to_be_bytes())
184}
185
186/// Slot for field `offset` within element `index` of a vector.
187pub fn vector_element_field(vector_key: &B256, index: u64, offset: u64) -> U256 {
188    let elem = vector_element_key(vector_key, index);
189    map_slot(elem.as_slice(), offset)
190}
191
192/// Gas constraints vector key.
193pub fn gas_constraints_vec_key() -> B256 {
194    l2_vector_key(GAS_CONSTRAINTS_SUBKEY)
195}
196
197/// Multi-gas constraints vector key.
198pub fn multi_gas_constraints_vec_key() -> B256 {
199    l2_vector_key(MULTI_GAS_CONSTRAINTS_SUBKEY)
200}
201
202/// Multi-gas base fees subspace key.
203pub fn multi_gas_base_fees_subspace() -> B256 {
204    l2_vector_key(MULTI_GAS_BASE_FEES_SUBKEY)
205}