tempo_precompiles/storage/types/
slot.rs1use alloy::primitives::U256;
2use std::marker::PhantomData;
3
4use crate::{
5 error::Result,
6 storage::{FieldLocation, Storable, StorageOps},
7};
8
9#[derive(Debug, Clone, Copy)]
28pub struct Slot<T> {
29 slot: U256,
30 ctx: crate::storage::types::LayoutCtx,
31 _ty: PhantomData<T>,
32}
33
34impl<T> Default for Slot<T> {
35 fn default() -> Self {
36 Self::new(U256::ZERO)
37 }
38}
39
40impl<T> Slot<T> {
41 #[inline]
46 pub const fn new(slot: U256) -> Self {
47 Self {
48 slot,
49 ctx: crate::storage::types::LayoutCtx::FULL,
50 _ty: PhantomData,
51 }
52 }
53
54 #[inline]
59 pub const fn new_at_offset(base_slot: U256, offset_slots: usize) -> Self {
60 Self {
61 slot: base_slot.saturating_add(U256::from_limbs([offset_slots as u64, 0, 0, 0])),
62 ctx: crate::storage::types::LayoutCtx::FULL,
63 _ty: PhantomData,
64 }
65 }
66
67 #[inline]
74 pub const fn new_at_loc<const N: usize>(base_slot: U256, loc: FieldLocation) -> Self
75 where
76 T: Storable<N>,
77 {
78 debug_assert!(
79 T::IS_PACKABLE,
80 "`fn new_at_loc` can only be used with packable types"
81 );
82 Self {
83 slot: base_slot.saturating_add(U256::from_limbs([loc.offset_slots as u64, 0, 0, 0])),
84 ctx: crate::storage::types::LayoutCtx::packed(loc.offset_bytes),
85 _ty: PhantomData,
86 }
87 }
88
89 #[inline]
91 pub const fn slot(&self) -> U256 {
92 self.slot
93 }
94
95 #[inline]
99 pub const fn offset(&self) -> Option<usize> {
100 self.ctx.packed_offset()
101 }
102
103 #[inline]
115 pub fn read<S: StorageOps, const N: usize>(&self, storage: &mut S) -> Result<T>
116 where
117 T: Storable<N>,
118 {
119 T::load(storage, self.slot, self.ctx)
120 }
121
122 #[inline]
134 pub fn write<S: StorageOps, const N: usize>(&self, storage: &mut S, value: T) -> Result<()>
135 where
136 T: Storable<N>,
137 {
138 value.store(storage, self.slot, self.ctx)
139 }
140
141 #[inline]
153 pub fn delete<S: StorageOps, const N: usize>(&self, storage: &mut S) -> Result<()>
154 where
155 T: Storable<N>,
156 {
157 T::delete(storage, self.slot, self.ctx)
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::storage::{
165 PrecompileStorageProvider, hashmap::HashMapStorageProvider, mapping_slot,
166 };
167 use alloy::primitives::{Address, B256};
168 use proptest::prelude::*;
169
170 struct TestContract<'a, S> {
172 address: Address,
173 storage: &'a mut S,
174 }
175
176 impl<'a, S: PrecompileStorageProvider> StorageOps for TestContract<'a, S> {
177 fn sstore(&mut self, slot: U256, value: U256) -> Result<()> {
178 self.storage.sstore(self.address, slot, value)
179 }
180
181 fn sload(&mut self, slot: U256) -> Result<U256> {
182 self.storage.sload(self.address, slot)
183 }
184 }
185
186 fn setup_test_contract<'a>(
188 storage: &'a mut HashMapStorageProvider,
189 ) -> TestContract<'a, HashMapStorageProvider> {
190 TestContract {
191 address: Address::random(),
192 storage,
193 }
194 }
195
196 const TEST_SLOT_0: U256 = U256::ZERO;
198 const TEST_SLOT_1: U256 = U256::from_limbs([1, 0, 0, 0]);
199 const TEST_SLOT_2: U256 = U256::from_limbs([2, 0, 0, 0]);
200 const TEST_SLOT_MAX: U256 = U256::MAX;
201
202 fn arb_address() -> impl Strategy<Value = Address> {
204 any::<[u8; 20]>().prop_map(Address::from)
205 }
206
207 fn arb_u256() -> impl Strategy<Value = U256> {
208 any::<[u64; 4]>().prop_map(U256::from_limbs)
209 }
210
211 #[test]
212 fn test_slot_size() {
213 assert_eq!(std::mem::size_of::<Slot<U256>>(), 40);
215 assert_eq!(std::mem::size_of::<Slot<Address>>(), 40);
216 assert_eq!(std::mem::size_of::<Slot<bool>>(), 40);
217 }
218
219 #[test]
220 fn test_slot_number_extraction() {
221 let slot_0 = Slot::<U256>::new(TEST_SLOT_0);
222 let slot_1 = Slot::<Address>::new(TEST_SLOT_1);
223 let slot_max = Slot::<bool>::new(TEST_SLOT_MAX);
224 assert_eq!(slot_0.slot(), U256::ZERO);
225 assert_eq!(slot_1.slot(), TEST_SLOT_1);
226 assert_eq!(slot_max.slot(), U256::MAX);
227 }
228
229 #[test]
230 fn test_slot_edge_case_zero() {
231 let slot = Slot::<U256>::new(TEST_SLOT_0);
233 assert_eq!(slot.slot(), U256::ZERO);
234
235 let mut storage = HashMapStorageProvider::new(1);
236 let mut contract = setup_test_contract(&mut storage);
237 let value = U256::random();
238
239 _ = slot.write(&mut contract, value);
240 let loaded = slot.read(&mut contract).unwrap();
241 assert_eq!(loaded, value);
242 }
243
244 #[test]
245 fn test_slot_edge_case_max() {
246 let slot = Slot::<U256>::new(TEST_SLOT_MAX);
248 assert_eq!(slot.slot(), U256::MAX);
249
250 let mut storage = HashMapStorageProvider::new(1);
251 let mut contract = setup_test_contract(&mut storage);
252
253 let value = U256::random();
254 _ = slot.write(&mut contract, value);
255 let loaded = slot.read(&mut contract).unwrap();
256 assert_eq!(loaded, value);
257 }
258
259 #[test]
260 fn test_slot_read_write_u256() {
261 let mut storage = HashMapStorageProvider::new(1);
262 let mut contract = setup_test_contract(&mut storage);
263 let slot = Slot::<U256>::new(TEST_SLOT_1);
264 let test_value = U256::random();
265
266 _ = slot.write(&mut contract, test_value);
268
269 let loaded = slot.read(&mut contract).unwrap();
271 assert_eq!(loaded, test_value);
272
273 let raw = contract.storage.sload(contract.address, TEST_SLOT_1);
275 assert_eq!(raw, Ok(test_value));
276 }
277
278 #[test]
279 fn test_slot_read_write_address() {
280 let mut storage = HashMapStorageProvider::new(1);
281 let mut contract = setup_test_contract(&mut storage);
282 let test_addr = Address::random();
283 let slot = Slot::<Address>::new(TEST_SLOT_1);
284
285 _ = slot.write(&mut contract, test_addr);
287
288 let loaded = slot.read(&mut contract).unwrap();
290 assert_eq!(loaded, test_addr);
291 }
292
293 #[test]
294 fn test_slot_read_write_bool() {
295 let mut storage = HashMapStorageProvider::new(1);
296 let mut contract = setup_test_contract(&mut storage);
297 let slot = Slot::<bool>::new(TEST_SLOT_1);
298
299 _ = slot.write(&mut contract, true);
301 assert!(slot.read(&mut contract).unwrap());
302
303 _ = slot.write(&mut contract, false);
305 assert!(!slot.read(&mut contract).unwrap());
306 }
307
308 #[test]
309 fn test_slot_read_write_string() {
310 let mut storage = HashMapStorageProvider::new(1);
311 let mut contract = setup_test_contract(&mut storage);
312 let slot = Slot::<String>::new(TEST_SLOT_1);
313
314 let test_name = "TestToken";
315 _ = slot.write(&mut contract, test_name.to_string());
316
317 let loaded = slot.read(&mut contract).unwrap();
318 assert_eq!(loaded, test_name);
319 }
320
321 #[test]
322 fn test_slot_default_value_is_zero() {
323 let mut storage = HashMapStorageProvider::new(1);
324 let mut contract = setup_test_contract(&mut storage);
325 let slot = Slot::<U256>::new(TEST_SLOT_1);
326
327 let value = slot.read(&mut contract).unwrap();
329 assert_eq!(value, U256::ZERO);
330 }
331
332 #[test]
333 fn test_slot_overwrite() {
334 let mut storage = HashMapStorageProvider::new(1);
335 let mut contract = setup_test_contract(&mut storage);
336 let slot = Slot::<u64>::new(TEST_SLOT_1);
337
338 _ = slot.write(&mut contract, 100);
340 assert_eq!(slot.read(&mut contract), Ok(100));
341
342 _ = slot.write(&mut contract, 200);
344 assert_eq!(slot.read(&mut contract), Ok(200));
345 }
346
347 proptest! {
348 #![proptest_config(ProptestConfig::with_cases(500))]
349
350 #[test]
351 fn proptest_slot_read_write_u256(value in arb_u256()) {
352 let mut storage = HashMapStorageProvider::new(1);
353 let mut contract = setup_test_contract(&mut storage);
354 let slot = Slot::<U256>::new(TEST_SLOT_2);
355
356 slot.write(&mut contract, value)?;
358 let loaded = slot.read(&mut contract)?;
359 prop_assert_eq!(loaded, value, "roundtrip failed");
360
361 slot.delete(&mut contract)?;
363 let after_delete = slot.read(&mut contract)?;
364 prop_assert_eq!(after_delete, U256::ZERO, "not zero after delete");
365 }
366
367 #[test]
368 fn proptest_slot_read_write_address(addr_value in arb_address()) {
369 let mut storage = HashMapStorageProvider::new(1);
370 let mut contract = setup_test_contract(&mut storage);
371 let slot = Slot::<Address>::new(TEST_SLOT_1);
372
373 slot.write(&mut contract, addr_value)?;
375 let loaded = slot.read(&mut contract)?;
376 prop_assert_eq!(loaded, addr_value, "address roundtrip failed");
377 }
378
379 #[test]
380 fn proptest_slot_isolation(value1 in arb_u256(), value2 in arb_u256()) {
381 let mut storage = HashMapStorageProvider::new(1);
382 let mut contract = setup_test_contract(&mut storage);
383 let slot1 = Slot::<U256>::new(TEST_SLOT_1);
384 let slot2 = Slot::<U256>::new(TEST_SLOT_2);
385
386 slot1.write(&mut contract, value1)?;
387 slot2.write(&mut contract, value2)?;
388
389 let loaded1 = slot1.read(&mut contract)?;
391 let loaded2 = slot2.read(&mut contract)?;
392
393 prop_assert_eq!(loaded1, value1, "slot 1 value changed");
394 prop_assert_eq!(loaded2, value2, "slot 2 value changed");
395
396 slot1.delete(&mut contract)?;
398 let after_delete1 = slot1.read(&mut contract)?;
399 let after_delete2 = slot2.read(&mut contract)?;
400
401 prop_assert_eq!(after_delete1, U256::ZERO, "slot 1 not deleted");
402 prop_assert_eq!(after_delete2, value2, "slot 2 affected by slot 1 delete");
403 }
404 }
405
406 #[test]
409 fn test_slot_at_offset() -> eyre::Result<()> {
410 let mut storage = HashMapStorageProvider::new(1);
411 let mut contract = setup_test_contract(&mut storage);
412
413 let pair_key: B256 = U256::from(0xabcd).into();
414 let orderbook_base_slot = mapping_slot(pair_key, U256::ZERO);
415
416 let base_address = Address::random();
417
418 let slot = Slot::<Address>::new_at_offset(orderbook_base_slot, 0);
420 slot.write(&mut contract, base_address)?;
421
422 let read_address = slot.read(&mut contract)?;
424
425 assert_eq!(read_address, base_address);
426
427 slot.delete(&mut contract)?;
429
430 let deleted = slot.read(&mut contract)?;
431
432 assert_eq!(deleted, Address::ZERO);
433
434 Ok(())
435 }
436
437 #[test]
438 fn test_slot_at_offset_multi_slot_value() -> eyre::Result<()> {
439 let mut storage = HashMapStorageProvider::new(1);
440 let mut contract = setup_test_contract(&mut storage);
441
442 let struct_key: B256 = U256::from(0xabcd).into();
443 let struct_base = mapping_slot(struct_key, U256::ZERO);
444
445 let test_string = "Hello, Orderbook!".to_string();
447
448 let slot = Slot::<String>::new_at_offset(struct_base, 2);
449 slot.write(&mut contract, test_string.clone())?;
450
451 let read_string = slot.read(&mut contract)?;
452
453 assert_eq!(read_string, test_string);
454
455 Ok(())
456 }
457
458 #[test]
459 fn test_multiple_primitive_fields() -> eyre::Result<()> {
460 let mut storage = HashMapStorageProvider::new(1);
461 let mut contract = setup_test_contract(&mut storage);
462
463 let key: B256 = U256::from(0x9999).into();
464 let base = mapping_slot(key, U256::ZERO);
465
466 let field_0 = Address::random();
468 let field_1: u64 = 12345;
469 let field_2 = U256::from(999999);
470
471 Slot::<Address>::new_at_offset(base, 0).write(&mut contract, field_0)?;
472 Slot::<u64>::new_at_offset(base, 1).write(&mut contract, field_1)?;
473 Slot::<U256>::new_at_offset(base, 2).write(&mut contract, field_2)?;
474
475 let read_0 = Slot::<Address>::new_at_offset(base, 0).read(&mut contract)?;
477 let read_1 = Slot::<u64>::new_at_offset(base, 1).read(&mut contract)?;
478 let read_2 = Slot::<U256>::new_at_offset(base, 2).read(&mut contract)?;
479
480 assert_eq!(read_0, field_0);
481 assert_eq!(read_1, field_1);
482 assert_eq!(read_2, field_2);
483
484 Ok(())
485 }
486}