1use crate::{
15 error::{Result, TempoPrecompileError},
16 storage::{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 Ok(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_eq!(ctx, LayoutCtx::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_eq!(ctx, LayoutCtx::FULL, "Bytes cannot be packed");
126 store_bytes_like(self.as_ref(), storage, slot)
127 }
128
129 #[inline]
131 fn delete<S: StorageOps>(storage: &mut S, slot: U256, ctx: LayoutCtx) -> Result<()> {
132 debug_assert_eq!(ctx, LayoutCtx::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_eq!(ctx, LayoutCtx::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_eq!(ctx, LayoutCtx::FULL, "String cannot be packed");
151 store_bytes_like(self.as_bytes(), storage, slot)
152 }
153
154 #[inline]
156 fn delete<S: StorageOps>(storage: &mut S, slot: U256, ctx: LayoutCtx) -> Result<()> {
157 debug_assert_eq!(ctx, LayoutCtx::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::with_capacity(length);
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]
205fn store_bytes_like<S: StorageOps>(bytes: &[u8], storage: &mut S, base_slot: U256) -> Result<()> {
206 let length = bytes.len();
207
208 if length <= 31 {
209 storage.store(base_slot, encode_short_string(bytes))
210 } else {
211 storage.store(base_slot, encode_long_string_length(length))?;
212
213 let slot_start = calc_data_slot(base_slot);
215 let chunks = calc_chunks(length);
216
217 for i in 0..chunks {
218 let slot = slot_start + U256::from(i);
219 let chunk_start = i * 32;
220 let chunk_end = (chunk_start + 32).min(length);
221 let chunk = &bytes[chunk_start..chunk_end];
222
223 let mut chunk_bytes = [0u8; 32];
225 chunk_bytes[..chunk.len()].copy_from_slice(chunk);
226
227 storage.store(slot, U256::from_be_bytes(chunk_bytes))?;
228 }
229
230 Ok(())
231 }
232}
233
234#[inline]
238fn delete_bytes_like<S: StorageOps>(storage: &mut S, base_slot: U256) -> Result<()> {
239 let base_value = storage.load(base_slot)?;
240 let is_long = is_long_string(base_value);
241
242 if is_long {
243 let length = calc_string_length(base_value, true);
245 let slot_start = calc_data_slot(base_slot);
246 let chunks = calc_chunks(length);
247
248 for i in 0..chunks {
250 let slot = slot_start + U256::from(i);
251 storage.store(slot, U256::ZERO)?;
252 }
253 }
254
255 storage.store(base_slot, U256::ZERO)
257}
258
259#[inline]
263fn calc_data_slot(base_slot: U256) -> U256 {
264 U256::from_be_bytes(keccak256(base_slot.to_be_bytes::<32>()).0)
265}
266
267#[inline]
273fn is_long_string(slot_value: U256) -> bool {
274 (slot_value.byte(0) & 1) != 0
275}
276
277#[inline]
279fn calc_string_length(slot_value: U256, is_long: bool) -> usize {
280 if is_long {
281 let length_times_two_plus_one: U256 = slot_value;
284 let length_times_two: U256 = length_times_two_plus_one - U256::ONE;
285 let length_u256: U256 = length_times_two >> 1;
286 length_u256.to::<usize>()
287 } else {
288 let bytes = slot_value.to_be_bytes::<32>();
291 (bytes[31] / 2) as usize
292 }
293}
294
295#[inline]
297fn calc_chunks(byte_length: usize) -> usize {
298 byte_length.div_ceil(32)
299}
300
301#[inline]
305fn encode_short_string(bytes: &[u8]) -> U256 {
306 let mut storage_bytes = [0u8; 32];
307 storage_bytes[..bytes.len()].copy_from_slice(bytes);
308 storage_bytes[31] = (bytes.len() * 2) as u8;
309 U256::from_be_bytes(storage_bytes)
310}
311
312#[inline]
316fn encode_long_string_length(byte_length: usize) -> U256 {
317 U256::from(byte_length * 2 + 1)
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::{
324 storage::{Handler, StorageCtx},
325 test_util::setup_storage,
326 };
327 use proptest::prelude::*;
328
329 fn arb_safe_slot() -> impl Strategy<Value = U256> {
331 any::<[u64; 4]>().prop_map(|limbs| {
332 U256::from_limbs(limbs) % (U256::MAX - U256::from(10000))
334 })
335 }
336
337 fn arb_short_string() -> impl Strategy<Value = String> {
339 prop_oneof![
340 Just(String::new()),
342 "[a-zA-Z0-9]{1,31}",
344 "[\u{0041}-\u{005A}\u{4E00}-\u{4E19}]{1,10}",
346 ]
347 }
348
349 fn arb_32byte_string() -> impl Strategy<Value = String> {
351 "[a-zA-Z0-9]{32}"
352 }
353
354 fn arb_long_string() -> impl Strategy<Value = String> {
356 prop_oneof![
357 "[a-zA-Z0-9]{33,100}",
359 "[\u{0041}-\u{005A}\u{4E00}-\u{4E19}]{11,30}",
361 ]
362 }
363
364 fn arb_short_bytes() -> impl Strategy<Value = Bytes> {
366 prop::collection::vec(any::<u8>(), 0..=31).prop_map(Bytes::from)
367 }
368
369 fn arb_32byte_bytes() -> impl Strategy<Value = Bytes> {
371 prop::collection::vec(any::<u8>(), 32..=32).prop_map(Bytes::from)
372 }
373
374 fn arb_long_bytes() -> impl Strategy<Value = Bytes> {
376 prop::collection::vec(any::<u8>(), 33..=100).prop_map(Bytes::from)
377 }
378
379 #[test]
382 fn test_calc_data_slot_matches_manual_keccak() {
383 let base_slot = U256::random();
384 let data_slot = calc_data_slot(base_slot);
385
386 let expected = U256::from_be_bytes(keccak256(base_slot.to_be_bytes::<32>()).0);
388
389 assert_eq!(
390 data_slot, expected,
391 "calc_data_slot should match manual keccak256 computation"
392 );
393 }
394
395 #[test]
396 fn test_is_long_string_boundaries() {
397 let short_31_bytes = encode_short_string(&[b'a'; 31]);
399 assert!(
400 !is_long_string(short_31_bytes),
401 "31-byte string should be short"
402 );
403
404 let long_32_bytes = encode_long_string_length(32);
406 assert!(
407 is_long_string(long_32_bytes),
408 "32-byte string should be long"
409 );
410
411 let empty = encode_short_string(&[]);
413 assert!(!is_long_string(empty), "Empty string should be short");
414
415 let one_byte = encode_short_string(b"x");
417 assert!(!is_long_string(one_byte), "1-byte string should be short");
418 }
419
420 #[test]
421 fn test_calc_string_length_short() {
422 for len in 0..=31 {
424 let bytes = vec![b'a'; len];
425 let encoded = encode_short_string(&bytes);
426 let decoded_len = calc_string_length(encoded, false);
427 assert_eq!(
428 decoded_len, len,
429 "Short string length mismatch for {len} bytes"
430 );
431 }
432 }
433
434 #[test]
435 fn test_calc_string_length_long() {
436 for len in [32, 33, 63, 64, 65, 100, 1000, 10000] {
438 let encoded = encode_long_string_length(len);
439 let decoded_len = calc_string_length(encoded, true);
440 assert_eq!(
441 decoded_len, len,
442 "Long string length mismatch for {len} bytes"
443 );
444 }
445 }
446
447 #[test]
448 fn test_calc_chunks_boundaries() {
449 assert_eq!(calc_chunks(0), 0, "0 bytes should require 0 chunks");
450 assert_eq!(calc_chunks(1), 1, "1 byte should require 1 chunk");
451 assert_eq!(calc_chunks(31), 1, "31 bytes should require 1 chunk");
452 assert_eq!(calc_chunks(32), 1, "32 bytes should require 1 chunk");
453 assert_eq!(calc_chunks(33), 2, "33 bytes should require 2 chunks");
454 assert_eq!(calc_chunks(64), 2, "64 bytes should require 2 chunks");
455 assert_eq!(calc_chunks(65), 3, "65 bytes should require 3 chunks");
456 assert_eq!(calc_chunks(100), 4, "100 bytes should require 4 chunks");
457 }
458
459 #[test]
460 fn test_encode_short_string_format() {
461 let test_str = b"Hello";
462 let encoded = encode_short_string(test_str);
463 let bytes = encoded.to_be_bytes::<32>();
464
465 assert_eq!(&bytes[..5], test_str, "Data should be left-aligned");
467
468 assert_eq!(&bytes[5..31], &[0u8; 26], "Padding should be zero");
470
471 assert_eq!(
473 bytes[31],
474 (test_str.len() * 2) as u8,
475 "LSB should be length * 2"
476 );
477
478 assert_eq!(bytes[31] & 1, 0, "Bit 0 should be 0 for short strings");
480 }
481
482 #[test]
483 fn test_encode_short_string_empty() {
484 let encoded = encode_short_string(&[]);
485 let bytes = encoded.to_be_bytes::<32>();
486
487 assert_eq!(bytes, [0u8; 32], "Empty string should encode to all zeros");
489 }
490
491 #[test]
492 fn test_encode_long_string_length_formula() {
493 for len in [32, 33, 100, 1000, 10000] {
494 let encoded = encode_long_string_length(len);
495 let expected = U256::from(len * 2 + 1);
496 assert_eq!(
497 encoded, expected,
498 "Long string length encoding mismatch for {len} bytes"
499 );
500
501 assert_eq!(encoded.byte(0) & 1, 1, "Bit 0 should be 1 for long strings");
503 }
504 }
505
506 #[test]
507 fn test_encode_decode_roundtrip() {
508 for len in [0, 1, 15, 30, 31] {
510 let bytes = vec![b'x'; len];
511 let encoded = encode_short_string(&bytes);
512 let decoded_len = calc_string_length(encoded, false);
513 assert_eq!(
514 decoded_len, len,
515 "Short string roundtrip failed for {len} bytes"
516 );
517 }
518
519 for len in [32, 33, 64, 100] {
521 let encoded = encode_long_string_length(len);
522 let decoded_len = calc_string_length(encoded, true);
523 assert_eq!(
524 decoded_len, len,
525 "Long string roundtrip failed for {len} bytes"
526 );
527 }
528 }
529
530 proptest! {
533 #![proptest_config(ProptestConfig::with_cases(500))]
534
535 #[test]
536 fn test_short_strings(s in arb_short_string(), base_slot in arb_safe_slot()) {
537 let (mut storage, address) = setup_storage();
538 StorageCtx::enter(&mut storage, || {
539 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
540
541 slot.write(s.clone()).unwrap();
543 let loaded = slot.read().unwrap();
544 prop_assert_eq!(&s, &loaded, "Short string roundtrip failed");
545
546 slot.delete().unwrap();
548 let after_delete = slot.read().unwrap();
549 prop_assert_eq!(after_delete, String::new(), "Short string not empty after delete");
550
551 Ok(())
552 }).unwrap();
553 }
554
555 #[test]
556 #[allow(clippy::redundant_clone)]
557 fn test_32byte_strings(s in arb_32byte_string(), base_slot in arb_safe_slot()) {
558 let (mut storage, address) = setup_storage();
559 StorageCtx::enter(&mut storage, || {
560 prop_assert_eq!(s.len(), 32, "Generated string should be exactly 32 bytes");
562
563 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
564
565 slot.write(s.clone()).unwrap();
567 let loaded = slot.read().unwrap();
568 prop_assert_eq!(s.clone(), loaded, "32-byte string roundtrip failed");
569
570 slot.delete().unwrap();
572 let after_delete = slot.read().unwrap();
573 prop_assert_eq!(after_delete, String::new(), "32-byte string not empty after delete");
574
575 Ok(())
576 }).unwrap();
577 }
578
579 #[test]
580 fn test_long_strings(s in arb_long_string(), base_slot in arb_safe_slot()) {
581 let (mut storage, address) = setup_storage();
582 StorageCtx::enter(&mut storage, || {
583 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
584
585 slot.write(s.clone()).unwrap();
587 let loaded = slot.read().unwrap();
588 prop_assert_eq!(&s, &loaded, "Long string roundtrip failed for length: {}", s.len());
589
590 let chunks = calc_chunks(s.len());
592
593 slot.delete().unwrap();
595 let after_delete = slot.read().unwrap();
596 prop_assert_eq!(after_delete, String::new(), "Long string not empty after delete");
597
598 let data_slot_start = calc_data_slot(base_slot);
600 for i in 0..chunks {
601 let slot = Slot::<U256>::new_at_offset(data_slot_start, i, address);
602 let value = slot.read().unwrap();
603 prop_assert_eq!(value, U256::ZERO, "Data slot not cleared after delete");
604 }
605
606 Ok(())
607 }).unwrap();
608 }
609
610 #[test]
611 fn test_short_bytes(b in arb_short_bytes(), base_slot in arb_safe_slot()) {
612 let (mut storage, address) = setup_storage();
613 StorageCtx::enter(&mut storage, || {
614 let mut slot = BytesLikeHandler::<Bytes>::new(base_slot, address);
615
616 slot.write(b.clone()).unwrap();
618 let loaded = slot.read().unwrap();
619 prop_assert_eq!(&b, &loaded, "Short bytes roundtrip failed for length: {}", b.len());
620
621 slot.delete().unwrap();
623 let after_delete = slot.read().unwrap();
624 prop_assert_eq!(after_delete, Bytes::new(), "Short bytes not empty after delete");
625
626 Ok(())
627 }).unwrap();
628 }
629
630 #[test]
631 fn test_32byte_bytes(b in arb_32byte_bytes(), base_slot in arb_safe_slot()) {
632 let (mut storage, address) = setup_storage();
633 StorageCtx::enter(&mut storage, || {
634 prop_assert_eq!(b.len(), 32, "Generated bytes should be exactly 32 bytes");
636
637 let mut slot = BytesLikeHandler::<Bytes>::new(base_slot, address);
638
639 slot.write(b.clone()).unwrap();
641 let loaded = slot.read().unwrap();
642 prop_assert_eq!(&b, &loaded, "32-byte bytes roundtrip failed");
643
644 slot.delete().unwrap();
646 let after_delete = slot.read().unwrap();
647 prop_assert_eq!(after_delete, Bytes::new(), "32-byte bytes not empty after delete");
648
649 Ok(())
650 }).unwrap();
651 }
652
653 #[test]
654 fn test_long_bytes(b in arb_long_bytes(), base_slot in arb_safe_slot()) {
655 let (mut storage, address) = setup_storage();
656 StorageCtx::enter(&mut storage, || {
657 let mut slot = BytesLikeHandler::<Bytes>::new(base_slot, address);
658
659 slot.write(b.clone()).unwrap();
661 let loaded = slot.read().unwrap();
662 prop_assert_eq!(&b, &loaded, "Long bytes roundtrip failed for length: {}", b.len());
663
664 let chunks = calc_chunks(b.len());
666
667 slot.delete().unwrap();
669 let after_delete = slot.read().unwrap();
670 prop_assert_eq!(after_delete, Bytes::new(), "Long bytes not empty after delete");
671
672 let data_slot_start = calc_data_slot(base_slot);
674 for i in 0..chunks {
675 let slot = Slot::<U256>::new_at_offset(data_slot_start, i, address);
676 let value = slot.read().unwrap();
677 prop_assert_eq!(value, U256::ZERO, "Data slot not cleared after delete");
678 }
679
680 Ok(())
681 }).unwrap();
682 }
683
684 #[test]
685 fn test_string_len(s in prop_oneof![arb_short_string(), arb_long_string()], base_slot in arb_safe_slot()) {
686 let (mut storage, address) = setup_storage();
687 StorageCtx::enter(&mut storage, || {
688 let mut slot = BytesLikeHandler::<String>::new(base_slot, address);
689
690 prop_assert_eq!(slot.len().unwrap(), 0, "Empty string should have len 0");
692 prop_assert!(slot.is_empty().unwrap(), "Empty string should be empty");
693
694 slot.write(s.clone()).unwrap();
696 prop_assert_eq!(slot.len().unwrap(), s.len(), "len() should match string byte length");
697 prop_assert_eq!(slot.is_empty().unwrap(), s.is_empty(), "is_empty() should match");
698
699 slot.delete().unwrap();
701 prop_assert_eq!(slot.len().unwrap(), 0, "Deleted string should have len 0");
702
703 Ok(())
704 }).unwrap();
705 }
706 }
707}