1use alloy::primitives::U256;
16
17use crate::{
18 error::Result,
19 storage::{FromWord, Layout, StorableType, StorageOps},
20};
21
22pub 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#[derive(Debug, Clone, Copy)]
44pub struct FieldLocation {
45 pub offset_slots: usize,
47 pub offset_bytes: usize,
49 pub size: usize,
51}
52
53impl FieldLocation {
54 #[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#[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#[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 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 let shift_bits = offset * 8;
102 let mask = create_element_mask(bytes);
103
104 T::from_word((slot_value >> shift_bits) & mask)
106}
107
108#[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 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 let field_value = value.to_word();
133
134 let shift_bits = offset * 8;
136 let mask = create_element_mask(bytes);
137
138 let clear_mask = !(mask << shift_bits);
140 let cleared = current & clear_mask;
141
142 let positioned = (field_value & mask) << shift_bits;
144 Ok(cleared | positioned)
145}
146
147#[inline]
152pub fn delete_from_word(current: U256, offset: usize, bytes: usize) -> Result<U256> {
153 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#[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#[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#[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#[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#[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 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 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 #[test]
271 fn test_calc_element_slot() {
272 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 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 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 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 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 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 assert_eq!(calc_packed_slot_count(10, 1), 1); assert_eq!(calc_packed_slot_count(32, 1), 1); assert_eq!(calc_packed_slot_count(33, 1), 2); assert_eq!(calc_packed_slot_count(100, 1), 4); assert_eq!(calc_packed_slot_count(16, 2), 1); assert_eq!(calc_packed_slot_count(17, 2), 2); 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 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 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 assert_eq!(create_element_mask(1), U256::from(0xff));
368
369 assert_eq!(create_element_mask(2), U256::from(0xffff));
371
372 assert_eq!(create_element_mask(4), U256::from(0xffffffffu32));
374
375 assert_eq!(create_element_mask(8), U256::from(u64::MAX));
377
378 assert_eq!(create_element_mask(16), U256::from(u128::MAX));
380
381 assert_eq!(create_element_mask(32), U256::MAX);
383
384 assert_eq!(create_element_mask(64), U256::MAX);
386 }
387
388 #[test]
389 fn test_delete_from_word() {
390 let slot = gen_word_from(&[
392 "0xff", "0x56", "0x34", "0x12", ]);
397
398 let cleared = delete_from_word(slot, 1, 1).unwrap();
400 let expected = gen_word_from(&[
401 "0xff", "0x56", "0x00", "0x12", ]);
406 assert_eq!(cleared, expected, "Should zero offset 1");
407
408 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 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 #[test]
423 fn test_boundary_validation_rejects_spanning() {
424 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 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 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 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 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 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 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 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 #[test]
482 fn test_bool() {
483 let expected = gen_word_from(&[
485 "0x01", ]);
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 let expected = gen_word_from(&[
497 "0x01", "0x01", ]);
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 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", "0x56", "0x34", "0x12", ]);
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 let v1: u16 = 0x1234;
541 let v2: u16 = 0x5678;
542 let v3: u16 = u16::MAX;
543
544 let expected = gen_word_from(&[
545 "0xffff", "0x5678", "0x1234", ]);
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 let v1: u32 = 0x12345678;
565 let v2: u32 = u32::MAX;
566
567 let expected = gen_word_from(&[
568 "0xffffffff", "0x12345678", ]);
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 let v1: u64 = 0x123456789abcdef0;
585 let v2: u64 = u64::MAX;
586
587 let expected = gen_word_from(&[
588 "0xffffffffffffffff", "0x123456789abcdef0", ]);
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 let v1: u128 = 0x123456789abcdef0fedcba9876543210;
605 let v2: u128 = u128::MAX;
606
607 let expected = gen_word_from(&[
608 "0xffffffffffffffffffffffffffffffff", "0x123456789abcdef0fedcba9876543210", ]);
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 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 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 let v1: i8 = -128; let v2: i8 = 0;
647 let v3: i8 = 127; let v4: i8 = -1;
649
650 let expected = gen_word_from(&[
651 "0xff", "0x7f", "0x00", "0x80", ]);
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 let v1: i16 = -32768; let v2: i16 = 32767; let v3: i16 = -1;
676
677 let expected = gen_word_from(&[
678 "0xffff", "0x7fff", "0x8000", ]);
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 let v1: i32 = -2147483648; let v2: i32 = 2147483647; let expected = gen_word_from(&[
701 "0x7fffffff", "0x80000000", ]);
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 let v1: i64 = -9223372036854775808; let v2: i64 = 9223372036854775807; let expected = gen_word_from(&[
721 "0x7fffffffffffffff", "0x8000000000000000", ]);
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 let v1: i128 = -170141183460469231731687303715884105728; let v2: i128 = 170141183460469231731687303715884105727; let expected = gen_word_from(&[
741 "0x7fffffffffffffffffffffffffffffff", "0x80000000000000000000000000000000", ]);
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 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", "0xddeeff00", "0xbbcc", "0xaa", ]);
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", "0x1111111111111111111111111111111111111111", "0x01", ]);
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 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 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 #[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 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 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 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 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 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 use proptest::prelude::*;
912
913 fn arb_address() -> impl Strategy<Value = Address> {
915 any::<[u8; 20]>().prop_map(Address::from)
916 }
917
918 fn arb_u256() -> impl Strategy<Value = U256> {
920 any::<[u64; 4]>().prop_map(U256::from_limbs)
921 }
922
923 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 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 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 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 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 slot = insert_into_word(slot, &v1_new, 0, 1)?;
1065
1066 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); }
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 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 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 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 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 let slot = calc_element_slot(idx, 1);
1123 let offset = calc_element_offset(idx, 1);
1124
1125 prop_assert_eq!(slot * 32 + offset, idx);
1127
1128 prop_assert!(offset < 32);
1130 }
1131
1132 #[test]
1133 fn proptest_element_slot_offset_consistency_u16(
1134 idx in 0usize..1000,
1135 ) {
1136 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 let slot = calc_element_slot(idx, 20);
1150 let offset = calc_element_offset(idx, 20);
1151 let elems_per_slot = 32 / 20; prop_assert_eq!(slot, idx / elems_per_slot);
1155 prop_assert_eq!(offset, (idx % elems_per_slot) * 20);
1156 prop_assert!(offset + 20 <= 32); }
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 prop_assert_eq!(slot_count, expected);
1170
1171 prop_assert!(slot_count * elems_per_slot >= n);
1173
1174 if slot_count > 0 {
1176 prop_assert!(slot_count * elems_per_slot - n < elems_per_slot);
1177 }
1178 }
1179 }
1180}