1use crate::error::ProgramError;
38
39pub trait TailCodec: Sized {
47 const MAX_ENCODED_LEN: usize;
52
53 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError>;
57
58 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError>;
61}
62
63pub trait TailElement: TailCodec + Copy + Default + PartialEq {}
69
70impl<T> TailElement for T where T: TailCodec + Copy + Default + PartialEq {}
71
72impl TailCodec for u8 {
75 const MAX_ENCODED_LEN: usize = 1;
76 #[inline]
77 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
78 if out.is_empty() {
79 return Err(ProgramError::AccountDataTooSmall);
80 }
81 out[0] = *self;
82 Ok(1)
83 }
84 #[inline]
85 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
86 input
87 .first()
88 .copied()
89 .map(|b| (b, 1))
90 .ok_or(ProgramError::InvalidAccountData)
91 }
92}
93
94macro_rules! tail_codec_int {
95 ( $( $ty:ty : $n:expr ),+ $(,)? ) => {
96 $(
97 impl TailCodec for $ty {
98 const MAX_ENCODED_LEN: usize = $n;
99 #[inline]
100 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
101 if out.len() < $n {
102 return Err(ProgramError::AccountDataTooSmall);
103 }
104 out[..$n].copy_from_slice(&self.to_le_bytes());
105 Ok($n)
106 }
107 #[inline]
108 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
109 if input.len() < $n {
110 return Err(ProgramError::InvalidAccountData);
111 }
112 let mut bytes = [0u8; $n];
113 bytes.copy_from_slice(&input[..$n]);
114 Ok((Self::from_le_bytes(bytes), $n))
115 }
116 }
117 )+
118 };
119}
120
121tail_codec_int! {
122 u16: 2, u32: 4, u64: 8, u128: 16,
123 i16: 2, i32: 4, i64: 8, i128: 16,
124}
125
126impl TailCodec for bool {
128 const MAX_ENCODED_LEN: usize = 1;
129 #[inline]
130 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
131 if out.is_empty() {
132 return Err(ProgramError::AccountDataTooSmall);
133 }
134 out[0] = if *self { 1 } else { 0 };
135 Ok(1)
136 }
137 #[inline]
138 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
139 match input.first().copied() {
140 Some(0) => Ok((false, 1)),
141 Some(1) => Ok((true, 1)),
142 _ => Err(ProgramError::InvalidAccountData),
143 }
144 }
145}
146
147impl<const N: usize> TailCodec for [u8; N] {
149 const MAX_ENCODED_LEN: usize = N;
150 #[inline]
151 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
152 if out.len() < N {
153 return Err(ProgramError::AccountDataTooSmall);
154 }
155 out[..N].copy_from_slice(self);
156 Ok(N)
157 }
158 #[inline]
159 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
160 if input.len() < N {
161 return Err(ProgramError::InvalidAccountData);
162 }
163 let mut out = [0u8; N];
164 out.copy_from_slice(&input[..N]);
165 Ok((out, N))
166 }
167}
168
169impl<T: TailCodec> TailCodec for Option<T> {
171 const MAX_ENCODED_LEN: usize = 1 + T::MAX_ENCODED_LEN;
172 #[inline]
173 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
174 if out.is_empty() {
175 return Err(ProgramError::AccountDataTooSmall);
176 }
177 match self {
178 None => {
179 out[0] = 0;
180 Ok(1)
181 }
182 Some(inner) => {
183 out[0] = 1;
184 let written = inner.encode(&mut out[1..])?;
185 Ok(1 + written)
186 }
187 }
188 }
189 #[inline]
190 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
191 match input.first().copied() {
192 Some(0) => Ok((None, 1)),
193 Some(1) => {
194 let (inner, n) = T::decode(&input[1..])?;
195 Ok((Some(inner), 1 + n))
196 }
197 _ => Err(ProgramError::InvalidAccountData),
198 }
199 }
200}
201
202#[derive(Clone, Copy, Eq, PartialEq)]
210pub struct BoundedString<const N: usize> {
211 len: u16,
212 bytes: [u8; N],
213}
214
215impl<const N: usize> BoundedString<N> {
216 #[inline]
218 pub const fn empty() -> Self {
219 Self {
220 len: 0,
221 bytes: [0u8; N],
222 }
223 }
224
225 #[inline]
227 pub fn from_str(value: &str) -> Result<Self, ProgramError> {
228 Self::from_bytes(value.as_bytes())
229 }
230
231 #[inline]
233 pub fn from_bytes(value: &[u8]) -> Result<Self, ProgramError> {
234 if value.len() > N || value.len() > u16::MAX as usize {
235 return Err(ProgramError::InvalidInstructionData);
236 }
237 let mut out = Self::empty();
238 out.bytes[..value.len()].copy_from_slice(value);
239 out.len = value.len() as u16;
240 Ok(out)
241 }
242
243 #[inline]
245 pub fn set_str(&mut self, value: &str) -> Result<(), ProgramError> {
246 self.set_bytes(value.as_bytes())
247 }
248
249 #[inline]
251 pub fn set_bytes(&mut self, value: &[u8]) -> Result<(), ProgramError> {
252 if value.len() > N || value.len() > u16::MAX as usize {
253 return Err(ProgramError::InvalidInstructionData);
254 }
255 self.bytes = [0u8; N];
256 self.bytes[..value.len()].copy_from_slice(value);
257 self.len = value.len() as u16;
258 Ok(())
259 }
260
261 #[inline]
263 pub fn clear(&mut self) {
264 self.bytes = [0u8; N];
265 self.len = 0;
266 }
267
268 #[inline(always)]
270 pub fn as_bytes(&self) -> &[u8] {
271 &self.bytes[..self.len as usize]
272 }
273
274 #[inline]
276 pub fn as_str(&self) -> Result<&str, ProgramError> {
277 core::str::from_utf8(self.as_bytes()).map_err(|_| ProgramError::InvalidAccountData)
278 }
279
280 #[inline(always)]
282 pub const fn len(&self) -> usize {
283 self.len as usize
284 }
285
286 #[inline(always)]
288 pub const fn capacity(&self) -> usize {
289 N
290 }
291
292 #[inline(always)]
294 pub const fn remaining_capacity(&self) -> usize {
295 N - self.len as usize
296 }
297
298 #[inline(always)]
300 pub const fn is_full(&self) -> bool {
301 self.len as usize == N
302 }
303
304 #[inline(always)]
306 pub const fn is_empty(&self) -> bool {
307 self.len == 0
308 }
309}
310
311impl<const N: usize> Default for BoundedString<N> {
312 #[inline]
313 fn default() -> Self {
314 Self::empty()
315 }
316}
317
318impl<const N: usize> TailCodec for BoundedString<N> {
319 const MAX_ENCODED_LEN: usize = 2 + N;
320
321 #[inline]
322 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
323 let len = self.len as usize;
324 if len > N || out.len() < 2 + len {
325 return Err(ProgramError::AccountDataTooSmall);
326 }
327 out[..2].copy_from_slice(&self.len.to_le_bytes());
328 out[2..2 + len].copy_from_slice(&self.bytes[..len]);
329 Ok(2 + len)
330 }
331
332 #[inline]
333 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
334 if input.len() < 2 {
335 return Err(ProgramError::InvalidAccountData);
336 }
337 let len = u16::from_le_bytes([input[0], input[1]]) as usize;
338 if len > N || input.len() < 2 + len {
339 return Err(ProgramError::InvalidAccountData);
340 }
341 let mut out = Self::empty();
342 out.len = len as u16;
343 out.bytes[..len].copy_from_slice(&input[2..2 + len]);
344 Ok((out, 2 + len))
345 }
346}
347
348#[derive(Clone, Copy, Eq, PartialEq)]
354pub struct BoundedVec<T, const N: usize>
355where
356 T: TailCodec + Copy + Default,
357{
358 len: u16,
359 items: [T; N],
360}
361
362impl<T, const N: usize> BoundedVec<T, N>
363where
364 T: TailCodec + Copy + Default,
365{
366 #[inline]
368 pub fn empty() -> Self {
369 Self {
370 len: 0,
371 items: [T::default(); N],
372 }
373 }
374
375 #[inline]
377 pub fn from_slice(values: &[T]) -> Result<Self, ProgramError> {
378 if values.len() > N || values.len() > u16::MAX as usize {
379 return Err(ProgramError::InvalidInstructionData);
380 }
381 let mut out = Self::empty();
382 out.items[..values.len()].copy_from_slice(values);
383 out.len = values.len() as u16;
384 Ok(out)
385 }
386
387 #[inline]
389 pub fn push(&mut self, item: T) -> Result<(), ProgramError> {
390 let len = self.len as usize;
391 if len >= N || len >= u16::MAX as usize {
392 return Err(ProgramError::AccountDataTooSmall);
393 }
394 self.items[len] = item;
395 self.len += 1;
396 Ok(())
397 }
398
399 #[inline]
401 pub fn pop(&mut self) -> Option<T> {
402 let len = self.len as usize;
403 if len == 0 {
404 return None;
405 }
406 let new_len = len - 1;
407 let item = self.items[new_len];
408 self.items[new_len] = T::default();
409 self.len = new_len as u16;
410 Some(item)
411 }
412
413 #[inline]
415 pub fn clear(&mut self) {
416 let len = self.len as usize;
417 let mut i = 0;
418 while i < len {
419 self.items[i] = T::default();
420 i += 1;
421 }
422 self.len = 0;
423 }
424
425 #[inline(always)]
427 pub fn as_slice(&self) -> &[T] {
428 &self.items[..self.len as usize]
429 }
430
431 #[inline(always)]
433 pub fn as_mut_slice(&mut self) -> &mut [T] {
434 &mut self.items[..self.len as usize]
435 }
436
437 #[inline(always)]
439 pub const fn len(&self) -> usize {
440 self.len as usize
441 }
442
443 #[inline(always)]
445 pub const fn capacity(&self) -> usize {
446 N
447 }
448
449 #[inline(always)]
451 pub const fn remaining_capacity(&self) -> usize {
452 N - self.len as usize
453 }
454
455 #[inline(always)]
457 pub const fn is_full(&self) -> bool {
458 self.len as usize == N
459 }
460
461 #[inline(always)]
463 pub const fn is_empty(&self) -> bool {
464 self.len == 0
465 }
466}
467
468impl<T, const N: usize> BoundedVec<T, N>
469where
470 T: TailCodec + Copy + Default + PartialEq,
471{
472 #[inline]
474 pub fn contains(&self, item: &T) -> bool {
475 self.as_slice().iter().any(|candidate| candidate == item)
476 }
477
478 #[inline]
483 pub fn push_unique(&mut self, item: T) -> Result<bool, ProgramError> {
484 if self.contains(&item) {
485 return Ok(false);
486 }
487 self.push(item)?;
488 Ok(true)
489 }
490
491 #[inline]
495 pub fn remove_first(&mut self, item: &T) -> bool {
496 let len = self.len as usize;
497 let mut found = None;
498 let mut i = 0;
499 while i < len {
500 if &self.items[i] == item {
501 found = Some(i);
502 break;
503 }
504 i += 1;
505 }
506 let Some(index) = found else {
507 return false;
508 };
509 let mut j = index;
510 while j + 1 < len {
511 self.items[j] = self.items[j + 1];
512 j += 1;
513 }
514 self.items[len - 1] = T::default();
515 self.len = (len - 1) as u16;
516 true
517 }
518}
519
520pub type HopperString<const N: usize> = BoundedString<N>;
522
523pub type HopperVec<T, const N: usize> = BoundedVec<T, N>;
525
526impl<T, const N: usize> Default for BoundedVec<T, N>
527where
528 T: TailCodec + Copy + Default,
529{
530 #[inline]
531 fn default() -> Self {
532 Self::empty()
533 }
534}
535
536impl<T, const N: usize> TailCodec for BoundedVec<T, N>
537where
538 T: TailCodec + Copy + Default,
539{
540 const MAX_ENCODED_LEN: usize = 2 + (N * T::MAX_ENCODED_LEN);
541
542 #[inline]
543 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
544 let len = self.len as usize;
545 if len > N || out.len() < 2 {
546 return Err(ProgramError::AccountDataTooSmall);
547 }
548 out[..2].copy_from_slice(&self.len.to_le_bytes());
549 let mut cursor = 2;
550 for item in self.as_slice() {
551 let written = item.encode(&mut out[cursor..])?;
552 cursor = cursor
553 .checked_add(written)
554 .ok_or(ProgramError::AccountDataTooSmall)?;
555 }
556 Ok(cursor)
557 }
558
559 #[inline]
560 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
561 if input.len() < 2 {
562 return Err(ProgramError::InvalidAccountData);
563 }
564 let len = u16::from_le_bytes([input[0], input[1]]) as usize;
565 if len > N {
566 return Err(ProgramError::InvalidAccountData);
567 }
568 let mut out = Self::empty();
569 let mut cursor = 2;
570 let mut i = 0;
571 while i < len {
572 let (item, consumed) = T::decode(&input[cursor..])?;
573 out.items[i] = item;
574 cursor = cursor
575 .checked_add(consumed)
576 .ok_or(ProgramError::InvalidAccountData)?;
577 i += 1;
578 }
579 out.len = len as u16;
580 Ok((out, cursor))
581 }
582}
583
584impl TailCodec for crate::address::Address {
585 const MAX_ENCODED_LEN: usize = 32;
586
587 #[inline]
588 fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
589 if out.len() < 32 {
590 return Err(ProgramError::AccountDataTooSmall);
591 }
592 out[..32].copy_from_slice(self.as_array());
593 Ok(32)
594 }
595
596 #[inline]
597 fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
598 if input.len() < 32 {
599 return Err(ProgramError::InvalidAccountData);
600 }
601 let mut bytes = [0u8; 32];
602 bytes.copy_from_slice(&input[..32]);
603 Ok((crate::address::Address::new(bytes), 32))
604 }
605}
606
607#[inline]
617pub fn read_tail_len(data: &[u8], body_end: usize) -> Result<u32, ProgramError> {
618 let end = body_end
619 .checked_add(4)
620 .ok_or(ProgramError::AccountDataTooSmall)?;
621 if data.len() < end {
622 return Err(ProgramError::AccountDataTooSmall);
623 }
624 let mut bytes = [0u8; 4];
625 bytes.copy_from_slice(&data[body_end..end]);
626 Ok(u32::from_le_bytes(bytes))
627}
628
629#[inline]
632pub fn tail_payload(data: &[u8], body_end: usize) -> Result<&[u8], ProgramError> {
633 let len = read_tail_len(data, body_end)? as usize;
634 let start = body_end + 4;
635 let end = start
636 .checked_add(len)
637 .ok_or(ProgramError::InvalidAccountData)?;
638 if data.len() < end {
639 return Err(ProgramError::InvalidAccountData);
640 }
641 Ok(&data[start..end])
642}
643
644#[inline]
650pub fn tail_capacity(data: &[u8], body_end: usize) -> Result<usize, ProgramError> {
651 let start = body_end
652 .checked_add(4)
653 .ok_or(ProgramError::AccountDataTooSmall)?;
654 if data.len() < start {
655 return Err(ProgramError::AccountDataTooSmall);
656 }
657 Ok(data.len() - start)
658}
659
660#[inline]
666pub fn borrow_bounded_str<const N: usize>(input: &[u8]) -> Result<(&str, usize), ProgramError> {
667 if input.len() < 2 {
668 return Err(ProgramError::InvalidAccountData);
669 }
670 let len = u16::from_le_bytes([input[0], input[1]]) as usize;
671 if len > N || input.len() < 2 + len {
672 return Err(ProgramError::InvalidAccountData);
673 }
674 let bytes = &input[2..2 + len];
675 let value = core::str::from_utf8(bytes).map_err(|_| ProgramError::InvalidAccountData)?;
676 Ok((value, 2 + len))
677}
678
679#[inline]
686pub fn borrow_address_slice<const N: usize>(
687 input: &[u8],
688) -> Result<(&[crate::address::Address], usize), ProgramError> {
689 if input.len() < 2 {
690 return Err(ProgramError::InvalidAccountData);
691 }
692 let len = u16::from_le_bytes([input[0], input[1]]) as usize;
693 if len > N {
694 return Err(ProgramError::InvalidAccountData);
695 }
696 let byte_len = len
697 .checked_mul(32)
698 .ok_or(ProgramError::InvalidAccountData)?;
699 let end = 2usize
700 .checked_add(byte_len)
701 .ok_or(ProgramError::InvalidAccountData)?;
702 if input.len() < end {
703 return Err(ProgramError::InvalidAccountData);
704 }
705 let bytes = &input[2..end];
706 let ptr = bytes.as_ptr() as *const crate::address::Address;
707 let values = unsafe { core::slice::from_raw_parts(ptr, len) };
710 Ok((values, end))
711}
712
713#[inline]
717pub fn read_tail<T: TailCodec>(data: &[u8], body_end: usize) -> Result<T, ProgramError> {
718 let payload = tail_payload(data, body_end)?;
719 let (value, consumed) = T::decode(payload)?;
720 if consumed != payload.len() {
721 return Err(ProgramError::InvalidAccountData);
722 }
723 Ok(value)
724}
725
726#[inline]
731pub fn write_tail<T: TailCodec>(
732 data: &mut [u8],
733 body_end: usize,
734 tail: &T,
735) -> Result<usize, ProgramError> {
736 let prefix_end = body_end
737 .checked_add(4)
738 .ok_or(ProgramError::AccountDataTooSmall)?;
739 if data.len() < prefix_end {
740 return Err(ProgramError::AccountDataTooSmall);
741 }
742 let written = tail.encode(&mut data[prefix_end..])?;
743 if written > u32::MAX as usize {
744 return Err(ProgramError::InvalidAccountData);
745 }
746 data[body_end..prefix_end].copy_from_slice(&(written as u32).to_le_bytes());
747 Ok(written)
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753
754 #[test]
755 fn u32_roundtrip() {
756 let mut buf = [0u8; 8];
757 let n = 0xDEAD_BEEFu32.encode(&mut buf).unwrap();
758 assert_eq!(n, 4);
759 let (back, consumed) = u32::decode(&buf).unwrap();
760 assert_eq!(consumed, 4);
761 assert_eq!(back, 0xDEAD_BEEF);
762 }
763
764 #[test]
765 fn u64_roundtrip() {
766 let mut buf = [0u8; 8];
767 0x0123_4567_89AB_CDEFu64.encode(&mut buf).unwrap();
768 let (back, _) = u64::decode(&buf).unwrap();
769 assert_eq!(back, 0x0123_4567_89AB_CDEF);
770 }
771
772 #[test]
773 fn bool_encode_decode() {
774 let mut buf = [0u8; 1];
775 true.encode(&mut buf).unwrap();
776 assert_eq!(buf[0], 1);
777 assert_eq!(bool::decode(&buf).unwrap(), (true, 1));
778 false.encode(&mut buf).unwrap();
779 assert_eq!(buf[0], 0);
780 assert_eq!(bool::decode(&buf).unwrap(), (false, 1));
781 }
782
783 #[test]
784 fn bool_rejects_garbage() {
785 let buf = [2u8];
786 assert!(bool::decode(&buf).is_err());
787 }
788
789 #[test]
790 fn byte_array_roundtrip() {
791 let src: [u8; 8] = *b"HOPPER!!";
792 let mut buf = [0u8; 16];
793 let n = src.encode(&mut buf).unwrap();
794 assert_eq!(n, 8);
795 let (back, consumed) = <[u8; 8]>::decode(&buf).unwrap();
796 assert_eq!(consumed, 8);
797 assert_eq!(back, src);
798 }
799
800 #[test]
801 fn option_none_encodes_to_one_byte() {
802 let mut buf = [0u8; 16];
803 let n = Option::<u64>::None.encode(&mut buf).unwrap();
804 assert_eq!(n, 1);
805 assert_eq!(buf[0], 0);
806 let (back, c) = <Option<u64>>::decode(&buf).unwrap();
807 assert_eq!(back, None);
808 assert_eq!(c, 1);
809 }
810
811 #[test]
812 fn option_some_includes_inner_payload() {
813 let mut buf = [0u8; 16];
814 let n = Option::<u64>::Some(0xAAAA_BBBB_CCCC_DDDD)
815 .encode(&mut buf)
816 .unwrap();
817 assert_eq!(n, 9);
818 assert_eq!(buf[0], 1);
819 let (back, c) = <Option<u64>>::decode(&buf).unwrap();
820 assert_eq!(back, Some(0xAAAA_BBBB_CCCC_DDDD));
821 assert_eq!(c, 9);
822 }
823
824 #[test]
825 fn option_rejects_invalid_tag() {
826 let buf = [7u8, 0, 0, 0, 0, 0, 0, 0, 0];
827 assert!(<Option<u64>>::decode(&buf).is_err());
828 }
829
830 #[test]
831 fn tail_length_prefix_roundtrip() {
832 let mut data = [0u8; 64];
835 let body_end = 24usize;
836 let tail_value: u64 = 0x1234_5678_9ABC_DEF0;
837 let written = write_tail(&mut data, body_end, &tail_value).unwrap();
838 assert_eq!(written, 8);
839 let read_len = read_tail_len(&data, body_end).unwrap();
840 assert_eq!(read_len, 8);
841 let back: u64 = read_tail::<u64>(&data, body_end).unwrap();
842 assert_eq!(back, tail_value);
843 }
844
845 #[test]
846 fn tail_decode_rejects_excess_payload() {
847 let mut data = [0u8; 32];
850 let body_end = 16usize;
854 data[body_end..body_end + 4].copy_from_slice(&8u32.to_le_bytes());
855 data[body_end + 4..body_end + 8].copy_from_slice(&0x1122_3344u32.to_le_bytes());
858 data[body_end + 8..body_end + 12].copy_from_slice(&0xFFu32.to_le_bytes());
859 let result = read_tail::<u32>(&data, body_end);
861 assert!(result.is_err());
862 }
863
864 #[test]
865 fn tail_bounds_check_on_short_buffer() {
866 let data = [0u8; 10];
867 assert!(read_tail_len(&data, 16).is_err());
868 assert!(tail_payload(&data, 16).is_err());
869 }
870
871 #[test]
872 fn max_encoded_len_matches_actual_encode_size() {
873 let mut buf = [0u8; 32];
874 assert_eq!(0u32.encode(&mut buf).unwrap(), u32::MAX_ENCODED_LEN);
875 assert_eq!(0u64.encode(&mut buf).unwrap(), u64::MAX_ENCODED_LEN);
876 assert_eq!(true.encode(&mut buf).unwrap(), bool::MAX_ENCODED_LEN);
877 assert_eq!(
878 [0u8; 7].encode(&mut buf).unwrap(),
879 <[u8; 7]>::MAX_ENCODED_LEN
880 );
881 assert_eq!(Option::<u32>::None.encode(&mut buf).unwrap(), 1);
882 assert_eq!(
883 Option::<u32>::Some(0).encode(&mut buf).unwrap(),
884 <Option<u32>>::MAX_ENCODED_LEN
885 );
886 }
887
888 #[test]
889 fn bounded_string_roundtrip() {
890 let label = BoundedString::<32>::from_str("multisig").unwrap();
891 let mut buf = [0u8; BoundedString::<32>::MAX_ENCODED_LEN];
892 let written = label.encode(&mut buf).unwrap();
893 assert_eq!(written, 10);
894 let (back, consumed) = BoundedString::<32>::decode(&buf).unwrap();
895 assert_eq!(consumed, written);
896 assert_eq!(back.as_str().unwrap(), "multisig");
897 }
898
899 #[test]
900 fn bounded_string_capacity_helpers() {
901 let mut label = HopperString::<8>::from_str("ops").unwrap();
902 assert_eq!(label.remaining_capacity(), 5);
903 assert!(!label.is_full());
904 label.set_str("12345678").unwrap();
905 assert!(label.is_full());
906 label.clear();
907 assert!(label.is_empty());
908 assert_eq!(label.as_bytes(), b"");
909 }
910
911 #[test]
912 fn bounded_vec_roundtrip() {
913 let mut vec = BoundedVec::<u64, 4>::empty();
914 vec.push(7).unwrap();
915 vec.push(9).unwrap();
916 let mut buf = [0u8; BoundedVec::<u64, 4>::MAX_ENCODED_LEN];
917 let written = vec.encode(&mut buf).unwrap();
918 assert_eq!(written, 18);
919 let (back, consumed) = BoundedVec::<u64, 4>::decode(&buf).unwrap();
920 assert_eq!(consumed, written);
921 assert_eq!(back.as_slice(), &[7, 9]);
922 }
923
924 #[test]
925 fn bounded_vec_set_helpers_preserve_order() {
926 let mut vec = HopperVec::<u64, 4>::empty();
927 assert_eq!(vec.remaining_capacity(), 4);
928 assert_eq!(vec.push_unique(7).unwrap(), true);
929 assert_eq!(vec.push_unique(7).unwrap(), false);
930 vec.push(9).unwrap();
931 vec.push(11).unwrap();
932 assert!(vec.contains(&9));
933 assert!(vec.remove_first(&9));
934 assert_eq!(vec.as_slice(), &[7, 11]);
935 assert_eq!(vec.pop(), Some(11));
936 assert_eq!(vec.as_slice(), &[7]);
937 vec.clear();
938 assert!(vec.is_empty());
939 }
940}
941
942#[cfg(kani)]
943mod kani_proofs {
944 use super::*;
945
946 #[kani::proof]
947 fn bounded_string_decode_never_exceeds_capacity() {
948 let len: u16 = kani::any();
949 let mut input = [0u8; BoundedString::<4>::MAX_ENCODED_LEN];
950 input[..2].copy_from_slice(&len.to_le_bytes());
951
952 let result = BoundedString::<4>::decode(&input);
953 if len as usize > 4 {
954 assert!(result.is_err());
955 } else {
956 let (decoded, consumed) = result.unwrap();
957 assert!(decoded.len() <= decoded.capacity());
958 assert_eq!(consumed, 2 + decoded.len());
959 }
960 }
961
962 #[kani::proof]
963 fn bounded_vec_mutators_preserve_capacity() {
964 let values: [u8; 5] = kani::any();
965 let mut vec = BoundedVec::<u8, 4>::empty();
966
967 let _ = vec.push(values[0]);
968 let _ = vec.push(values[1]);
969 let _ = vec.push(values[2]);
970 let _ = vec.push(values[3]);
971 let fifth = vec.push(values[4]);
972
973 assert!(vec.len() <= vec.capacity());
974 assert!(fifth.is_err());
975 let _ = vec.pop();
976 assert!(vec.len() <= vec.capacity());
977 vec.clear();
978 assert_eq!(vec.len(), 0);
979 }
980
981 #[kani::proof]
982 fn tail_payload_bounds_checks_arbitrary_prefixes() {
983 let data: [u8; 16] = kani::any();
984 let body_end: usize = kani::any();
985 kani::assume(body_end < data.len());
986
987 let result = tail_payload(&data, body_end);
988 if let Ok(payload) = result {
989 assert!(payload.len() <= data.len());
990 }
991 }
992}