1use crate::{
15 error::{Result, TempoPrecompileError},
16 storage::{StorageCtx, StorageOps, types::*},
17};
18use alloy::primitives::{Address, Bytes, U256, keccak256};
19use std::marker::PhantomData;
20
21impl StorableType for Bytes {
22 const LAYOUT: Layout = Layout::Slots(1);
23 const IS_DYNAMIC: bool = true;
24 type Handler = BytesLikeHandler<Self>;
25
26 fn handle(slot: U256, _ctx: LayoutCtx, address: Address) -> Self::Handler {
27 BytesLikeHandler::new(slot, address)
28 }
29}
30
31impl StorableType for String {
32 const LAYOUT: Layout = Layout::Slots(1);
33 const IS_DYNAMIC: bool = true;
34 type Handler = BytesLikeHandler<Self>;
35
36 fn handle(slot: U256, _ctx: LayoutCtx, address: Address) -> Self::Handler {
37 BytesLikeHandler::new(slot, address)
38 }
39}
40
41#[derive(Debug, Clone)]
45pub struct BytesLikeHandler<T> {
46 base_slot: U256,
47 address: Address,
48 _ty: PhantomData<T>,
49}
50
51impl<T: Storable> BytesLikeHandler<T> {
52 #[inline]
54 pub fn new(base_slot: U256, address: Address) -> Self {
55 Self {
56 base_slot,
57 address,
58 _ty: PhantomData,
59 }
60 }
61
62 #[inline]
63 fn as_slot(&self) -> Slot<T> {
64 Slot::new(self.base_slot, self.address)
65 }
66
67 #[inline]
69 pub fn len(&self) -> Result<usize> {
70 let base_value = Slot::<U256>::new(self.base_slot, self.address).read()?;
71 let is_long = is_long_string(base_value);
72 calc_string_length(base_value, is_long)
73 }
74
75 #[inline]
77 pub fn is_empty(&self) -> Result<bool> {
78 Ok(self.len()? == 0)
79 }
80}
81
82impl<T: Storable> Handler<T> for BytesLikeHandler<T> {
83 #[inline]
84 fn read(&self) -> Result<T> {
85 self.as_slot().read()
86 }
87
88 #[inline]
89 fn write(&mut self, value: T) -> Result<()> {
90 self.as_slot().write(value)
91 }
92
93 #[inline]
94 fn delete(&mut self) -> Result<()> {
95 self.as_slot().delete()
96 }
97
98 #[inline]
99 fn t_read(&self) -> Result<T> {
100 self.as_slot().t_read()
101 }
102
103 #[inline]
104 fn t_write(&mut self, value: T) -> Result<()> {
105 self.as_slot().t_write(value)
106 }
107
108 #[inline]
109 fn t_delete(&mut self) -> Result<()> {
110 self.as_slot().t_delete()
111 }
112}
113
114impl Storable for Bytes {
117 #[inline]
118 fn load<S: StorageOps>(storage: &S, slot: U256, ctx: LayoutCtx) -> Result<Self> {
119 debug_assert!(ctx.is_full(), "Bytes cannot be packed");
120 load_bytes_like(storage, slot, |data| Ok(Self::from(data)))
121 }
122
123 #[inline]
124 fn store<S: StorageOps>(&self, storage: &mut S, slot: U256, ctx: LayoutCtx) -> Result<()> {
125 debug_assert!(ctx.is_full(), "Bytes cannot be packed");
126 store_bytes_like(self.as_ref(), storage, slot, ctx)
127 }
128
129 #[inline]
131 fn delete<S: StorageOps>(storage: &mut S, slot: U256, ctx: LayoutCtx) -> Result<()> {
132 debug_assert!(ctx.is_full(), "Bytes cannot be packed");
133 delete_bytes_like(storage, slot)
134 }
135}
136
137impl Storable for String {
138 #[inline]
139 fn load<S: StorageOps>(storage: &S, slot: U256, ctx: LayoutCtx) -> Result<Self> {
140 debug_assert!(ctx.is_full(), "String cannot be packed");
141 load_bytes_like(storage, slot, |data| {
142 Self::from_utf8(data).map_err(|e| {
143 TempoPrecompileError::Fatal(format!("Invalid UTF-8 in stored string: {e}"))
144 })
145 })
146 }
147
148 #[inline]
149 fn store<S: StorageOps>(&self, storage: &mut S, slot: U256, ctx: LayoutCtx) -> Result<()> {
150 debug_assert!(ctx.is_full(), "String cannot be packed");
151 store_bytes_like(self.as_bytes(), storage, slot, ctx)
152 }
153
154 #[inline]
156 fn delete<S: StorageOps>(storage: &mut S, slot: U256, ctx: LayoutCtx) -> Result<()> {
157 debug_assert!(ctx.is_full(), "String cannot be packed");
158 delete_bytes_like(storage, slot)
159 }
160}
161
162#[inline]
166fn load_bytes_like<T, S, F>(storage: &S, base_slot: U256, into: F) -> Result<T>
167where
168 S: StorageOps,
169 F: FnOnce(Vec<u8>) -> Result<T>,
170{
171 let base_value = storage.load(base_slot)?;
172 let is_long = is_long_string(base_value);
173 let length = calc_string_length(base_value, is_long)?;
174
175 if is_long {
176 let slot_start = calc_data_slot(base_slot);
178 let chunks = calc_chunks(length);
179 let mut data = Vec::new();
180
181 for i in 0..chunks {
182 let slot = slot_start + U256::from(i);
183 let chunk_value = storage.load(slot)?;
184 let chunk_bytes = chunk_value.to_be_bytes::<32>();
185
186 let bytes_to_take = if i == chunks - 1 {
188 length - (i * 32)
189 } else {
190 32
191 };
192 data.extend_from_slice(&chunk_bytes[..bytes_to_take]);
193 }
194
195 into(data)
196 } else {
197 let bytes = base_value.to_be_bytes::<32>();
199 into(bytes[..length].to_vec())
200 }
201}
202
203#[inline]
206fn store_bytes_like<S: StorageOps>(
207 bytes: &[u8],
208 storage: &mut S,
209 base_slot: U256,
210 ctx: LayoutCtx,
211) -> Result<()> {
212 let new_len = bytes.len();
213 let new_is_long = new_len > 31;
214 let mut data_slot: Option<U256> = None;
215
216 if !ctx.skip_tail_cleanup() && StorageCtx.spec().is_t5() {
218 let prev = storage.load(base_slot)?;
219 if is_long_string(prev) {
221 let prev_chunks = calc_chunks(calc_string_length(prev, true)?);
222 let new_chunks = if new_is_long { calc_chunks(new_len) } else { 0 };
223 if prev_chunks > new_chunks {
224 let slot_start = calc_data_slot(base_slot);
225 for i in new_chunks..prev_chunks {
226 storage.store(slot_start + U256::from(i), U256::ZERO)?;
227 }
228 data_slot = Some(slot_start);
229 }
230 }
231 }
232
233 if !new_is_long {
234 storage.store(base_slot, encode_short_string(bytes))
235 } else {
236 storage.store(base_slot, encode_long_string_length(new_len))?;
237
238 let slot_start = data_slot.unwrap_or_else(|| calc_data_slot(base_slot));
240 let chunks = calc_chunks(new_len);
241 for i in 0..chunks {
242 let slot = slot_start + U256::from(i);
243 let chunk_start = i * 32;
244 let chunk_end = (chunk_start + 32).min(new_len);
245 let chunk = &bytes[chunk_start..chunk_end];
246
247 let mut chunk_bytes = [0u8; 32];
249 chunk_bytes[..chunk.len()].copy_from_slice(chunk);
250
251 storage.store(slot, U256::from_be_bytes(chunk_bytes))?;
252 }
253
254 Ok(())
255 }
256}
257
258#[inline]
262fn delete_bytes_like<S: StorageOps>(storage: &mut S, base_slot: U256) -> Result<()> {
263 let base_value = storage.load(base_slot)?;
264 let is_long = is_long_string(base_value);
265
266 if is_long {
267 let length = calc_string_length(base_value, true)?;
269 let slot_start = calc_data_slot(base_slot);
270 let chunks = calc_chunks(length);
271
272 for i in 0..chunks {
274 let slot = slot_start + U256::from(i);
275 storage.store(slot, U256::ZERO)?;
276 }
277 }
278
279 storage.store(base_slot, U256::ZERO)
281}
282
283#[inline]
287fn calc_data_slot(base_slot: U256) -> U256 {
288 U256::from_be_bytes(keccak256(base_slot.to_be_bytes::<32>()).0)
289}
290
291#[inline]
297fn is_long_string(slot_value: U256) -> bool {
298 (slot_value.byte(0) & 1) != 0
299}
300
301#[inline]
305fn calc_string_length(slot_value: U256, is_long: bool) -> Result<usize> {
306 if is_long {
307 let length_times_two_plus_one: U256 = slot_value;
310 let length_times_two: U256 = length_times_two_plus_one - U256::ONE;
311 let length_u256: U256 = length_times_two >> 1;
312 if length_u256 > U256::from(u32::MAX) {
313 return Err(TempoPrecompileError::under_overflow());
314 }
315 Ok(length_u256.to::<usize>())
316 } else {
317 let bytes = slot_value.to_be_bytes::<32>();
320 let length = (bytes[31] / 2) as usize;
321 if length > 31 {
322 return Err(TempoPrecompileError::Fatal(format!(
324 "short string length {length} exceeds maximum of 31 bytes"
325 )));
326 }
327 Ok(length)
328 }
329}
330
331#[inline]
333fn calc_chunks(byte_length: usize) -> usize {
334 byte_length.div_ceil(32)
335}
336
337#[inline]
341fn encode_short_string(bytes: &[u8]) -> U256 {
342 let mut storage_bytes = [0u8; 32];
343 storage_bytes[..bytes.len()].copy_from_slice(bytes);
344 storage_bytes[31] = (bytes.len() * 2) as u8;
345 U256::from_be_bytes(storage_bytes)
346}
347
348#[inline]
352fn encode_long_string_length(byte_length: usize) -> U256 {
353 U256::from(byte_length * 2 + 1)
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::{
360 storage::{Handler, StorageCtx},
361 test_util::setup_storage,
362 };
363 use proptest::prelude::*;
364
365 fn arb_safe_slot() -> impl Strategy<Value = U256> {
367 any::<[u64; 4]>().prop_map(|limbs| {
368 U256::from_limbs(limbs) % (U256::MAX - U256::from(10000))
370 })
371 }
372
373 fn arb_short_string() -> impl Strategy<Value = String> {
375 prop_oneof![
376 Just(String::new()),
378 "[a-zA-Z0-9]{1,31}",
380 "[\u{0041}-\u{005A}\u{4E00}-\u{4E19}]{1,10}",
382 ]
383 }
384
385 fn arb_32byte_string() -> impl Strategy<Value = String> {
387 "[a-zA-Z0-9]{32}"
388 }
389
390 fn arb_long_string() -> impl Strategy<Value = String> {
392 prop_oneof![
393 "[a-zA-Z0-9]{33,100}",
395 "[\u{0041}-\u{005A}\u{4E00}-\u{4E19}]{11,30}",
397 ]
398 }
399
400 fn arb_short_bytes() -> impl Strategy<Value = Bytes> {
402 prop::collection::vec(any::<u8>(), 0..=31).prop_map(Bytes::from)
403 }
404
405 fn arb_32byte_bytes() -> impl Strategy<Value = Bytes> {
407 prop::collection::vec(any::<u8>(), 32..=32).prop_map(Bytes::from)
408 }
409
410 fn arb_long_bytes() -> impl Strategy<Value = Bytes> {
412 prop::collection::vec(any::<u8>(), 33..=100).prop_map(Bytes::from)
413 }
414
415 #[test]
418 fn test_calc_data_slot_matches_manual_keccak() {
419 let base_slot = U256::random();
420 let data_slot = calc_data_slot(base_slot);
421
422 let expected = U256::from_be_bytes(keccak256(base_slot.to_be_bytes::<32>()).0);
424
425 assert_eq!(
426 data_slot, expected,
427 "calc_data_slot should match manual keccak256 computation"
428 );
429 }
430
431 #[test]
432 fn test_is_long_string_boundaries() {
433 let short_31_bytes = encode_short_string(&[b'a'; 31]);
435 assert!(
436 !is_long_string(short_31_bytes),
437 "31-byte string should be short"
438 );
439
440 let long_32_bytes = encode_long_string_length(32);
442 assert!(
443 is_long_string(long_32_bytes),
444 "32-byte string should be long"
445 );
446
447 let empty = encode_short_string(&[]);
449 assert!(!is_long_string(empty), "Empty string should be short");
450
451 let one_byte = encode_short_string(b"x");
453 assert!(!is_long_string(one_byte), "1-byte string should be short");
454 }
455
456 #[test]
457 fn test_calc_string_length_short() {
458 for len in 0..=31 {
460 let bytes = vec![b'a'; len];
461 let encoded = encode_short_string(&bytes);
462 let decoded_len = calc_string_length(encoded, false);
463 assert_eq!(
464 decoded_len,
465 Ok(len),
466 "Short string length mismatch for {len} bytes"
467 );
468 }
469 }
470
471 #[test]
472 fn test_calc_string_length_long() {
473 for len in [32, 33, 63, 64, 65, 100, 1000, 10000] {
475 let encoded = encode_long_string_length(len);
476 let decoded_len = calc_string_length(encoded, true);
477 assert_eq!(
478 decoded_len,
479 Ok(len),
480 "Long string length mismatch for {len} bytes"
481 );
482 }
483 }
484
485 #[test]
486 fn test_calc_chunks_boundaries() {
487 assert_eq!(calc_chunks(0), 0, "0 bytes should require 0 chunks");
488 assert_eq!(calc_chunks(1), 1, "1 byte should require 1 chunk");
489 assert_eq!(calc_chunks(31), 1, "31 bytes should require 1 chunk");
490 assert_eq!(calc_chunks(32), 1, "32 bytes should require 1 chunk");
491 assert_eq!(calc_chunks(33), 2, "33 bytes should require 2 chunks");
492 assert_eq!(calc_chunks(64), 2, "64 bytes should require 2 chunks");
493 assert_eq!(calc_chunks(65), 3, "65 bytes should require 3 chunks");
494 assert_eq!(calc_chunks(100), 4, "100 bytes should require 4 chunks");
495 }
496
497 #[test]
498 fn test_encode_short_string_format() {
499 let test_str = b"Hello";
500 let encoded = encode_short_string(test_str);
501 let bytes = encoded.to_be_bytes::<32>();
502
503 assert_eq!(&bytes[..5], test_str, "Data should be left-aligned");
505
506 assert_eq!(&bytes[5..31], &[0u8; 26], "Padding should be zero");
508
509 assert_eq!(
511 bytes[31],
512 (test_str.len() * 2) as u8,
513 "LSB should be length * 2"
514 );
515
516 assert_eq!(bytes[31] & 1, 0, "Bit 0 should be 0 for short strings");
518 }
519
520 #[test]
521 fn test_encode_short_string_empty() {
522 let encoded = encode_short_string(&[]);
523 let bytes = encoded.to_be_bytes::<32>();
524
525 assert_eq!(bytes, [0u8; 32], "Empty string should encode to all zeros");
527 }
528
529 #[test]
530 fn test_encode_long_string_length_formula() {
531 for len in [32, 33, 100, 1000, 10000] {
532 let encoded = encode_long_string_length(len);
533 let expected = U256::from(len * 2 + 1);
534 assert_eq!(
535 encoded, expected,
536 "Long string length encoding mismatch for {len} bytes"
537 );
538
539 assert_eq!(encoded.byte(0) & 1, 1, "Bit 0 should be 1 for long strings");
541 }
542 }
543
544 #[test]
545 fn test_encode_decode_roundtrip() {
546 for len in [0, 1, 15, 30, 31] {
548 let bytes = vec![b'x'; len];
549 let encoded = encode_short_string(&bytes);
550 let decoded_len = calc_string_length(encoded, false);
551 assert_eq!(
552 decoded_len,
553 Ok(len),
554 "Short string roundtrip failed for {len} bytes"
555 );
556 }
557
558 for len in [32, 33, 64, 100] {
560 let encoded = encode_long_string_length(len);
561 let decoded_len = calc_string_length(encoded, true);
562 assert_eq!(
563 decoded_len,
564 Ok(len),
565 "Long string roundtrip failed for {len} bytes"
566 );
567 }
568 }
569
570 #[test]
573 fn test_calc_string_length_tampered() {
574 let malicious_slot = U256::from(0x0008000000000001u64);
578 assert!(is_long_string(malicious_slot));
579 assert_eq!(
580 calc_string_length(malicious_slot, true),
581 Err(TempoPrecompileError::under_overflow())
582 );
583
584 let at_max = U256::from(u64::from(u32::MAX) * 2 + 1);
586 assert_eq!(calc_string_length(at_max, true), Ok(u32::MAX as usize));
587
588 let above_max = U256::from((u64::from(u32::MAX) + 1) * 2 + 1);
590 assert_eq!(
591 calc_string_length(above_max, true),
592 Err(TempoPrecompileError::under_overflow())
593 );
594
595 let max_short = U256::from(31u64 * 2);
599 assert!(!is_long_string(max_short));
600 assert_eq!(calc_string_length(max_short, false), Ok(31));
601
602 let malicious_short = U256::from(0xFEu64);
604 assert!(!is_long_string(malicious_short));
605 assert!(calc_string_length(malicious_short, false).is_err());
606
607 let above_short = U256::from(32u64 * 2);
609 assert!(calc_string_length(above_short, false).is_err());
610 }
611
612 proptest! {
615 #![proptest_config(ProptestConfig::with_cases(500))]
616
617 #[test]
618 fn test_short_strings(s in arb_short_string(), base_slot in arb_safe_slot()) {
619 let (mut storage, address) = setup_storage();
620 StorageCtx::enter(&mut storage, || {
621 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
622
623 slot.write(s.clone()).unwrap();
625 let loaded = slot.read().unwrap();
626 prop_assert_eq!(&s, &loaded, "Short string roundtrip failed");
627
628 slot.delete().unwrap();
630 let after_delete = slot.read().unwrap();
631 prop_assert_eq!(after_delete, String::new(), "Short string not empty after delete");
632
633 Ok(())
634 }).unwrap();
635 }
636
637 #[test]
638 #[allow(clippy::redundant_clone)]
639 fn test_32byte_strings(s in arb_32byte_string(), base_slot in arb_safe_slot()) {
640 let (mut storage, address) = setup_storage();
641 StorageCtx::enter(&mut storage, || {
642 prop_assert_eq!(s.len(), 32, "Generated string should be exactly 32 bytes");
644
645 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
646
647 slot.write(s.clone()).unwrap();
649 let loaded = slot.read().unwrap();
650 prop_assert_eq!(s.clone(), loaded, "32-byte string roundtrip failed");
651
652 slot.delete().unwrap();
654 let after_delete = slot.read().unwrap();
655 prop_assert_eq!(after_delete, String::new(), "32-byte string not empty after delete");
656
657 Ok(())
658 }).unwrap();
659 }
660
661 #[test]
662 fn test_long_strings(s in arb_long_string(), base_slot in arb_safe_slot()) {
663 let (mut storage, address) = setup_storage();
664 StorageCtx::enter(&mut storage, || {
665 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
666
667 slot.write(s.clone()).unwrap();
669 let loaded = slot.read().unwrap();
670 prop_assert_eq!(&s, &loaded, "Long string roundtrip failed for length: {}", s.len());
671
672 let chunks = calc_chunks(s.len());
674
675 slot.delete().unwrap();
677 let after_delete = slot.read().unwrap();
678 prop_assert_eq!(after_delete, String::new(), "Long string not empty after delete");
679
680 let data_slot_start = calc_data_slot(base_slot);
682 for i in 0..chunks {
683 let slot = Slot::<U256>::new_at_offset(data_slot_start, i, address);
684 let value = slot.read().unwrap();
685 prop_assert_eq!(value, U256::ZERO, "Data slot not cleared after delete");
686 }
687
688 Ok(())
689 }).unwrap();
690 }
691
692 #[test]
693 fn test_short_bytes(b in arb_short_bytes(), base_slot in arb_safe_slot()) {
694 let (mut storage, address) = setup_storage();
695 StorageCtx::enter(&mut storage, || {
696 let mut slot = BytesLikeHandler::<Bytes>::new(base_slot, address);
697
698 slot.write(b.clone()).unwrap();
700 let loaded = slot.read().unwrap();
701 prop_assert_eq!(&b, &loaded, "Short bytes roundtrip failed for length: {}", b.len());
702
703 slot.delete().unwrap();
705 let after_delete = slot.read().unwrap();
706 prop_assert_eq!(after_delete, Bytes::new(), "Short bytes not empty after delete");
707
708 Ok(())
709 }).unwrap();
710 }
711
712 #[test]
713 fn test_32byte_bytes(b in arb_32byte_bytes(), base_slot in arb_safe_slot()) {
714 let (mut storage, address) = setup_storage();
715 StorageCtx::enter(&mut storage, || {
716 prop_assert_eq!(b.len(), 32, "Generated bytes should be exactly 32 bytes");
718
719 let mut slot = BytesLikeHandler::<Bytes>::new(base_slot, address);
720
721 slot.write(b.clone()).unwrap();
723 let loaded = slot.read().unwrap();
724 prop_assert_eq!(&b, &loaded, "32-byte bytes roundtrip failed");
725
726 slot.delete().unwrap();
728 let after_delete = slot.read().unwrap();
729 prop_assert_eq!(after_delete, Bytes::new(), "32-byte bytes not empty after delete");
730
731 Ok(())
732 }).unwrap();
733 }
734
735 #[test]
736 fn test_long_bytes(b in arb_long_bytes(), base_slot in arb_safe_slot()) {
737 let (mut storage, address) = setup_storage();
738 StorageCtx::enter(&mut storage, || {
739 let mut slot = BytesLikeHandler::<Bytes>::new(base_slot, address);
740
741 slot.write(b.clone()).unwrap();
743 let loaded = slot.read().unwrap();
744 prop_assert_eq!(&b, &loaded, "Long bytes roundtrip failed for length: {}", b.len());
745
746 let chunks = calc_chunks(b.len());
748
749 slot.delete().unwrap();
751 let after_delete = slot.read().unwrap();
752 prop_assert_eq!(after_delete, Bytes::new(), "Long bytes not empty after delete");
753
754 let data_slot_start = calc_data_slot(base_slot);
756 for i in 0..chunks {
757 let slot = Slot::<U256>::new_at_offset(data_slot_start, i, address);
758 let value = slot.read().unwrap();
759 prop_assert_eq!(value, U256::ZERO, "Data slot not cleared after delete");
760 }
761
762 Ok(())
763 }).unwrap();
764 }
765
766 #[test]
767 fn test_string_len(s in prop_oneof![arb_short_string(), arb_long_string()], base_slot in arb_safe_slot()) {
768 let (mut storage, address) = setup_storage();
769 StorageCtx::enter(&mut storage, || {
770 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
771
772 prop_assert_eq!(slot.len().unwrap(), 0, "Empty string should have len 0");
774 prop_assert!(slot.is_empty().unwrap(), "Empty string should be empty");
775
776 slot.write(s.clone()).unwrap();
778 prop_assert_eq!(slot.len().unwrap(), s.len(), "len() should match string byte length");
779 prop_assert_eq!(slot.is_empty().unwrap(), s.is_empty(), "is_empty() should match");
780
781 slot.delete().unwrap();
783 prop_assert_eq!(slot.len().unwrap(), 0, "Deleted string should have len 0");
784
785 Ok(())
786 }).unwrap();
787 }
788 }
789}