tempo_precompiles/storage/
packing.rs

1//! Shared utilities for packing and unpacking values in EVM storage slots.
2//!
3//! This module provides helper functions for bit-level manipulation of storage slots,
4//! enabling efficient packing of multiple small values into single 32-byte slots.
5//!
6//! Packing only applies to primitive types where `LAYOUT::Bytes(count) && count < 32`.
7//! Non-primitives (structs, fixed-size arrays, dynamic types) have `LAYOUT = Layout::Slot`.
8//!
9//! ## Solidity Compatibility
10//!
11//! This implementation matches Solidity's value packing convention:
12//! - Values are right-aligned within their byte range
13//! - Types smaller than 32 bytes can pack multiple per slot when dimensions align
14
15use alloy::primitives::U256;
16
17use crate::{
18    error::Result,
19    storage::{Layout, Storable},
20};
21
22/// Location information for a packed field within a storage slot.
23#[derive(Debug, Clone, Copy)]
24pub struct FieldLocation {
25    /// Offset in slots from the base slot
26    pub offset_slots: usize,
27    /// Offset in bytes within the target slot
28    pub offset_bytes: usize,
29    /// Size of the field in bytes
30    pub size: usize,
31}
32
33impl FieldLocation {
34    /// Create a new field location
35    #[inline]
36    pub const fn new(offset_slots: usize, offset_bytes: usize, size: usize) -> Self {
37        Self {
38            offset_slots,
39            offset_bytes,
40            size,
41        }
42    }
43}
44
45/// Whether a given amount of bytes should be packed, or not.
46#[inline]
47pub fn is_packable(bytes: usize) -> bool {
48    bytes < 32 && 32 % bytes == 0
49}
50
51/// Create a bit mask for a value of the given byte size.
52///
53/// For values less than 32 bytes, returns a mask with the appropriate number of bits set.
54/// For 32-byte values, returns U256::MAX.
55#[inline]
56pub fn create_element_mask(byte_count: usize) -> U256 {
57    if byte_count >= 32 {
58        U256::MAX
59    } else {
60        (U256::ONE << (byte_count * 8)) - U256::ONE
61    }
62}
63
64/// Extract a packed value from a storage slot at a given byte offset.
65#[inline]
66pub fn extract_packed_value<const SLOTS: usize, T: Storable<SLOTS>>(
67    slot_value: U256,
68    offset: usize,
69    bytes: usize,
70) -> Result<T> {
71    debug_assert!(
72        matches!(T::LAYOUT, Layout::Bytes(..)),
73        "Packing is only supported by primitive types"
74    );
75
76    // Validate that the value doesn't span slot boundaries
77    if offset + bytes > 32 {
78        return Err(crate::error::TempoPrecompileError::Fatal(format!(
79            "Value of {} bytes at offset {} would span slot boundary (max offset: {})",
80            bytes,
81            offset,
82            32 - bytes
83        )));
84    }
85
86    // Calculate how many bits to shift right to align the value
87    let shift_bits = offset * 8;
88
89    // Create mask for the value's bit width
90    let mask = if bytes == 32 {
91        U256::MAX
92    } else {
93        (U256::ONE << (bytes * 8)) - U256::ONE
94    };
95
96    // Extract and right-align the value
97    T::from_evm_words(std::array::from_fn(|_| (slot_value >> shift_bits) & mask))
98}
99
100/// Insert a packed value into a storage slot at a given byte offset.
101#[inline]
102pub fn insert_packed_value<const SLOTS: usize, T: Storable<SLOTS>>(
103    current: U256,
104    value: &T,
105    offset: usize,
106    bytes: usize,
107) -> Result<U256> {
108    debug_assert!(
109        matches!(T::LAYOUT, Layout::Bytes(..)),
110        "Packing is only supported by primitive types"
111    );
112
113    // Validate that the value doesn't span slot boundaries
114    if offset + bytes > 32 {
115        return Err(crate::error::TempoPrecompileError::Fatal(format!(
116            "Value of {} bytes at offset {} would span slot boundary (max offset: {})",
117            bytes,
118            offset,
119            32 - bytes
120        )));
121    }
122
123    // Encode field to its canonical right-aligned U256 representation
124    let field_value = value.to_evm_words()?[0];
125
126    // Calculate shift and mask
127    let shift_bits = offset * 8;
128    let mask = if bytes == 32 {
129        U256::MAX
130    } else {
131        (U256::ONE << (bytes * 8)) - U256::ONE
132    };
133
134    // Clear the bits for this field in the current slot value
135    let clear_mask = !(mask << shift_bits);
136    let cleared = current & clear_mask;
137
138    // Position the new value and combine with cleared slot
139    let positioned = (field_value & mask) << shift_bits;
140    Ok(cleared | positioned)
141}
142
143/// Zero out a packed value in a storage slot at a given byte offset.
144///
145/// This is the inverse operation to `insert_packed_value`, clearing the bits
146/// for a specific field while preserving other packed values in the slot.
147#[inline]
148pub fn zero_packed_value(current: U256, offset: usize, bytes: usize) -> Result<U256> {
149    // Validate that the value doesn't span slot boundaries
150    if offset + bytes > 32 {
151        return Err(crate::error::TempoPrecompileError::Fatal(format!(
152            "Value of {} bytes at offset {} would span slot boundary (max offset: {})",
153            bytes,
154            offset,
155            32 - bytes
156        )));
157    }
158
159    let mask = create_element_mask(bytes);
160    let shifted_mask = mask << (offset * 8);
161    Ok(current & !shifted_mask)
162}
163
164/// Calculate which slot an array element at index `idx` starts in.
165#[inline]
166pub const fn calc_element_slot(idx: usize, elem_bytes: usize) -> usize {
167    (idx * elem_bytes) / 32
168}
169
170/// Calculate the byte offset within a slot for an array element at index `idx`.
171#[inline]
172pub const fn calc_element_offset(idx: usize, elem_bytes: usize) -> usize {
173    (idx * elem_bytes) % 32
174}
175
176/// Calculate the total number of slots needed for an array.
177#[inline]
178pub const fn calc_packed_slot_count(n: usize, elem_bytes: usize) -> usize {
179    (n * elem_bytes).div_ceil(32)
180}
181
182/// Extract a field value from a storage slot for testing purposes.
183///
184/// This is a convenience wrapper around `extract_packed_value` that's more
185/// ergonomic for use in test assertions.
186pub fn extract_field<T: Storable<1>>(slot_value: U256, offset: usize, bytes: usize) -> Result<T> {
187    extract_packed_value(slot_value, offset, bytes)
188}
189
190/// Test helper function for constructing U256 slot values from hex string literals.
191///
192/// Takes an array of hex strings (with or without "0x" prefix), concatenates
193/// them left-to-right, left-pads with zeros to 32 bytes, and returns a U256.
194///
195/// # Example
196/// ```ignore
197/// let slot = gen_slot_from(&[
198///     "0x2a",                                        // 1 byte
199///     "0x1111111111111111111111111111111111111111",  // 20 bytes
200///     "0x01",                                        // 1 byte
201/// ]);
202/// // Produces: [10 zeros] [0x2a] [20 bytes of 0x11] [0x01]
203/// ```
204pub fn gen_slot_from(values: &[&str]) -> U256 {
205    let mut bytes = Vec::new();
206
207    for value in values {
208        let hex_str = value.strip_prefix("0x").unwrap_or(value);
209
210        // Parse hex string to bytes
211        assert!(
212            hex_str.len() % 2 == 0,
213            "Hex string '{value}' has odd length"
214        );
215
216        for i in (0..hex_str.len()).step_by(2) {
217            let byte_str = &hex_str[i..i + 2];
218            let byte = u8::from_str_radix(byte_str, 16)
219                .unwrap_or_else(|e| panic!("Invalid hex in '{value}': {e}"));
220            bytes.push(byte);
221        }
222    }
223
224    assert!(
225        bytes.len() <= 32,
226        "Total bytes ({}) exceed 32-byte slot limit",
227        bytes.len()
228    );
229
230    // Left-pad with zeros to 32 bytes
231    let mut slot_bytes = [0u8; 32];
232    let start_idx = 32 - bytes.len();
233    slot_bytes[start_idx..].copy_from_slice(&bytes);
234
235    U256::from_be_bytes(slot_bytes)
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use alloy::primitives::Address;
242
243    // -- HELPER FUNCTION TESTS ----------------------------------------------------
244
245    #[test]
246    fn test_calc_element_slot() {
247        // u8 array (1 byte per element)
248        assert_eq!(calc_element_slot(0, 1), 0);
249        assert_eq!(calc_element_slot(31, 1), 0);
250        assert_eq!(calc_element_slot(32, 1), 1);
251        assert_eq!(calc_element_slot(63, 1), 1);
252        assert_eq!(calc_element_slot(64, 1), 2);
253
254        // u16 array (2 bytes per element)
255        assert_eq!(calc_element_slot(0, 2), 0);
256        assert_eq!(calc_element_slot(15, 2), 0);
257        assert_eq!(calc_element_slot(16, 2), 1);
258
259        // Address array (20 bytes per element)
260        assert_eq!(calc_element_slot(0, 20), 0);
261        assert_eq!(calc_element_slot(1, 20), 0);
262        assert_eq!(calc_element_slot(2, 20), 1); // 40 bytes = 2 slots
263    }
264
265    #[test]
266    fn test_calc_element_offset() {
267        // u8 array
268        assert_eq!(calc_element_offset(0, 1), 0);
269        assert_eq!(calc_element_offset(1, 1), 1);
270        assert_eq!(calc_element_offset(31, 1), 31);
271        assert_eq!(calc_element_offset(32, 1), 0);
272
273        // u16 array
274        assert_eq!(calc_element_offset(0, 2), 0);
275        assert_eq!(calc_element_offset(1, 2), 2);
276        assert_eq!(calc_element_offset(15, 2), 30);
277        assert_eq!(calc_element_offset(16, 2), 0);
278
279        // address array
280        assert_eq!(calc_element_offset(0, 20), 0);
281        assert_eq!(calc_element_offset(1, 20), 20);
282        assert_eq!(calc_element_offset(2, 20), 8);
283    }
284
285    #[test]
286    fn test_calc_packed_slot_count() {
287        // u8 array
288        assert_eq!(calc_packed_slot_count(10, 1), 1); // [u8; 10] = 10 bytes
289        assert_eq!(calc_packed_slot_count(32, 1), 1); // [u8; 32] = 32 bytes
290        assert_eq!(calc_packed_slot_count(33, 1), 2); // [u8; 33] = 33 bytes
291        assert_eq!(calc_packed_slot_count(100, 1), 4); // [u8; 100] = 100 bytes
292
293        // u16 array
294        assert_eq!(calc_packed_slot_count(16, 2), 1); // [u16; 16] = 32 bytes
295        assert_eq!(calc_packed_slot_count(17, 2), 2); // [u16; 17] = 34 bytes
296
297        // address array
298        assert_eq!(calc_packed_slot_count(1, 20), 1); // [Address; 1] = 20 bytes
299        assert_eq!(calc_packed_slot_count(2, 20), 2); // [Address; 2] = 40 bytes
300        assert_eq!(calc_packed_slot_count(3, 20), 2); // [Address; 3] = 60 bytes
301    }
302
303    #[test]
304    fn test_create_element_mask() {
305        // 1 byte mask
306        assert_eq!(create_element_mask(1), U256::from(0xff));
307
308        // 2 byte mask
309        assert_eq!(create_element_mask(2), U256::from(0xffff));
310
311        // 4 byte mask
312        assert_eq!(create_element_mask(4), U256::from(0xffffffffu32));
313
314        // 8 byte mask
315        assert_eq!(create_element_mask(8), U256::from(u64::MAX));
316
317        // 16 byte mask (u128::MAX)
318        assert_eq!(create_element_mask(16), U256::from(u128::MAX));
319
320        // 32 byte mask
321        assert_eq!(create_element_mask(32), U256::MAX);
322
323        // Greater than 32 bytes should also return MAX
324        assert_eq!(create_element_mask(64), U256::MAX);
325    }
326
327    #[test]
328    fn test_zero_packed_value() {
329        // Start with a slot containing multiple packed u8 values
330        let slot = gen_slot_from(&[
331            "0xff", // offset 3 (1 byte)
332            "0x56", // offset 2 (1 byte)
333            "0x34", // offset 1 (1 byte)
334            "0x12", // offset 0 (1 byte)
335        ]);
336
337        // Zero out the value at offset 1
338        let cleared = zero_packed_value(slot, 1, 1).unwrap();
339        let expected = gen_slot_from(&[
340            "0xff", // offset 3 - unchanged
341            "0x56", // offset 2 - unchanged
342            "0x00", // offset 1 - cleared
343            "0x12", // offset 0 - unchanged
344        ]);
345        assert_eq!(cleared, expected, "Should zero offset 1");
346
347        // Zero out a u16 (2 bytes) at offset 0
348        let slot = gen_slot_from(&["0x5678", "0x1234"]);
349        let cleared = zero_packed_value(slot, 0, 2).unwrap();
350        let expected = gen_slot_from(&["0x5678", "0x0000"]);
351        assert_eq!(cleared, expected, "Should zero u16 at offset 0");
352
353        // Zero out the last byte in a slot
354        let slot = gen_slot_from(&["0xff"]);
355        let cleared = zero_packed_value(slot, 0, 1).unwrap();
356        assert_eq!(cleared, U256::ZERO, "Should zero entire slot");
357    }
358
359    #[test]
360    fn test_extract_field_wrapper() {
361        let address = Address::random();
362        let mut slot = U256::ZERO;
363        slot = insert_packed_value(slot, &address, 0, 20).unwrap();
364
365        // Use extract_field wrapper
366        let extracted: Address = extract_field(slot, 0, 20).unwrap();
367        assert_eq!(extracted, address);
368    }
369
370    // -- BOUNDARY VALIDATION ------------------------------------------------------
371
372    #[test]
373    fn test_boundary_validation_rejects_spanning() {
374        // Address (20 bytes) at offset 13 would span slot boundary (13 + 20 = 33 > 32)
375        let addr = Address::random();
376        let result = insert_packed_value(U256::ZERO, &addr, 13, 20);
377        assert!(
378            result.is_err(),
379            "Should reject address at offset 13 (would span slot)"
380        );
381
382        // u16 (2 bytes) at offset 31 would span slot boundary (31 + 2 = 33 > 32)
383        let val: u16 = 42;
384        let result = insert_packed_value(U256::ZERO, &val, 31, 2);
385        assert!(
386            result.is_err(),
387            "Should reject u16 at offset 31 (would span slot)"
388        );
389
390        // u32 (4 bytes) at offset 29 would span slot boundary (29 + 4 = 33 > 32)
391        let val: u32 = 42;
392        let result = insert_packed_value(U256::ZERO, &val, 29, 4);
393        assert!(
394            result.is_err(),
395            "Should reject u32 at offset 29 (would span slot)"
396        );
397
398        // Test extract as well
399        let result = extract_packed_value::<1, Address>(U256::ZERO, 13, 20);
400        assert!(
401            result.is_err(),
402            "Should reject extracting address from offset 13"
403        );
404    }
405
406    #[test]
407    fn test_boundary_validation_accepts_valid() {
408        // Address (20 bytes) at offset 12 is valid (12 + 20 = 32)
409        let addr = Address::random();
410        let result = insert_packed_value(U256::ZERO, &addr, 12, 20);
411        assert!(result.is_ok(), "Should accept address at offset 12");
412
413        // u16 (2 bytes) at offset 30 is valid (30 + 2 = 32)
414        let val: u16 = 42;
415        let result = insert_packed_value(U256::ZERO, &val, 30, 2);
416        assert!(result.is_ok(), "Should accept u16 at offset 30");
417
418        // u8 (1 byte) at offset 31 is valid (31 + 1 = 32)
419        let val: u8 = 42;
420        let result = insert_packed_value(U256::ZERO, &val, 31, 1);
421        assert!(result.is_ok(), "Should accept u8 at offset 31");
422
423        // U256 (32 bytes) at offset 0 is valid (0 + 32 = 32)
424        let val = U256::from(42);
425        let result = insert_packed_value(U256::ZERO, &val, 0, 32);
426        assert!(result.is_ok(), "Should accept U256 at offset 0");
427    }
428
429    // -- PACKING VALIDATION ------------------------------------------------------
430
431    #[test]
432    fn test_bool() {
433        // single bool
434        let expected = gen_slot_from(&[
435            "0x01", // offset 0 (1 byte)
436        ]);
437
438        let slot = insert_packed_value(U256::ZERO, &true, 0, 1).unwrap();
439        assert_eq!(
440            slot, expected,
441            "Single bool [true] should match Solidity layout"
442        );
443        assert!(extract_packed_value::<1, bool>(slot, 0, 1).unwrap());
444
445        // two bools
446        let expected = gen_slot_from(&[
447            "0x01", // offset 1 (1 byte)
448            "0x01", // offset 0 (1 byte)
449        ]);
450
451        let mut slot = U256::ZERO;
452        slot = insert_packed_value(slot, &true, 0, 1).unwrap();
453        slot = insert_packed_value(slot, &true, 1, 1).unwrap();
454        assert_eq!(slot, expected, "[true, true] should match Solidity layout");
455        assert!(extract_packed_value::<1, bool>(slot, 0, 1).unwrap());
456        assert!(extract_packed_value::<1, bool>(slot, 1, 1).unwrap());
457    }
458
459    #[test]
460    fn test_u8_packing() {
461        // Pack multiple u8 values
462        let v1: u8 = 0x12;
463        let v2: u8 = 0x34;
464        let v3: u8 = 0x56;
465        let v4: u8 = u8::MAX;
466
467        let expected = gen_slot_from(&[
468            "0xff", // offset 3 (1 byte)
469            "0x56", // offset 2 (1 byte)
470            "0x34", // offset 1 (1 byte)
471            "0x12", // offset 0 (1 byte)
472        ]);
473
474        let mut slot = U256::ZERO;
475        slot = insert_packed_value(slot, &v1, 0, 1).unwrap();
476        slot = insert_packed_value(slot, &v2, 1, 1).unwrap();
477        slot = insert_packed_value(slot, &v3, 2, 1).unwrap();
478        slot = insert_packed_value(slot, &v4, 3, 1).unwrap();
479
480        assert_eq!(slot, expected, "u8 packing should match Solidity layout");
481        assert_eq!(extract_packed_value::<1, u8>(slot, 0, 1).unwrap(), v1);
482        assert_eq!(extract_packed_value::<1, u8>(slot, 1, 1).unwrap(), v2);
483        assert_eq!(extract_packed_value::<1, u8>(slot, 2, 1).unwrap(), v3);
484        assert_eq!(extract_packed_value::<1, u8>(slot, 3, 1).unwrap(), v4);
485    }
486
487    #[test]
488    fn test_u16_packing() {
489        // Pack u16 values including max
490        let v1: u16 = 0x1234;
491        let v2: u16 = 0x5678;
492        let v3: u16 = u16::MAX;
493
494        let expected = gen_slot_from(&[
495            "0xffff", // offset 4 (2 bytes)
496            "0x5678", // offset 2 (2 bytes)
497            "0x1234", // offset 0 (2 bytes)
498        ]);
499
500        let mut slot = U256::ZERO;
501        slot = insert_packed_value(slot, &v1, 0, 2).unwrap();
502        slot = insert_packed_value(slot, &v2, 2, 2).unwrap();
503        slot = insert_packed_value(slot, &v3, 4, 2).unwrap();
504
505        assert_eq!(slot, expected, "u16 packing should match Solidity layout");
506        assert_eq!(extract_packed_value::<1, u16>(slot, 0, 2).unwrap(), v1);
507        assert_eq!(extract_packed_value::<1, u16>(slot, 2, 2).unwrap(), v2);
508        assert_eq!(extract_packed_value::<1, u16>(slot, 4, 2).unwrap(), v3);
509    }
510
511    #[test]
512    fn test_u32_packing() {
513        // Pack u32 values
514        let v1: u32 = 0x12345678;
515        let v2: u32 = u32::MAX;
516
517        let expected = gen_slot_from(&[
518            "0xffffffff", // offset 4 (4 bytes)
519            "0x12345678", // offset 0 (4 bytes)
520        ]);
521
522        let mut slot = U256::ZERO;
523        slot = insert_packed_value(slot, &v1, 0, 4).unwrap();
524        slot = insert_packed_value(slot, &v2, 4, 4).unwrap();
525
526        assert_eq!(slot, expected, "u32 packing should match Solidity layout");
527        assert_eq!(extract_packed_value::<1, u32>(slot, 0, 4).unwrap(), v1);
528        assert_eq!(extract_packed_value::<1, u32>(slot, 4, 4).unwrap(), v2);
529    }
530
531    #[test]
532    fn test_u64_packing() {
533        // Pack u64 values
534        let v1: u64 = 0x123456789abcdef0;
535        let v2: u64 = u64::MAX;
536
537        let expected = gen_slot_from(&[
538            "0xffffffffffffffff", // offset 8 (8 bytes)
539            "0x123456789abcdef0", // offset 0 (8 bytes)
540        ]);
541
542        let mut slot = U256::ZERO;
543        slot = insert_packed_value(slot, &v1, 0, 8).unwrap();
544        slot = insert_packed_value(slot, &v2, 8, 8).unwrap();
545
546        assert_eq!(slot, expected, "u64 packing should match Solidity layout");
547        assert_eq!(extract_packed_value::<1, u64>(slot, 0, 8).unwrap(), v1);
548        assert_eq!(extract_packed_value::<1, u64>(slot, 8, 8).unwrap(), v2);
549    }
550
551    #[test]
552    fn test_u128_packing() {
553        // Pack two u128 values (fills entire slot)
554        let v1: u128 = 0x123456789abcdef0fedcba9876543210;
555        let v2: u128 = u128::MAX;
556
557        let expected = gen_slot_from(&[
558            "0xffffffffffffffffffffffffffffffff", // offset 16 (16 bytes)
559            "0x123456789abcdef0fedcba9876543210", // offset 0 (16 bytes)
560        ]);
561
562        let mut slot = U256::ZERO;
563        slot = insert_packed_value(slot, &v1, 0, 16).unwrap();
564        slot = insert_packed_value(slot, &v2, 16, 16).unwrap();
565
566        assert_eq!(slot, expected, "u128 packing should match Solidity layout");
567        assert_eq!(extract_packed_value::<1, u128>(slot, 0, 16).unwrap(), v1);
568        assert_eq!(extract_packed_value::<1, u128>(slot, 16, 16).unwrap(), v2);
569    }
570
571    #[test]
572    fn test_u256_packing() {
573        // u256 takes full slot
574        let value = U256::from_be_bytes([
575            0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
576            0x32, 0x10, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc,
577            0xdd, 0xee, 0xff, 0x00,
578        ]);
579
580        let expected =
581            gen_slot_from(&["0x123456789abcdef0fedcba9876543210112233445566778899aabbccddeeff00"]);
582
583        let slot = insert_packed_value(U256::ZERO, &value, 0, 32).unwrap();
584        assert_eq!(slot, expected, "u256 packing should match Solidity layout");
585        assert_eq!(extract_packed_value::<1, U256>(slot, 0, 32).unwrap(), value);
586
587        // Test U256::MAX
588        let slot = insert_packed_value(U256::ZERO, &U256::MAX, 0, 32).unwrap();
589        assert_eq!(
590            extract_packed_value::<1, U256>(slot, 0, 32).unwrap(),
591            U256::MAX
592        );
593    }
594
595    #[test]
596    fn test_i8_packing() {
597        // Pack signed i8 values including negative numbers
598        let v1: i8 = -128; // i8::MIN
599        let v2: i8 = 0;
600        let v3: i8 = 127; // i8::MAX
601        let v4: i8 = -1;
602
603        let expected = gen_slot_from(&[
604            "0xff", // offset 3: -1 (two's complement)
605            "0x7f", // offset 2: 127
606            "0x00", // offset 1: 0
607            "0x80", // offset 0: -128 (two's complement)
608        ]);
609
610        let mut slot = U256::ZERO;
611        slot = insert_packed_value(slot, &v1, 0, 1).unwrap();
612        slot = insert_packed_value(slot, &v2, 1, 1).unwrap();
613        slot = insert_packed_value(slot, &v3, 2, 1).unwrap();
614        slot = insert_packed_value(slot, &v4, 3, 1).unwrap();
615
616        assert_eq!(slot, expected, "i8 packing should match Solidity layout");
617        assert_eq!(extract_packed_value::<1, i8>(slot, 0, 1).unwrap(), v1);
618        assert_eq!(extract_packed_value::<1, i8>(slot, 1, 1).unwrap(), v2);
619        assert_eq!(extract_packed_value::<1, i8>(slot, 2, 1).unwrap(), v3);
620        assert_eq!(extract_packed_value::<1, i8>(slot, 3, 1).unwrap(), v4);
621    }
622
623    #[test]
624    fn test_i16_packing() {
625        // Pack signed i16 values
626        let v1: i16 = -32768; // i16::MIN
627        let v2: i16 = 32767; // i16::MAX
628        let v3: i16 = -1;
629
630        let expected = gen_slot_from(&[
631            "0xffff", // offset 4: -1 (two's complement)
632            "0x7fff", // offset 2: 32767
633            "0x8000", // offset 0: -32768 (two's complement)
634        ]);
635
636        let mut slot = U256::ZERO;
637        slot = insert_packed_value(slot, &v1, 0, 2).unwrap();
638        slot = insert_packed_value(slot, &v2, 2, 2).unwrap();
639        slot = insert_packed_value(slot, &v3, 4, 2).unwrap();
640
641        assert_eq!(slot, expected, "i16 packing should match Solidity layout");
642        assert_eq!(extract_packed_value::<1, i16>(slot, 0, 2).unwrap(), v1);
643        assert_eq!(extract_packed_value::<1, i16>(slot, 2, 2).unwrap(), v2);
644        assert_eq!(extract_packed_value::<1, i16>(slot, 4, 2).unwrap(), v3);
645    }
646
647    #[test]
648    fn test_i32_packing() {
649        // Pack signed i32 values
650        let v1: i32 = -2147483648; // i32::MIN
651        let v2: i32 = 2147483647; // i32::MAX
652
653        let expected = gen_slot_from(&[
654            "0x7fffffff", // offset 4: i32::MAX
655            "0x80000000", // offset 0: i32::MIN (two's complement)
656        ]);
657
658        let mut slot = U256::ZERO;
659        slot = insert_packed_value(slot, &v1, 0, 4).unwrap();
660        slot = insert_packed_value(slot, &v2, 4, 4).unwrap();
661
662        assert_eq!(slot, expected, "i32 packing should match Solidity layout");
663        assert_eq!(extract_packed_value::<1, i32>(slot, 0, 4).unwrap(), v1);
664        assert_eq!(extract_packed_value::<1, i32>(slot, 4, 4).unwrap(), v2);
665    }
666
667    #[test]
668    fn test_i64_packing() {
669        // Pack signed i64 values
670        let v1: i64 = -9223372036854775808; // i64::MIN
671        let v2: i64 = 9223372036854775807; // i64::MAX
672
673        let expected = gen_slot_from(&[
674            "0x7fffffffffffffff", // offset 8: i64::MAX
675            "0x8000000000000000", // offset 0: i64::MIN (two's complement)
676        ]);
677
678        let mut slot = U256::ZERO;
679        slot = insert_packed_value(slot, &v1, 0, 8).unwrap();
680        slot = insert_packed_value(slot, &v2, 8, 8).unwrap();
681
682        assert_eq!(slot, expected, "i64 packing should match Solidity layout");
683        assert_eq!(extract_packed_value::<1, i64>(slot, 0, 8).unwrap(), v1);
684        assert_eq!(extract_packed_value::<1, i64>(slot, 8, 8).unwrap(), v2);
685    }
686
687    #[test]
688    fn test_i128_packing() {
689        // Pack two i128 values (fills entire slot)
690        let v1: i128 = -170141183460469231731687303715884105728; // i128::MIN
691        let v2: i128 = 170141183460469231731687303715884105727; // i128::MAX
692
693        let expected = gen_slot_from(&[
694            "0x7fffffffffffffffffffffffffffffff", // offset 16: i128::MAX
695            "0x80000000000000000000000000000000", // offset 0: i128::MIN (two's complement)
696        ]);
697
698        let mut slot = U256::ZERO;
699        slot = insert_packed_value(slot, &v1, 0, 16).unwrap();
700        slot = insert_packed_value(slot, &v2, 16, 16).unwrap();
701
702        assert_eq!(slot, expected, "i128 packing should match Solidity layout");
703        assert_eq!(extract_packed_value::<1, i128>(slot, 0, 16).unwrap(), v1);
704        assert_eq!(extract_packed_value::<1, i128>(slot, 16, 16).unwrap(), v2);
705    }
706
707    #[test]
708    fn test_mixed_uint_packing() {
709        // Pack various types together: u8 + u16 + u32 + u64
710        let v1: u8 = 0xaa;
711        let v2: u16 = 0xbbcc;
712        let v3: u32 = 0xddeeff00;
713        let v4: u64 = 0x1122334455667788;
714
715        let expected = gen_slot_from(&[
716            "0x1122334455667788", // u64 at offset 7 (8 bytes)
717            "0xddeeff00",         // u32 at offset 3 (4 bytes)
718            "0xbbcc",             // u16 at offset 1 (2 bytes)
719            "0xaa",               // u8 at offset 0 (1 byte)
720        ]);
721
722        let mut slot = U256::ZERO;
723        slot = insert_packed_value(slot, &v1, 0, 1).unwrap();
724        slot = insert_packed_value(slot, &v2, 1, 2).unwrap();
725        slot = insert_packed_value(slot, &v3, 3, 4).unwrap();
726        slot = insert_packed_value(slot, &v4, 7, 8).unwrap();
727
728        assert_eq!(
729            slot, expected,
730            "Mixed types packing should match Solidity layout"
731        );
732        assert_eq!(extract_packed_value::<1, u8>(slot, 0, 1).unwrap(), v1);
733        assert_eq!(extract_packed_value::<1, u16>(slot, 1, 2).unwrap(), v2);
734        assert_eq!(extract_packed_value::<1, u32>(slot, 3, 4).unwrap(), v3);
735        assert_eq!(extract_packed_value::<1, u64>(slot, 7, 8).unwrap(), v4);
736    }
737
738    #[test]
739    fn test_mixed_type_packing() {
740        let addr = Address::from([0x11; 20]);
741        let number: u8 = 0x2a;
742
743        let expected = gen_slot_from(&[
744            "0x2a",                                       // offset 21 (1 byte)
745            "0x1111111111111111111111111111111111111111", // offset 1 (20 bytes)
746            "0x01",                                       // offset 0 (1 byte)
747        ]);
748
749        let mut slot = U256::ZERO;
750        slot = insert_packed_value(slot, &true, 0, 1).unwrap();
751        slot = insert_packed_value(slot, &addr, 1, 20).unwrap();
752        slot = insert_packed_value(slot, &number, 21, 1).unwrap();
753        assert_eq!(
754            slot, expected,
755            "[bool, address, u8] should match Solidity layout"
756        );
757        assert!(extract_packed_value::<1, bool>(slot, 0, 1).unwrap());
758        assert_eq!(
759            extract_packed_value::<1, Address>(slot, 1, 20).unwrap(),
760            addr
761        );
762        assert_eq!(extract_packed_value::<1, u8>(slot, 21, 1).unwrap(), number);
763    }
764
765    #[test]
766    fn test_zero_values() {
767        // Ensure zero values pack correctly and don't bleed bits
768        let v1: u8 = 0;
769        let v2: u16 = 0;
770        let v3: u32 = 0;
771
772        let expected = U256::ZERO;
773
774        let mut slot = U256::ZERO;
775        slot = insert_packed_value(slot, &v1, 0, 1).unwrap();
776        slot = insert_packed_value(slot, &v2, 1, 2).unwrap();
777        slot = insert_packed_value(slot, &v3, 3, 4).unwrap();
778
779        assert_eq!(slot, expected, "Zero values should produce zero slot");
780        assert_eq!(extract_packed_value::<1, u8>(slot, 0, 1).unwrap(), 0);
781        assert_eq!(extract_packed_value::<1, u16>(slot, 1, 2).unwrap(), 0);
782        assert_eq!(extract_packed_value::<1, u32>(slot, 3, 4).unwrap(), 0);
783
784        // Test that zeros don't interfere with non-zero values
785        let v4: u8 = 0xff;
786        slot = insert_packed_value(slot, &v4, 10, 1).unwrap();
787        assert_eq!(extract_packed_value::<1, u8>(slot, 0, 1).unwrap(), 0);
788        assert_eq!(extract_packed_value::<1, u8>(slot, 10, 1).unwrap(), 0xff);
789    }
790
791    // -- SLOT PACKED FIELD TESTS ------------------------------------------
792
793    use crate::storage::{
794        PrecompileStorageProvider, StorageOps, hashmap::HashMapStorageProvider, types::Slot,
795    };
796
797    /// Test helper that implements StorageOps for integration tests
798    struct TestContract<'a, S> {
799        address: Address,
800        storage: &'a mut S,
801    }
802
803    impl<'a, S: PrecompileStorageProvider> StorageOps for TestContract<'a, S> {
804        fn sstore(&mut self, slot: U256, value: U256) -> Result<()> {
805            self.storage.sstore(self.address, slot, value)
806        }
807
808        fn sload(&mut self, slot: U256) -> Result<U256> {
809            self.storage.sload(self.address, slot)
810        }
811    }
812
813    /// Helper to create a test contract with fresh storage
814    fn setup_test_contract<'a>(
815        storage: &'a mut HashMapStorageProvider,
816    ) -> TestContract<'a, HashMapStorageProvider> {
817        TestContract {
818            address: Address::random(),
819            storage,
820        }
821    }
822
823    #[test]
824    fn test_packed_at_multiple_types() -> Result<()> {
825        let mut storage = HashMapStorageProvider::new(1);
826        let mut contract = setup_test_contract(&mut storage);
827        let struct_base = U256::from(0x2000);
828
829        // Pack multiple types in same slot: bool(1) + u64(8) + u128(16)
830        let flag = true;
831        let timestamp: u64 = 1234567890;
832        let amount: u128 = 999888777666;
833
834        Slot::<bool>::new_at_loc(struct_base, FieldLocation::new(0, 0, 1))
835            .write(&mut contract, flag)?;
836        Slot::<u64>::new_at_loc(struct_base, FieldLocation::new(0, 1, 8))
837            .write(&mut contract, timestamp)?;
838        Slot::<u128>::new_at_loc(struct_base, FieldLocation::new(0, 9, 16))
839            .write(&mut contract, amount)?;
840
841        // Verify all packed correctly
842        let read_flag = Slot::<bool>::new_at_loc(struct_base, FieldLocation::new(0, 0, 1))
843            .read(&mut contract)?;
844        let read_time = Slot::<u64>::new_at_loc(struct_base, FieldLocation::new(0, 1, 8))
845            .read(&mut contract)?;
846        let read_amount = Slot::<u128>::new_at_loc(struct_base, FieldLocation::new(0, 9, 16))
847            .read(&mut contract)?;
848
849        assert_eq!(read_flag, flag);
850        assert_eq!(read_time, timestamp);
851        assert_eq!(read_amount, amount);
852
853        // Clear the middle one
854        Slot::<u64>::new_at_loc(struct_base, FieldLocation::new(0, 1, 8)).delete(&mut contract)?;
855
856        // Verify
857        let read_flag = Slot::<bool>::new_at_loc(struct_base, FieldLocation::new(0, 0, 1))
858            .read(&mut contract)?;
859        let read_time = Slot::<u64>::new_at_loc(struct_base, FieldLocation::new(0, 1, 8))
860            .read(&mut contract)?;
861        let read_amount = Slot::<u128>::new_at_loc(struct_base, FieldLocation::new(0, 9, 16))
862            .read(&mut contract)?;
863
864        assert_eq!(read_flag, flag);
865        assert_eq!(read_time, 0);
866        assert_eq!(read_amount, amount);
867
868        Ok(())
869    }
870
871    #[test]
872    fn test_packed_at_different_slots() -> Result<()> {
873        let mut storage = HashMapStorageProvider::new(1);
874        let mut contract = setup_test_contract(&mut storage);
875        let struct_base = U256::from(0x4000);
876
877        // Field in slot 0 (bool is 1 byte, packable)
878        let flag = false;
879        Slot::<bool>::new_at_loc(struct_base, FieldLocation::new(0, 0, 1))
880            .write(&mut contract, flag)?;
881
882        // Field in slot 1 (u128 is 16 bytes, packable)
883        let amount: u128 = 0xdeadbeef;
884        Slot::<u128>::new_at_loc(struct_base, FieldLocation::new(1, 0, 16))
885            .write(&mut contract, amount)?;
886
887        // Field in slot 2 (u64 is 8 bytes, packable)
888        let value: u64 = 123456789;
889        Slot::<u64>::new_at_loc(struct_base, FieldLocation::new(2, 0, 8))
890            .write(&mut contract, value)?;
891
892        // Verify all independent
893        let read_flag = Slot::<bool>::new_at_loc(struct_base, FieldLocation::new(0, 0, 1))
894            .read(&mut contract)?;
895        let read_amount = Slot::<u128>::new_at_loc(struct_base, FieldLocation::new(1, 0, 16))
896            .read(&mut contract)?;
897        let read_val = Slot::<u64>::new_at_loc(struct_base, FieldLocation::new(2, 0, 8))
898            .read(&mut contract)?;
899
900        assert_eq!(read_flag, flag);
901        assert_eq!(read_amount, amount);
902        assert_eq!(read_val, value);
903
904        Ok(())
905    }
906    // -- PROPERTY TESTS -----------------------------------------------------------
907
908    use proptest::prelude::*;
909
910    /// Strategy for generating random Address values
911    fn arb_address() -> impl Strategy<Value = Address> {
912        any::<[u8; 20]>().prop_map(Address::from)
913    }
914
915    /// Strategy for generating random U256 values
916    fn arb_u256() -> impl Strategy<Value = U256> {
917        any::<[u64; 4]>().prop_map(U256::from_limbs)
918    }
919
920    /// Strategy for generating valid offsets for a given byte size
921    fn arb_offset(bytes: usize) -> impl Strategy<Value = usize> {
922        0..=(32 - bytes)
923    }
924
925    proptest! {
926        #![proptest_config(ProptestConfig::with_cases(500))]
927
928        #[test]
929        fn proptest_roundtrip_u8(value: u8, offset in arb_offset(1)) {
930            let slot = insert_packed_value(U256::ZERO, &value, offset, 1)?;
931            let extracted: u8 = extract_packed_value(slot, offset, 1)?;
932            prop_assert_eq!(extracted, value);
933        }
934
935        #[test]
936        fn proptest_roundtrip_u16(value: u16, offset in arb_offset(2)) {
937            let slot = insert_packed_value(U256::ZERO, &value, offset, 2)?;
938            let extracted: u16 = extract_packed_value(slot, offset, 2)?;
939            prop_assert_eq!(extracted, value);
940        }
941
942        #[test]
943        fn proptest_roundtrip_u32(value: u32, offset in arb_offset(4)) {
944            let slot = insert_packed_value(U256::ZERO, &value, offset, 4)?;
945            let extracted: u32 = extract_packed_value(slot, offset, 4)?;
946            prop_assert_eq!(extracted, value);
947        }
948
949        #[test]
950        fn proptest_roundtrip_u64(value: u64, offset in arb_offset(8)) {
951            let slot = insert_packed_value(U256::ZERO, &value, offset, 8)?;
952            let extracted: u64 = extract_packed_value(slot, offset, 8)?;
953            prop_assert_eq!(extracted, value);
954        }
955
956        #[test]
957        fn proptest_roundtrip_u128(value: u128, offset in arb_offset(16)) {
958            let slot = insert_packed_value(U256::ZERO, &value, offset, 16)?;
959            let extracted: u128 = extract_packed_value(slot, offset, 16)?;
960            prop_assert_eq!(extracted, value);
961        }
962
963        #[test]
964        fn proptest_roundtrip_address(addr in arb_address(), offset in arb_offset(20)) {
965            let slot = insert_packed_value(U256::ZERO, &addr, offset, 20)?;
966            let extracted: Address = extract_packed_value(slot, offset, 20)?;
967            prop_assert_eq!(extracted, addr);
968        }
969
970        #[test]
971        fn proptest_roundtrip_u256(value in arb_u256()) {
972            // U256 takes the full 32 bytes, so offset must be 0
973            let slot = insert_packed_value(U256::ZERO, &value, 0, 32)?;
974            let extracted: U256 = extract_packed_value(slot, 0, 32)?;
975            prop_assert_eq!(extracted, value);
976        }
977
978        #[test]
979        fn proptest_roundtrip_bool(value: bool, offset in arb_offset(1)) {
980            let slot = insert_packed_value(U256::ZERO, &value, offset, 1)?;
981            let extracted: bool = extract_packed_value(slot, offset, 1)?;
982            prop_assert_eq!(extracted, value);
983        }
984
985        #[test]
986        fn proptest_roundtrip_i8(value: i8, offset in arb_offset(1)) {
987            let slot = insert_packed_value(U256::ZERO, &value, offset, 1)?;
988            let extracted: i8 = extract_packed_value(slot, offset, 1)?;
989            prop_assert_eq!(extracted, value);
990        }
991
992        #[test]
993        fn proptest_roundtrip_i16(value: i16, offset in arb_offset(2)) {
994            let slot = insert_packed_value(U256::ZERO, &value, offset, 2)?;
995            let extracted: i16 = extract_packed_value(slot, offset, 2)?;
996            prop_assert_eq!(extracted, value);
997        }
998
999        #[test]
1000        fn proptest_roundtrip_i32(value: i32, offset in arb_offset(4)) {
1001            let slot = insert_packed_value(U256::ZERO, &value, offset, 4)?;
1002            let extracted: i32 = extract_packed_value(slot, offset, 4)?;
1003            prop_assert_eq!(extracted, value);
1004        }
1005
1006        #[test]
1007        fn proptest_roundtrip_i64(value: i64, offset in arb_offset(8)) {
1008            let slot = insert_packed_value(U256::ZERO, &value, offset, 8)?;
1009            let extracted: i64 = extract_packed_value(slot, offset, 8)?;
1010            prop_assert_eq!(extracted, value);
1011        }
1012
1013        #[test]
1014        fn proptest_roundtrip_i128(value: i128, offset in arb_offset(16)) {
1015            let slot = insert_packed_value(U256::ZERO, &value, offset, 16)?;
1016            let extracted: i128 = extract_packed_value(slot, offset, 16)?;
1017            prop_assert_eq!(extracted, value);
1018        }
1019    }
1020
1021    proptest! {
1022        #![proptest_config(ProptestConfig::with_cases(500))]
1023
1024        #[test]
1025        fn proptest_multiple_values_no_interference(
1026            v1: u8,
1027            v2: u16,
1028            v3: u32,
1029        ) {
1030            // Pack three values at non-overlapping offsets
1031            // u8 at offset 0 (1 byte)
1032            // u16 at offset 1 (2 bytes)
1033            // u32 at offset 3 (4 bytes)
1034            let mut slot = U256::ZERO;
1035            slot = insert_packed_value(slot, &v1, 0, 1)?;
1036            slot = insert_packed_value(slot, &v2, 1, 2)?;
1037            slot = insert_packed_value(slot, &v3, 3, 4)?;
1038
1039            // Verify all values can be extracted correctly
1040            let e1: u8 = extract_packed_value(slot, 0, 1)?;
1041            let e2: u16 = extract_packed_value(slot, 1, 2)?;
1042            let e3: u32 = extract_packed_value(slot, 3, 4)?;
1043
1044            prop_assert_eq!(e1, v1);
1045            prop_assert_eq!(e2, v2);
1046            prop_assert_eq!(e3, v3);
1047        }
1048
1049        #[test]
1050        fn proptest_overwrite_preserves_others(
1051            v1: u8,
1052            v2: u16,
1053            v1_new: u8,
1054        ) {
1055            // Pack two values
1056            let mut slot = U256::ZERO;
1057            slot = insert_packed_value(slot, &v1, 0, 1)?;
1058            slot = insert_packed_value(slot, &v2, 1, 2)?;
1059
1060            // Overwrite the first value
1061            slot = insert_packed_value(slot, &v1_new, 0, 1)?;
1062
1063            // Verify the second value is unchanged
1064            let e1: u8 = extract_packed_value(slot, 0, 1)?;
1065            let e2: u16 = extract_packed_value(slot, 1, 2)?;
1066
1067            prop_assert_eq!(e1, v1_new);
1068            prop_assert_eq!(e2, v2); // Should be unchanged
1069        }
1070
1071        #[test]
1072        fn proptest_bool_with_mixed_types(
1073            flag1: bool,
1074            u16_val: u16,
1075            flag2: bool,
1076            u32_val: u32,
1077        ) {
1078            // Pack bools alongside other types: bool(1) | u16(2) | bool(1) | u32(4)
1079            let mut slot = U256::ZERO;
1080            slot = insert_packed_value(slot, &flag1, 0, 1)?;
1081            slot = insert_packed_value(slot, &u16_val, 1, 2)?;
1082            slot = insert_packed_value(slot, &flag2, 3, 1)?;
1083            slot = insert_packed_value(slot, &u32_val, 4, 4)?;
1084
1085            // Extract and verify all values
1086            let e_flag1: bool = extract_packed_value(slot, 0, 1)?;
1087            let e_u16: u16 = extract_packed_value(slot, 1, 2)?;
1088            let e_flag2: bool = extract_packed_value(slot, 3, 1)?;
1089            let e_u32: u32 = extract_packed_value(slot, 4, 4)?;
1090
1091            prop_assert_eq!(e_flag1, flag1);
1092            prop_assert_eq!(e_u16, u16_val);
1093            prop_assert_eq!(e_flag2, flag2);
1094            prop_assert_eq!(e_u32, u32_val);
1095        }
1096
1097        #[test]
1098        fn proptest_multiple_bools_no_interference(
1099            flags in proptest::collection::vec(any::<bool>(), 1..=20)
1100        ) {
1101            // Pack multiple bools at consecutive offsets
1102            let mut slot = U256::ZERO;
1103            for (i, &flag) in flags.iter().enumerate() {
1104                slot = insert_packed_value(slot, &flag, i, 1)?;
1105            }
1106
1107            // Verify all flags can be extracted correctly
1108            for (i, &expected_flag) in flags.iter().enumerate() {
1109                let extracted: bool = extract_packed_value(slot, i, 1)?;
1110                prop_assert_eq!(extracted, expected_flag, "Flag at offset {} mismatch", i);
1111            }
1112        }
1113
1114        #[test]
1115        fn proptest_element_slot_offset_consistency_u8(
1116            idx in 0usize..1000,
1117        ) {
1118            // For u8 arrays (1 byte per element)
1119            let slot = calc_element_slot(idx, 1);
1120            let offset = calc_element_offset(idx, 1);
1121
1122            // Verify consistency: slot * 32 + offset should equal total bytes
1123            prop_assert_eq!(slot * 32 + offset, idx);
1124
1125            // Verify offset is in valid range
1126            prop_assert!(offset < 32);
1127        }
1128
1129        #[test]
1130        fn proptest_element_slot_offset_consistency_u16(
1131            idx in 0usize..1000,
1132        ) {
1133            // For u16 arrays (2 bytes per element)
1134            let slot = calc_element_slot(idx, 2);
1135            let offset = calc_element_offset(idx, 2);
1136
1137            prop_assert_eq!(slot * 32 + offset, idx * 2);
1138            prop_assert!(offset < 32);
1139        }
1140
1141        #[test]
1142        fn proptest_element_slot_offset_consistency_address(
1143            idx in 0usize..100,
1144        ) {
1145            // For address arrays (20 bytes per element)
1146            let slot = calc_element_slot(idx, 20);
1147            let offset = calc_element_offset(idx, 20);
1148
1149            prop_assert_eq!(slot * 32 + offset, idx * 20);
1150            prop_assert!(offset < 32);
1151        }
1152
1153        #[test]
1154        fn proptest_packed_slot_count_sufficient(
1155            n in 1usize..100,
1156            elem_bytes in 1usize..=32,
1157        ) {
1158            let slot_count = calc_packed_slot_count(n, elem_bytes);
1159            let total_bytes = n * elem_bytes;
1160            let min_slots = total_bytes.div_ceil(32);
1161
1162            // Verify the calculated slot count is correct
1163            prop_assert_eq!(slot_count, min_slots);
1164
1165            // Verify it's sufficient to hold all bytes
1166            prop_assert!(slot_count * 32 >= total_bytes);
1167
1168            // Verify it's not over-allocated (no more than 31 wasted bytes)
1169            if slot_count > 0 {
1170                prop_assert!(slot_count * 32 - total_bytes < 32);
1171            }
1172        }
1173    }
1174}