arb_storage/
storage.rs

1use std::{marker::PhantomData, ptr::NonNull};
2
3use alloy_primitives::{Address, B256, U256};
4use arb_storage_errors::StorageError;
5use revm::Database;
6
7use crate::{
8    slot::{derive_sub_key, storage_key_map, storage_key_map_b256},
9    state_ops::{ARBOS_STATE_ADDRESS, read_storage_at, write_storage_at},
10};
11
12/// Phantom backend used by read paths that drive ArbOS accessors through a
13/// [`StorageBackend`] (such as the precompile handlers reading via
14/// `EvmInternals`). Direct state-pointer I/O on a `Storage<Detached>` is
15/// unreachable by construction — the type does not implement [`Database`].
16pub enum Detached {}
17
18/// Hierarchical storage abstraction over EVM account state.
19///
20/// Subspaces are derived from `base_key` using keccak-based mixing; direct
21/// state I/O (when available) targets `account`. The struct is parameterised
22/// over the executor's `Database` so the same shape can be inhabited by an
23/// executor `&'a mut State<D>` or by [`Detached`] for read paths that operate
24/// purely through a [`StorageBackend`].
25///
26/// The lifetime `'a` ties the handle to the originating `&mut State<D>` borrow
27/// so a `Storage` value cannot outlive the state it references. The state is
28/// held as a [`NonNull`] internally (with a [`PhantomData`] for the lifetime)
29/// so multiple disjoint subspaces over the same state can coexist; direct I/O
30/// goes through one `unsafe` deref controlled by the same SAFETY invariant
31/// described below.
32///
33/// # Safety
34///
35/// All direct I/O methods on `Storage<'a, D>` materialise `&mut *state` for the
36/// duration of a single SLOAD/SSTORE. The executor runs each block on a single
37/// thread and the EVM call graph is sequential, so no two such borrows overlap
38/// at runtime. The lifetime `'a` ensures the underlying `State<D>` cannot be
39/// dropped while `Storage` handles into it are alive.
40pub struct Storage<'a, D> {
41    state: NonNull<revm::database::State<D>>,
42    base_key: B256,
43    account: Address,
44    _marker: PhantomData<&'a mut revm::database::State<D>>,
45}
46
47impl<'a, D> Storage<'a, D> {
48    /// Creates a new Storage backed by the ArbOS state account.
49    pub fn new(state: &'a mut revm::database::State<D>, base_key: B256) -> Self {
50        Self {
51            // SAFETY: `state` is a live `&mut` so `NonNull::new_unchecked` is sound.
52            state: unsafe { NonNull::new_unchecked(state as *mut _) },
53            base_key,
54            account: ARBOS_STATE_ADDRESS,
55            _marker: PhantomData,
56        }
57    }
58
59    /// Creates a new Storage backed by a specific account.
60    pub fn new_with_account(
61        state: &'a mut revm::database::State<D>,
62        base_key: B256,
63        account: Address,
64    ) -> Self {
65        Self {
66            // SAFETY: `state` is a live `&mut` so `NonNull::new_unchecked` is sound.
67            state: unsafe { NonNull::new_unchecked(state as *mut _) },
68            base_key,
69            account,
70            _marker: PhantomData,
71        }
72    }
73
74    /// Opens a child subspace by hashing the parent key with the child ID.
75    pub fn open_sub_storage(&self, sub_key: &[u8]) -> Storage<'a, D> {
76        let new_key = derive_sub_key(self.base_key, sub_key);
77        Storage {
78            state: self.state,
79            base_key: new_key,
80            account: self.account,
81            _marker: PhantomData,
82        }
83    }
84
85    /// Opens a child subspace using a pre-derived key, avoiding a keccak hash.
86    pub fn open_sub_storage_with_key(&self, key: B256) -> Storage<'a, D> {
87        Storage {
88            state: self.state,
89            base_key: key,
90            account: self.account,
91            _marker: PhantomData,
92        }
93    }
94
95    /// Opens a sibling handle bound to the same backing state but targeting a
96    /// different account and `base_key`. Used when constructing accessors for
97    /// well-known non-ArbOS addresses (e.g. the filtered-transactions store).
98    pub fn open_account(&self, account: Address, base_key: B256) -> Storage<'a, D> {
99        Storage {
100            state: self.state,
101            base_key,
102            account,
103            _marker: PhantomData,
104        }
105    }
106
107    fn storage_key(&self) -> &[u8] {
108        if self.base_key == B256::ZERO {
109            &[]
110        } else {
111            self.base_key.as_slice()
112        }
113    }
114
115    fn compute_slot(&self, offset: u64) -> U256 {
116        storage_key_map(self.storage_key(), offset)
117    }
118
119    fn compute_slot_for_key(&self, key: B256) -> U256 {
120        storage_key_map_b256(self.storage_key(), &key.0)
121    }
122
123    /// Creates a StorageSlot handle for a specific offset.
124    pub fn new_slot(&self, offset: u64) -> U256 {
125        self.compute_slot(offset)
126    }
127
128    /// Computes the EVM slot for a `B256`-keyed entry under this subspace.
129    pub fn slot_for_key(&self, key: B256) -> U256 {
130        self.compute_slot_for_key(key)
131    }
132
133    /// Returns the base key for this storage subspace.
134    pub fn base_key(&self) -> B256 {
135        self.base_key
136    }
137
138    /// Returns the account address this storage subspace is bound to.
139    pub fn account(&self) -> Address {
140        self.account
141    }
142}
143
144impl Storage<'static, Detached> {
145    /// Builds a `Storage` handle that has no executor state pointer.
146    ///
147    /// All reads and writes must be routed through a [`StorageBackend`];
148    /// direct I/O methods on `Storage` are gated on `D: Database` and are
149    /// therefore inaccessible here.
150    pub fn detached(account: Address, base_key: B256) -> Self {
151        Self {
152            // SAFETY: `Detached` storage never has its state dereferenced —
153            // there is no `Database` impl that would expose the direct I/O
154            // surface. The pointer value here is intentionally dangling and
155            // unused.
156            state: NonNull::dangling(),
157            base_key,
158            account,
159            _marker: PhantomData,
160        }
161    }
162}
163
164impl<'a, D: Database> Storage<'a, D> {
165    /// Reads a 32-byte value by uint64 offset.
166    pub fn get_by_uint64(&self, offset: u64) -> Result<B256, StorageError> {
167        let slot = self.compute_slot(offset);
168        // SAFETY: see struct-level invariant.
169        let state = unsafe { &mut *self.state.as_ptr() };
170        read_storage_at(state, self.account, slot).map(B256::from)
171    }
172
173    /// Writes a 32-byte value by uint64 offset.
174    pub fn set_by_uint64(&self, offset: u64, value: B256) -> Result<(), StorageError> {
175        let slot = self.compute_slot(offset);
176        let value_u256 = U256::from_be_bytes(value.0);
177        // SAFETY: see struct-level invariant.
178        let state = unsafe { &mut *self.state.as_ptr() };
179        write_storage_at(state, self.account, slot, value_u256)
180    }
181
182    /// Reads a `u64` by uint64 offset, truncating values that exceed `u64::MAX`.
183    pub fn get_uint64_by_uint64(&self, offset: u64) -> Result<u64, StorageError> {
184        let slot = self.compute_slot(offset);
185        // SAFETY: see struct-level invariant.
186        let state = unsafe { &mut *self.state.as_ptr() };
187        let value = read_storage_at(state, self.account, slot)?;
188        Ok(value.try_into().unwrap_or(0))
189    }
190
191    /// Writes a `u64` by uint64 offset.
192    pub fn set_uint64_by_uint64(&self, offset: u64, value: u64) -> Result<(), StorageError> {
193        let slot = self.compute_slot(offset);
194        // SAFETY: see struct-level invariant.
195        let state = unsafe { &mut *self.state.as_ptr() };
196        write_storage_at(state, self.account, slot, U256::from(value))
197    }
198
199    /// Reads a 32-byte value by B256 key using mapAddress algorithm.
200    pub fn get(&self, key: B256) -> Result<B256, StorageError> {
201        let slot = self.compute_slot_for_key(key);
202        // SAFETY: see struct-level invariant.
203        let state = unsafe { &mut *self.state.as_ptr() };
204        read_storage_at(state, self.account, slot).map(B256::from)
205    }
206
207    /// Writes a 32-byte value by B256 key using mapAddress algorithm.
208    pub fn set(&self, key: B256, value: B256) -> Result<(), StorageError> {
209        let slot = self.compute_slot_for_key(key);
210        let value_u256 = U256::from_be_bytes(value.0);
211        // SAFETY: see struct-level invariant.
212        let state = unsafe { &mut *self.state.as_ptr() };
213        write_storage_at(state, self.account, slot, value_u256)
214    }
215}
216
217impl<'a, D> Storage<'a, D> {
218    /// Re-borrows the underlying state for direct `State<D>`-level operations
219    /// (e.g. account-level reads such as `get_account_balance` and writes such
220    /// as `set_account_nonce`/`set_account_code`).
221    ///
222    /// The returned reference inherits the lifetime parameter `'a` of the
223    /// `Storage`, so it is decoupled from any temporary borrow on `&self`.
224    /// This shape lets callers thread the same backing state through methods
225    /// that take `&mut self` without artificial borrow conflicts.
226    ///
227    /// # Safety
228    ///
229    /// arbreth's executor runs each block on a single thread and the EVM call
230    /// graph is sequential, so no two such borrows are live at the same time
231    /// at runtime. Callers must not nest two `state_mut()` returns from
232    /// overlapping `Storage` handles.
233    pub unsafe fn state_mut(&self) -> &'a mut revm::database::State<D> {
234        // SAFETY: see method-level invariant. `'a` ensures lifetime safety.
235        unsafe { &mut *self.state.as_ptr() }
236    }
237}
238
239impl<D> Clone for Storage<'_, D> {
240    fn clone(&self) -> Self {
241        Self {
242            state: self.state,
243            base_key: self.base_key,
244            account: self.account,
245            _marker: PhantomData,
246        }
247    }
248}
249
250// SAFETY: `Storage` over a `D: Send` state is safe to send between threads
251// when no concurrent access occurs. The arbreth executor runs each block on a
252// single thread; reentrant Stylus calls execute synchronously on the same
253// thread. The lifetime `'a` prevents the handle from outliving the source
254// state borrow.
255unsafe impl<D: Send> Send for Storage<'_, D> {}
256unsafe impl<D: Sync> Sync for Storage<'_, D> {}