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 data = Vec::with_capacity(storage_key.len() + BOUNDARY);
62    data.extend_from_slice(storage_key);
63    data.extend_from_slice(&key_bytes[..BOUNDARY]);
64    let h = keccak256(&data);
65
66    let mut mapped = [0u8; 32];
67    mapped[..BOUNDARY].copy_from_slice(&h.0[..BOUNDARY]);
68    mapped[BOUNDARY] = key_bytes[BOUNDARY];
69    U256::from_be_bytes(mapped)
70}
71
72/// Compute the EVM storage slot for a B256 key within a storage scope.
73pub fn map_slot_b256(storage_key: &[u8], key: &B256) -> U256 {
74    const BOUNDARY: usize = 31;
75
76    let mut data = Vec::with_capacity(storage_key.len() + BOUNDARY);
77    data.extend_from_slice(storage_key);
78    data.extend_from_slice(&key.0[..BOUNDARY]);
79    let h = keccak256(&data);
80
81    let mut mapped = [0u8; 32];
82    mapped[..BOUNDARY].copy_from_slice(&h.0[..BOUNDARY]);
83    mapped[BOUNDARY] = key.0[BOUNDARY];
84    U256::from_be_bytes(mapped)
85}
86
87/// Derive a subspace storage key from a parent key and child key bytes.
88///
89/// Computes `keccak256(parent_key || sub_key)`.
90pub fn derive_subspace_key(parent_key: &[u8], sub_key: &[u8]) -> B256 {
91    let mut combined = Vec::with_capacity(parent_key.len() + sub_key.len());
92    combined.extend_from_slice(parent_key);
93    combined.extend_from_slice(sub_key);
94    keccak256(&combined)
95}
96
97/// The root storage key for ArbOS state (empty, since base_key is B256::ZERO).
98pub const ROOT_STORAGE_KEY: &[u8] = &[];
99
100/// Compute a root-level ArbOS state slot.
101#[inline]
102pub fn root_slot(offset: u64) -> U256 {
103    map_slot(ROOT_STORAGE_KEY, offset)
104}
105
106/// Compute a slot within a subspace of the root ArbOS state.
107///
108/// E.g., `subspace_slot(L1_PRICING_SUBSPACE, field_offset)` for an L1 pricing field.
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
114// ── Per-tx scratch slot ──────────────────────────────────────────────
115
116/// Scratch slot used to pass per-transaction L1 poster fee from the
117/// executor into the EVM where the ArbGasInfo precompile can read it.
118/// The value is written before EVM execution and has no long-term
119/// significance — it's overwritten every transaction.
120pub const CURRENT_TX_POSTER_FEE_OFFSET: u64 = 255;
121
122/// Compute the storage slot for the per-tx poster fee.
123pub fn current_tx_poster_fee_slot() -> U256 {
124    map_slot(ROOT_STORAGE_KEY, CURRENT_TX_POSTER_FEE_OFFSET)
125}
126
127/// Scratch slot for the currently-executing retryable ticket ID.
128/// Written by the executor before retry tx EVM execution so the Redeem
129/// precompile can reject self-modification attempts.
130/// Zero means no retryable is executing.
131pub const CURRENT_RETRYABLE_OFFSET: u64 = 254;
132
133/// Compute the storage slot for the current retryable ticket ID.
134pub fn current_retryable_slot() -> U256 {
135    map_slot(ROOT_STORAGE_KEY, CURRENT_RETRYABLE_OFFSET)
136}
137
138/// Scratch slot for the current redeemer (refund_to address) during retry tx.
139/// Written by the executor before retry tx EVM execution so GetCurrentRedeemer
140/// can return the correct address.
141pub const CURRENT_REDEEMER_OFFSET: u64 = 253;
142
143/// Compute the storage slot for the current redeemer address.
144pub fn current_redeemer_slot() -> U256 {
145    map_slot(ROOT_STORAGE_KEY, CURRENT_REDEEMER_OFFSET)
146}
147
148// ── L2 pricing vector helpers ────────────────────────────────────────
149
150/// L2 pricing subspace key (root → L2_PRICING_SUBSPACE).
151pub fn l2_pricing_subspace() -> B256 {
152    derive_subspace_key(ROOT_STORAGE_KEY, L2_PRICING_SUBSPACE)
153}
154
155/// Subspace keys within L2 pricing.
156const GAS_CONSTRAINTS_SUBKEY: &[u8] = &[0];
157const MULTI_GAS_CONSTRAINTS_SUBKEY: &[u8] = &[1];
158const MULTI_GAS_BASE_FEES_SUBKEY: &[u8] = &[2];
159
160/// Derive a sub-storage vector key under L2 pricing.
161fn l2_vector_key(sub_key: &[u8]) -> B256 {
162    derive_subspace_key(l2_pricing_subspace().as_slice(), sub_key)
163}
164
165/// Slot for the length of a sub-storage vector.
166pub fn vector_length_slot(vector_key: &B256) -> U256 {
167    map_slot(vector_key.as_slice(), 0)
168}
169
170/// Subspace key for element `index` within a vector.
171pub fn vector_element_key(vector_key: &B256, index: u64) -> B256 {
172    derive_subspace_key(vector_key.as_slice(), &index.to_be_bytes())
173}
174
175/// Slot for field `offset` within element `index` of a vector.
176pub fn vector_element_field(vector_key: &B256, index: u64, offset: u64) -> U256 {
177    let elem = vector_element_key(vector_key, index);
178    map_slot(elem.as_slice(), offset)
179}
180
181/// Gas constraints vector key.
182pub fn gas_constraints_vec_key() -> B256 {
183    l2_vector_key(GAS_CONSTRAINTS_SUBKEY)
184}
185
186/// Multi-gas constraints vector key.
187pub fn multi_gas_constraints_vec_key() -> B256 {
188    l2_vector_key(MULTI_GAS_CONSTRAINTS_SUBKEY)
189}
190
191/// Multi-gas base fees subspace key.
192pub fn multi_gas_base_fees_subspace() -> B256 {
193    l2_vector_key(MULTI_GAS_BASE_FEES_SUBKEY)
194}