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}