Skip to main content

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