Skip to main content

stylus_sdk/storage/
traits.rs

1// Copyright 2022-2024, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4use core::{
5    marker::PhantomData,
6    ops::{Deref, DerefMut},
7    ptr,
8};
9
10use alloy_primitives::{FixedBytes, Signed, Uint, B256, U256};
11use derivative::Derivative;
12
13use crate::host::VM;
14
15/// Accessor trait that lets a type be used in persistent storage.
16/// Users can implement this trait to add novel data structures to their contract definitions.
17/// The Stylus SDK by default provides only solidity types, which are represented [`the same way`].
18///
19/// [`the same way`]: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html
20pub trait StorageType: Sized {
21    /// For primitive types, this is the type being stored.
22    /// For collections, this is the [`StorageType`] being collected.
23    type Wraps<'a>: 'a
24    where
25        Self: 'a;
26
27    /// Mutable accessor to the type being stored.
28    type WrapsMut<'a>: 'a
29    where
30        Self: 'a;
31
32    /// The number of bytes in a slot needed to represent the type. Must not exceed 32.
33    /// For types larger than 32 bytes that are stored inline with a struct's fields,
34    /// set this to 32 and return the full size in [`StorageType::new`].
35    ///
36    /// For implementing collections, see how Solidity slots are assigned for [`Arrays and Maps`]
37    /// and their Stylus equivalents [`StorageVec`](super::StorageVec) and
38    /// [`StorageMap`](super::StorageMap). For multi-word, but still fixed-size types, see the
39    /// implementations for structs and [`StorageArray`](super::StorageArray).
40    ///
41    /// [`Arrays and Maps`]: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays
42    const SLOT_BYTES: usize = 32;
43
44    /// The number of words this type must fill. For primitives this is always 0.
45    /// For complex types requiring more than one inline word, set this to the total size.
46    const REQUIRED_SLOTS: usize = 0;
47
48    /// Where in persistent storage the type should live. Although useful for framework designers
49    /// creating new storage types, most user programs shouldn't call this.
50    /// Note: implementations will have to be `const` once [`generic_const_exprs`] stabilizes.
51    ///
52    /// # Safety
53    ///
54    /// Aliases storage if two calls to the same slot and offset occur within the same lifetime.
55    ///
56    /// [`generic_const_exprs`]: https://github.com/rust-lang/rust/issues/76560
57    unsafe fn new(slot: U256, offset: u8, host: VM) -> Self;
58
59    /// Load the wrapped type, consuming the accessor.
60    /// Note: most types have a `get` and/or `getter`, which don't consume `Self`.
61    fn load<'s>(self) -> Self::Wraps<'s>
62    where
63        Self: 's;
64
65    /// Load the wrapped mutable type, consuming the accessor.
66    /// Note: most types have a `set` and/or `setter`, which don't consume `Self`.
67    fn load_mut<'s>(self) -> Self::WrapsMut<'s>
68    where
69        Self: 's;
70}
71
72/// Trait for accessors that can be used to completely erase their underlying value.
73/// Note that some collections, like [`StorageMap`](super::StorageMap), don't implement this trait.
74pub trait Erase: StorageType {
75    /// Erase the value from persistent storage.
76    fn erase(&mut self);
77}
78
79/// Trait for simple accessors that store no more than their wrapped value.
80/// The type's representation must be entirely inline, or storage leaks become possible.
81/// Note: it is a logic error if erasure does anything more than writing the zero-value.
82pub trait SimpleStorageType<'a>: StorageType + Erase + Into<Self::Wraps<'a>>
83where
84    Self: 'a,
85{
86    /// Write the value to persistent storage.
87    fn set_by_wrapped(&mut self, value: Self::Wraps<'a>);
88}
89
90/// Binds a storage accessor to a lifetime to prevent aliasing.
91/// Because this type doesn't implement `DerefMut`, mutable methods on the accessor aren't
92/// available. For a mutable accessor, see [`StorageGuardMut`].
93#[derive(Derivative)]
94#[derivative(Debug = "transparent")]
95pub struct StorageGuard<'a, T: 'a> {
96    inner: T,
97    #[derivative(Debug = "ignore")]
98    marker: PhantomData<&'a T>,
99}
100
101impl<'a, T: 'a> StorageGuard<'a, T> {
102    /// Creates a new storage guard around an arbitrary type.
103    pub fn new(inner: T) -> Self {
104        Self {
105            inner,
106            marker: PhantomData,
107        }
108    }
109
110    /// Get the underlying `T` directly, bypassing the borrow checker.
111    ///
112    /// # Safety
113    ///
114    /// Enables storage aliasing.
115    pub unsafe fn into_raw(self) -> T {
116        self.inner
117    }
118}
119
120impl<'a, T: 'a> Deref for StorageGuard<'a, T> {
121    type Target = T;
122
123    fn deref(&self) -> &Self::Target {
124        &self.inner
125    }
126}
127
128/// Binds a storage accessor to a lifetime to prevent aliasing.
129pub struct StorageGuardMut<'a, T: 'a> {
130    inner: T,
131    marker: PhantomData<&'a T>,
132}
133
134impl<'a, T: 'a> StorageGuardMut<'a, T> {
135    /// Creates a new storage guard around an arbitrary type.
136    pub fn new(inner: T) -> Self {
137        Self {
138            inner,
139            marker: PhantomData,
140        }
141    }
142
143    /// Get the underlying `T` directly, bypassing the borrow checker.
144    ///
145    /// # Safety
146    ///
147    /// Enables storage aliasing.
148    pub unsafe fn into_raw(self) -> T {
149        self.inner
150    }
151}
152
153impl<'a, T: 'a> Deref for StorageGuardMut<'a, T> {
154    type Target = T;
155
156    fn deref(&self) -> &Self::Target {
157        &self.inner
158    }
159}
160
161impl<'a, T: 'a> DerefMut for StorageGuardMut<'a, T> {
162    fn deref_mut(&mut self) -> &mut Self::Target {
163        &mut self.inner
164    }
165}
166
167/// Trait for managing access to persistent storage.
168/// Implemented by the [`StorageCache`](super::StorageCache) type.
169pub trait GlobalStorage {
170    /// Retrieves `N ≤ 32` bytes from persistent storage, performing [`SLOAD`]'s only as needed.
171    /// The bytes are read from slot `key`, starting `offset` bytes from the left.
172    /// Note that the bytes must exist within a single, 32-byte EVM word.
173    ///
174    /// # Safety
175    ///
176    /// UB if the read would cross a word boundary.
177    /// May become safe when Rust stabilizes [`generic_const_exprs`].
178    ///
179    /// [`SLOAD`]: https://www.evm.codes/#54
180    /// [`generic_const_exprs`]: https://github.com/rust-lang/rust/issues/76560
181    unsafe fn get<const N: usize>(host: VM, key: U256, offset: usize) -> FixedBytes<N> {
182        debug_assert!(N + offset <= 32);
183        let word = Self::get_word(host, key);
184        let value = &word[offset..][..N];
185        FixedBytes::from_slice(value)
186    }
187
188    /// Retrieves a [`Uint`] from persistent storage, performing [`SLOAD`]'s only as needed.
189    /// The integer's bytes are read from slot `key`, starting `offset` bytes from the left.
190    /// Note that the bytes must exist within a single, 32-byte EVM word.
191    ///
192    /// # Safety
193    ///
194    /// UB if the read would cross a word boundary.
195    /// May become safe when Rust stabilizes [`generic_const_exprs`].
196    ///
197    /// [`SLOAD`]: https://www.evm.codes/#54
198    /// [`generic_const_exprs`]: https://github.com/rust-lang/rust/issues/76560
199    unsafe fn get_uint<const B: usize, const L: usize>(
200        host: VM,
201        key: U256,
202        offset: usize,
203    ) -> Uint<B, L> {
204        debug_assert!(B / 8 + offset <= 32);
205        let word = Self::get_word(host, key);
206        let value = &word[offset..][..B / 8];
207        Uint::try_from_be_slice(value).unwrap()
208    }
209
210    /// Retrieves a [`Signed`] from persistent storage, performing [`SLOAD`]'s only as needed.
211    /// The integer's bytes are read from slot `key`, starting `offset` bytes from the left.
212    /// Note that the bytes must exist within a single, 32-byte EVM word.
213    ///
214    /// # Safety
215    ///
216    /// UB if the read would cross a word boundary.
217    /// May become safe when Rust stabilizes [`generic_const_exprs`].
218    ///
219    /// [`SLOAD`]: https://www.evm.codes/#54
220    /// [`generic_const_exprs`]: https://github.com/rust-lang/rust/issues/76560
221    unsafe fn get_signed<const B: usize, const L: usize>(
222        host: VM,
223        key: U256,
224        offset: usize,
225    ) -> Signed<B, L> {
226        Signed::from_raw(Self::get_uint(host, key, offset))
227    }
228
229    /// Retrieves a [`u8`] from persistent storage, performing [`SLOAD`]'s only as needed.
230    /// The byte is read from slot `key`, starting `offset` bytes from the left.
231    ///
232    /// # Safety
233    ///
234    /// UB if the read is out of bounds.
235    /// May become safe when Rust stabilizes [`generic_const_exprs`].
236    ///
237    /// [`SLOAD`]: https://www.evm.codes/#54
238    /// [`generic_const_exprs`]: https://github.com/rust-lang/rust/issues/76560
239    unsafe fn get_byte(host: VM, key: U256, offset: usize) -> u8 {
240        debug_assert!(offset <= 32);
241        let word = Self::get::<1>(host, key, offset);
242        word[0]
243    }
244
245    /// Retrieves a [`Signed`] from persistent storage, performing [`SLOAD`]'s only as needed.
246    /// The integer's bytes are read from slot `key`, starting `offset` bytes from the left.
247    /// Note that the bytes must exist within a single, 32-byte EVM word.
248    ///
249    /// # Safety
250    ///
251    /// UB if the read would cross a word boundary.
252    /// May become safe when Rust stabilizes [`generic_const_exprs`].
253    ///
254    /// [`SLOAD`]: https://www.evm.codes/#54
255    /// [`generic_const_exprs`]: https://github.com/rust-lang/rust/issues/76560
256    fn get_word(host: VM, key: U256) -> B256;
257
258    /// Writes `N ≤ 32` bytes to persistent storage, performing [`SSTORE`]'s only as needed.
259    /// The bytes are written to slot `key`, starting `offset` bytes from the left.
260    /// Note that the bytes must be written to a single, 32-byte EVM word.
261    ///
262    /// # Safety
263    ///
264    /// UB if the write would cross a word boundary.
265    /// Aliases if called during the lifetime an overlapping accessor.
266    ///
267    /// [`SSTORE`]: https://www.evm.codes/#55
268    unsafe fn set<const N: usize>(host: VM, key: U256, offset: usize, value: FixedBytes<N>) {
269        debug_assert!(N + offset <= 32);
270
271        if N == 32 {
272            return Self::set_word(host, key, FixedBytes::from_slice(value.as_slice()));
273        }
274
275        let mut word = Self::get_word(host.clone(), key);
276
277        let dest = word[offset..].as_mut_ptr();
278        ptr::copy(value.as_ptr(), dest, N);
279
280        Self::set_word(host, key, word);
281    }
282
283    /// Writes a [`Uint`] to persistent storage, performing [`SSTORE`]'s only as needed.
284    /// The integer's bytes are written to slot `key`, starting `offset` bytes from the left.
285    /// Note that the bytes must be written to a single, 32-byte EVM word.
286    ///
287    /// # Safety
288    ///
289    /// UB if the write would cross a word boundary.
290    /// Aliases if called during the lifetime an overlapping accessor.
291    ///
292    /// [`SSTORE`]: https://www.evm.codes/#55
293    unsafe fn set_uint<const B: usize, const L: usize>(
294        host: VM,
295        key: U256,
296        offset: usize,
297        value: Uint<B, L>,
298    ) {
299        debug_assert!(B.is_multiple_of(8));
300        debug_assert!(B / 8 + offset <= 32);
301
302        if B == 256 {
303            return Self::set_word(
304                host,
305                key,
306                FixedBytes::from_slice(&value.to_be_bytes::<32>()),
307            );
308        }
309
310        let mut word = Self::get_word(host.clone(), key);
311
312        let value = value.to_be_bytes_vec();
313        let dest = word[offset..].as_mut_ptr();
314        ptr::copy(value.as_ptr(), dest, B / 8);
315        Self::set_word(host, key, word);
316    }
317
318    /// Writes a [`Signed`] to persistent storage, performing [`SSTORE`]'s only as needed.
319    /// The bytes are written to slot `key`, starting `offset` bytes from the left.
320    /// Note that the bytes must be written to a single, 32-byte EVM word.
321    ///
322    /// # Safety
323    ///
324    /// UB if the write would cross a word boundary.
325    /// Aliases if called during the lifetime an overlapping accessor.
326    ///
327    /// [`SSTORE`]: https://www.evm.codes/#55
328    unsafe fn set_signed<const B: usize, const L: usize>(
329        host: VM,
330        key: U256,
331        offset: usize,
332        value: Signed<B, L>,
333    ) {
334        Self::set_uint(host, key, offset, value.into_raw())
335    }
336
337    /// Writes a [`u8`] to persistent storage, performing [`SSTORE`]'s only as needed.
338    /// The byte is written to slot `key`, starting `offset` bytes from the left.
339    ///
340    /// # Safety
341    ///
342    /// UB if the write is out of bounds.
343    /// Aliases if called during the lifetime an overlapping accessor.
344    ///
345    /// [`SSTORE`]: https://www.evm.codes/#55
346    unsafe fn set_byte(host: VM, key: U256, offset: usize, value: u8) {
347        let fixed = FixedBytes::from_slice(&[value]);
348        Self::set::<1>(host, key, offset, fixed)
349    }
350
351    /// Stores a 32-byte EVM word to persistent storage, performing [`SSTORE`]'s only as needed.
352    ///
353    /// # Safety
354    ///
355    /// Aliases if called during the lifetime an overlapping accessor.
356    ///
357    /// [`SSTORE`]: https://www.evm.codes/#55
358    unsafe fn set_word(host: VM, key: U256, value: B256);
359
360    /// Clears the 32-byte word at the given key, performing [`SSTORE`]'s only as needed.
361    ///
362    /// # Safety
363    ///
364    /// Aliases if called during the lifetime an overlapping accessor.
365    ///
366    /// [`SSTORE`]: https://www.evm.codes/#55
367    unsafe fn clear_word(host: VM, key: U256) {
368        Self::set_word(host, key, B256::ZERO)
369    }
370}