Skip to main content

hopper_runtime/
tail.rs

1//! Hybrid serialization tail for `#[hopper::state(dynamic_tail = T)]`.
2//!
3//! Hybrid fixed-head and variable-tail storage keeps hot fields directly
4//! addressable while bounding variable data:
5//!
6//! > Lets Hopper own the fixed-layout hot path while still supporting a
7//! > dynamic tail for vectors, strings, and optional metadata.
8//!
9//! # Wire format
10//!
11//! After the layout's fixed body (offset `TYPE_OFFSET + WIRE_SIZE`), the
12//! tail is encoded as:
13//!
14//! ```text
15//! [ len: u32 LE ] [ payload: len bytes ]
16//! ```
17//!
18//! The fixed-body fast path remains fully zero-copy. code that never
19//! touches the tail pays zero overhead. Tail access is explicit
20//! (`tail_read::<T>()` / `tail_write::<T>()`), which is why the tail
21//! is **not** zero-copy: the typed representation is reconstructed on
22//! read and serialized on write.
23//!
24//! # Canonical tail encoding (`TailCodec`)
25//!
26//! `TailCodec` is a minimal Borsh-subset serializer:
27//!
28//! * integers: native little-endian
29//! * `[u8; N]`: raw bytes, fixed width
30//! * bounded byte/string payloads: program-defined length prefix + bytes
31//! * `Option<T>`: 1-byte tag (0 = None, 1 = Some) + inner payload
32//!
33//! Programs that need richer types (bounded strings, bounded vectors,
34//! custom structs) implement `TailCodec` themselves; the framework does not
35//! force a derive or pull `Vec` / `String` into the no-alloc runtime surface.
36
37use core::marker::PhantomData;
38
39use crate::borrow::{Ref, RefMut};
40use crate::error::ProgramError;
41use crate::segment_lease::SegmentLease;
42
43/// Canonical serializer for dynamic-tail payloads.
44///
45/// Implementations encode into a caller-provided buffer and decode
46/// from a caller-provided slice, returning the byte count consumed
47/// in both directions. Byte counts drive the length-prefix handling
48/// inside `#[hopper::state]`'s generated tail accessors. the
49/// encoding must be deterministic and bidirectional.
50pub trait TailCodec: Sized {
51    /// Upper bound on the encoded size. Used by generated helpers to
52    /// verify the account has enough room before invoking `encode`.
53    /// Implementors should pick the smallest valid bound. Hopper
54    /// uses this to pre-size reallocs.
55    const MAX_ENCODED_LEN: usize;
56
57    /// Serialize `self` into `out`. Returns the number of bytes
58    /// written (always `<= MAX_ENCODED_LEN`). Fails with
59    /// `AccountDataTooSmall` when `out.len() < encoded_len`.
60    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError>;
61
62    /// Deserialize from `input`. Returns `(value, bytes_consumed)`.
63    /// Fails with `InvalidAccountData` on malformed encoding.
64    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError>;
65}
66
67/// Element type accepted by `#[tail(vec<T, N>)]` in `#[hopper::dynamic_account]`.
68///
69/// A tail element must have deterministic Hopper tail encoding, be cheap to copy
70/// into the fixed-capacity backing array, have a default empty slot value, and be
71/// comparable for generated `push_unique_*` / `remove_*` helpers.
72pub trait TailElement: TailCodec + Copy + Default + PartialEq {}
73
74impl<T> TailElement for T where T: TailCodec + Copy + Default + PartialEq {}
75
76/// Borrowed final raw-byte tail.
77///
78/// `TailBytes<'a>` is intentionally not a `TailCodec`: it is a borrowed view
79/// over the bytes remaining after any bounded compact-tail fields. The owning
80/// account data provides the storage.
81#[derive(Clone, Copy, Eq, PartialEq)]
82pub struct TailBytes<'a> {
83    bytes: &'a [u8],
84}
85
86impl<'a> TailBytes<'a> {
87    /// Borrow `bytes` as a final raw tail.
88    #[inline(always)]
89    pub const fn new(bytes: &'a [u8]) -> Self {
90        Self { bytes }
91    }
92
93    /// Return the raw tail bytes.
94    #[inline(always)]
95    pub const fn as_bytes(&self) -> &'a [u8] {
96        self.bytes
97    }
98
99    /// Number of raw tail bytes.
100    #[inline(always)]
101    pub const fn len(&self) -> usize {
102        self.bytes.len()
103    }
104
105    /// Whether the raw tail is empty.
106    #[inline(always)]
107    pub const fn is_empty(&self) -> bool {
108        self.bytes.is_empty()
109    }
110}
111
112/// Borrowed final UTF-8 tail.
113///
114/// The raw bytes consume the remaining dynamic-tail payload. UTF-8 is checked
115/// only when the caller asks for `&str`, so binary-safe inspection and strict
116/// text validation are both available without copying.
117#[derive(Clone, Copy, Eq, PartialEq)]
118pub struct TailStr<'a> {
119    bytes: &'a [u8],
120}
121
122impl<'a> TailStr<'a> {
123    /// Borrow `bytes` as a final UTF-8 tail.
124    #[inline(always)]
125    pub const fn new(bytes: &'a [u8]) -> Self {
126        Self { bytes }
127    }
128
129    /// Borrow a string as a final UTF-8 tail.
130    #[inline(always)]
131    pub const fn from_str(value: &'a str) -> Self {
132        Self {
133            bytes: value.as_bytes(),
134        }
135    }
136
137    /// Return the raw UTF-8 bytes without validating again.
138    #[inline(always)]
139    pub const fn as_bytes(&self) -> &'a [u8] {
140        self.bytes
141    }
142
143    /// Validate and return the tail as UTF-8.
144    #[inline]
145    pub fn as_str(&self) -> Result<&'a str, ProgramError> {
146        core::str::from_utf8(self.bytes).map_err(|_| ProgramError::InvalidAccountData)
147    }
148
149    /// Number of raw UTF-8 bytes.
150    #[inline(always)]
151    pub const fn len(&self) -> usize {
152        self.bytes.len()
153    }
154
155    /// Whether the raw tail is empty.
156    #[inline(always)]
157    pub const fn is_empty(&self) -> bool {
158        self.bytes.is_empty()
159    }
160}
161
162// ── Primitive impls (little-endian, fixed width) ────────────────────
163
164impl TailCodec for u8 {
165    const MAX_ENCODED_LEN: usize = 1;
166    #[inline]
167    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
168        if out.is_empty() {
169            return Err(ProgramError::AccountDataTooSmall);
170        }
171        out[0] = *self;
172        Ok(1)
173    }
174    #[inline]
175    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
176        input
177            .first()
178            .copied()
179            .map(|b| (b, 1))
180            .ok_or(ProgramError::InvalidAccountData)
181    }
182}
183
184macro_rules! tail_codec_int {
185    ( $( $ty:ty : $n:expr ),+ $(,)? ) => {
186        $(
187            impl TailCodec for $ty {
188                const MAX_ENCODED_LEN: usize = $n;
189                #[inline]
190                fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
191                    if out.len() < $n {
192                        return Err(ProgramError::AccountDataTooSmall);
193                    }
194                    out[..$n].copy_from_slice(&self.to_le_bytes());
195                    Ok($n)
196                }
197                #[inline]
198                fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
199                    if input.len() < $n {
200                        return Err(ProgramError::InvalidAccountData);
201                    }
202                    let mut bytes = [0u8; $n];
203                    bytes.copy_from_slice(&input[..$n]);
204                    Ok((Self::from_le_bytes(bytes), $n))
205                }
206            }
207        )+
208    };
209}
210
211tail_codec_int! {
212    u16: 2, u32: 4, u64: 8, u128: 16,
213    i16: 2, i32: 4, i64: 8, i128: 16,
214}
215
216// `bool` as 1 byte (0 = false, 1 = true; anything else rejected).
217impl TailCodec for bool {
218    const MAX_ENCODED_LEN: usize = 1;
219    #[inline]
220    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
221        if out.is_empty() {
222            return Err(ProgramError::AccountDataTooSmall);
223        }
224        out[0] = if *self { 1 } else { 0 };
225        Ok(1)
226    }
227    #[inline]
228    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
229        match input.first().copied() {
230            Some(0) => Ok((false, 1)),
231            Some(1) => Ok((true, 1)),
232            _ => Err(ProgramError::InvalidAccountData),
233        }
234    }
235}
236
237// `[u8; N]`. raw fixed-width bytes.
238impl<const N: usize> TailCodec for [u8; N] {
239    const MAX_ENCODED_LEN: usize = N;
240    #[inline]
241    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
242        if out.len() < N {
243            return Err(ProgramError::AccountDataTooSmall);
244        }
245        out[..N].copy_from_slice(self);
246        Ok(N)
247    }
248    #[inline]
249    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
250        if input.len() < N {
251            return Err(ProgramError::InvalidAccountData);
252        }
253        let mut out = [0u8; N];
254        out.copy_from_slice(&input[..N]);
255        Ok((out, N))
256    }
257}
258
259// `Option<T>`. 1-byte tag + inner payload when present.
260impl<T: TailCodec> TailCodec for Option<T> {
261    const MAX_ENCODED_LEN: usize = 1 + T::MAX_ENCODED_LEN;
262    #[inline]
263    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
264        if out.is_empty() {
265            return Err(ProgramError::AccountDataTooSmall);
266        }
267        match self {
268            None => {
269                out[0] = 0;
270                Ok(1)
271            }
272            Some(inner) => {
273                out[0] = 1;
274                let written = inner.encode(&mut out[1..])?;
275                Ok(1 + written)
276            }
277        }
278    }
279    #[inline]
280    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
281        match input.first().copied() {
282            Some(0) => Ok((None, 1)),
283            Some(1) => {
284                let (inner, n) = T::decode(&input[1..])?;
285                Ok((Some(inner), 1 + n))
286            }
287            _ => Err(ProgramError::InvalidAccountData),
288        }
289    }
290}
291
292// -- Bounded dynamic-tail helpers ------------------------------------------
293
294/// Bounded UTF-8 string for Hopper dynamic tails.
295///
296/// This is the common migration target for bounded string account metadata.
297/// It keeps a fixed `[u8; N]` backing buffer, carries a small length prefix on
298/// the tail wire, and validates UTF-8 when read as `&str`.
299#[derive(Clone, Copy, Eq, PartialEq)]
300pub struct BoundedString<const N: usize> {
301    len: u16,
302    bytes: [u8; N],
303}
304
305impl<const N: usize> BoundedString<N> {
306    /// Construct an empty bounded string.
307    #[inline]
308    pub const fn empty() -> Self {
309        Self {
310            len: 0,
311            bytes: [0u8; N],
312        }
313    }
314
315    /// Construct from UTF-8 bytes, rejecting values longer than `N`.
316    ///
317    /// Inherent fallible constructor returning `ProgramError`, not
318    /// `core::str::FromStr` (which would force an associated `Err` type and a
319    /// trait import at every call site).
320    #[allow(clippy::should_implement_trait)]
321    #[inline]
322    pub fn from_str(value: &str) -> Result<Self, ProgramError> {
323        Self::from_bytes(value.as_bytes())
324    }
325
326    /// Construct from bytes, rejecting values longer than `N`.
327    #[inline]
328    pub fn from_bytes(value: &[u8]) -> Result<Self, ProgramError> {
329        if value.len() > N || value.len() > u16::MAX as usize {
330            return Err(ProgramError::InvalidInstructionData);
331        }
332        let mut out = Self::empty();
333        out.bytes[..value.len()].copy_from_slice(value);
334        out.len = value.len() as u16;
335        Ok(out)
336    }
337
338    /// Replace the contents in place.
339    #[inline]
340    pub fn set_str(&mut self, value: &str) -> Result<(), ProgramError> {
341        self.set_bytes(value.as_bytes())
342    }
343
344    /// Replace the contents in place.
345    #[inline]
346    pub fn set_bytes(&mut self, value: &[u8]) -> Result<(), ProgramError> {
347        if value.len() > N || value.len() > u16::MAX as usize {
348            return Err(ProgramError::InvalidInstructionData);
349        }
350        self.bytes = [0u8; N];
351        self.bytes[..value.len()].copy_from_slice(value);
352        self.len = value.len() as u16;
353        Ok(())
354    }
355
356    /// Clear the string without changing its capacity.
357    #[inline]
358    pub fn clear(&mut self) {
359        self.bytes = [0u8; N];
360        self.len = 0;
361    }
362
363    /// Return the initialized bytes.
364    #[inline(always)]
365    pub fn as_bytes(&self) -> &[u8] {
366        &self.bytes[..self.len as usize]
367    }
368
369    /// Return the initialized bytes as UTF-8.
370    #[inline]
371    pub fn as_str(&self) -> Result<&str, ProgramError> {
372        core::str::from_utf8(self.as_bytes()).map_err(|_| ProgramError::InvalidAccountData)
373    }
374
375    /// Number of initialized bytes.
376    #[inline(always)]
377    pub const fn len(&self) -> usize {
378        self.len as usize
379    }
380
381    /// Maximum byte capacity.
382    #[inline(always)]
383    pub const fn capacity(&self) -> usize {
384        N
385    }
386
387    /// Remaining byte capacity.
388    #[inline(always)]
389    pub const fn remaining_capacity(&self) -> usize {
390        N - self.len as usize
391    }
392
393    /// Whether the string has reached its maximum byte capacity.
394    #[inline(always)]
395    pub const fn is_full(&self) -> bool {
396        self.len as usize == N
397    }
398
399    /// Whether the string is empty.
400    #[inline(always)]
401    pub const fn is_empty(&self) -> bool {
402        self.len == 0
403    }
404}
405
406impl<const N: usize> Default for BoundedString<N> {
407    #[inline]
408    fn default() -> Self {
409        Self::empty()
410    }
411}
412
413impl<const N: usize> TailCodec for BoundedString<N> {
414    const MAX_ENCODED_LEN: usize = 2 + N;
415
416    #[inline]
417    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
418        let len = self.len as usize;
419        if len > N || out.len() < 2 + len {
420            return Err(ProgramError::AccountDataTooSmall);
421        }
422        out[..2].copy_from_slice(&self.len.to_le_bytes());
423        out[2..2 + len].copy_from_slice(&self.bytes[..len]);
424        Ok(2 + len)
425    }
426
427    #[inline]
428    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
429        if input.len() < 2 {
430            return Err(ProgramError::InvalidAccountData);
431        }
432        let len = u16::from_le_bytes([input[0], input[1]]) as usize;
433        if len > N || input.len() < 2 + len {
434            return Err(ProgramError::InvalidAccountData);
435        }
436        let mut out = Self::empty();
437        out.len = len as u16;
438        out.bytes[..len].copy_from_slice(&input[2..2 + len]);
439        Ok((out, 2 + len))
440    }
441}
442
443/// Bounded dynamic vector for Hopper dynamic tails.
444///
445/// This is the common migration target for bounded vector account metadata.
446/// Elements use `TailCodec`, so the vector can carry wire integers, addresses,
447/// or small custom structs declared with `hopper_dynamic_tail!`.
448#[derive(Clone, Copy, Eq, PartialEq)]
449pub struct BoundedVec<T, const N: usize>
450where
451    T: TailCodec + Copy + Default,
452{
453    len: u16,
454    items: [T; N],
455}
456
457impl<T, const N: usize> BoundedVec<T, N>
458where
459    T: TailCodec + Copy + Default,
460{
461    /// Construct an empty bounded vector.
462    #[inline]
463    pub fn empty() -> Self {
464        Self {
465            len: 0,
466            items: [T::default(); N],
467        }
468    }
469
470    /// Construct from a slice, rejecting values longer than `N`.
471    #[inline]
472    pub fn from_slice(values: &[T]) -> Result<Self, ProgramError> {
473        if values.len() > N || values.len() > u16::MAX as usize {
474            return Err(ProgramError::InvalidInstructionData);
475        }
476        let mut out = Self::empty();
477        out.items[..values.len()].copy_from_slice(values);
478        out.len = values.len() as u16;
479        Ok(out)
480    }
481
482    /// Push one item into the bounded vector.
483    #[inline]
484    pub fn push(&mut self, item: T) -> Result<(), ProgramError> {
485        let len = self.len as usize;
486        if len >= N || len >= u16::MAX as usize {
487            return Err(ProgramError::AccountDataTooSmall);
488        }
489        self.items[len] = item;
490        self.len += 1;
491        Ok(())
492    }
493
494    /// Pop the last initialized item, if present.
495    #[inline]
496    pub fn pop(&mut self) -> Option<T> {
497        let len = self.len as usize;
498        if len == 0 {
499            return None;
500        }
501        let new_len = len - 1;
502        let item = self.items[new_len];
503        self.items[new_len] = T::default();
504        self.len = new_len as u16;
505        Some(item)
506    }
507
508    /// Clear all initialized items without changing capacity.
509    #[inline]
510    pub fn clear(&mut self) {
511        let len = self.len as usize;
512        let mut i = 0;
513        while i < len {
514            self.items[i] = T::default();
515            i += 1;
516        }
517        self.len = 0;
518    }
519
520    /// Return the initialized items.
521    #[inline(always)]
522    pub fn as_slice(&self) -> &[T] {
523        &self.items[..self.len as usize]
524    }
525
526    /// Return the initialized items mutably.
527    #[inline(always)]
528    pub fn as_mut_slice(&mut self) -> &mut [T] {
529        &mut self.items[..self.len as usize]
530    }
531
532    /// Number of initialized items.
533    #[inline(always)]
534    pub const fn len(&self) -> usize {
535        self.len as usize
536    }
537
538    /// Maximum number of items.
539    #[inline(always)]
540    pub const fn capacity(&self) -> usize {
541        N
542    }
543
544    /// Remaining element capacity.
545    #[inline(always)]
546    pub const fn remaining_capacity(&self) -> usize {
547        N - self.len as usize
548    }
549
550    /// Whether the vector has reached its maximum element capacity.
551    #[inline(always)]
552    pub const fn is_full(&self) -> bool {
553        self.len as usize == N
554    }
555
556    /// Whether the vector is empty.
557    #[inline(always)]
558    pub const fn is_empty(&self) -> bool {
559        self.len == 0
560    }
561}
562
563impl<T, const N: usize> BoundedVec<T, N>
564where
565    T: TailCodec + Copy + Default + PartialEq,
566{
567    /// Return true when the initialized items contain `item`.
568    #[inline]
569    pub fn contains(&self, item: &T) -> bool {
570        self.as_slice().iter().any(|candidate| candidate == item)
571    }
572
573    /// Push `item` only if it is not already initialized.
574    ///
575    /// Returns `Ok(true)` when an item was inserted and `Ok(false)` when it
576    /// was already present.
577    #[inline]
578    pub fn push_unique(&mut self, item: T) -> Result<bool, ProgramError> {
579        if self.contains(&item) {
580            return Ok(false);
581        }
582        self.push(item)?;
583        Ok(true)
584    }
585
586    /// Remove the first matching item, preserving order.
587    ///
588    /// Returns `true` when an item was removed.
589    #[inline]
590    pub fn remove_first(&mut self, item: &T) -> bool {
591        let len = self.len as usize;
592        let mut found = None;
593        let mut i = 0;
594        while i < len {
595            if &self.items[i] == item {
596                found = Some(i);
597                break;
598            }
599            i += 1;
600        }
601        let Some(index) = found else {
602            return false;
603        };
604        let mut j = index;
605        while j + 1 < len {
606            self.items[j] = self.items[j + 1];
607            j += 1;
608        }
609        self.items[len - 1] = T::default();
610        self.len = (len - 1) as u16;
611        true
612    }
613}
614
615/// Short alias for bounded UTF-8 strings in dynamic tails.
616pub type HopperString<const N: usize> = BoundedString<N>;
617
618/// Short alias for bounded vectors in dynamic tails.
619pub type HopperVec<T, const N: usize> = BoundedVec<T, N>;
620
621impl<T, const N: usize> Default for BoundedVec<T, N>
622where
623    T: TailCodec + Copy + Default,
624{
625    #[inline]
626    fn default() -> Self {
627        Self::empty()
628    }
629}
630
631impl<T, const N: usize> TailCodec for BoundedVec<T, N>
632where
633    T: TailCodec + Copy + Default,
634{
635    const MAX_ENCODED_LEN: usize = 2 + (N * T::MAX_ENCODED_LEN);
636
637    #[inline]
638    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
639        let len = self.len as usize;
640        if len > N || out.len() < 2 {
641            return Err(ProgramError::AccountDataTooSmall);
642        }
643        out[..2].copy_from_slice(&self.len.to_le_bytes());
644        let mut cursor = 2;
645        for item in self.as_slice() {
646            let written = item.encode(&mut out[cursor..])?;
647            cursor = cursor
648                .checked_add(written)
649                .ok_or(ProgramError::AccountDataTooSmall)?;
650        }
651        Ok(cursor)
652    }
653
654    #[inline]
655    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
656        if input.len() < 2 {
657            return Err(ProgramError::InvalidAccountData);
658        }
659        let len = u16::from_le_bytes([input[0], input[1]]) as usize;
660        if len > N {
661            return Err(ProgramError::InvalidAccountData);
662        }
663        let mut out = Self::empty();
664        let mut cursor = 2;
665        let mut i = 0;
666        while i < len {
667            let (item, consumed) = T::decode(&input[cursor..])?;
668            out.items[i] = item;
669            cursor = cursor
670                .checked_add(consumed)
671                .ok_or(ProgramError::InvalidAccountData)?;
672            i += 1;
673        }
674        out.len = len as u16;
675        Ok((out, cursor))
676    }
677}
678
679impl TailCodec for crate::address::Address {
680    const MAX_ENCODED_LEN: usize = 32;
681
682    #[inline]
683    fn encode(&self, out: &mut [u8]) -> Result<usize, ProgramError> {
684        if out.len() < 32 {
685            return Err(ProgramError::AccountDataTooSmall);
686        }
687        out[..32].copy_from_slice(self.as_array());
688        Ok(32)
689    }
690
691    #[inline]
692    fn decode(input: &[u8]) -> Result<(Self, usize), ProgramError> {
693        if input.len() < 32 {
694            return Err(ProgramError::InvalidAccountData);
695        }
696        let mut bytes = [0u8; 32];
697        bytes.copy_from_slice(&input[..32]);
698        Ok((crate::address::Address::new(bytes), 32))
699    }
700}
701
702// ══════════════════════════════════════════════════════════════════════
703//  Seq<T>, the growable typed sequence (O(1) push, open-ended tail)
704// ══════════════════════════════════════════════════════════════════════
705//
706// A `Seq<T>` tail stores `[ count: u32 LE ][ elem_0 ][ elem_1 ] ...` where
707// every element occupies a FIXED `T::STRIDE` bytes. Because the stride is
708// constant, element `i` lives at a computable offset
709// `SEQ_LEN_PREFIX + i*STRIDE` with no scan, `push` is O(1) (write one
710// element, bump the count), and the streaming cursors below NEVER
711// materialize a `[T; N]` array; they decode/encode ONE element at a time,
712// directly over the account bytes.
713//
714// Unlike `BoundedVec<T, N>` (which owns a `[T; N]` and decodes the whole
715// tail), a `Seq<T>` carries NO compile-time capacity: the live capacity is
716// `(region_len - 4) / STRIDE`, computed from the account's current length.
717// Growing the account (via `realloc`) raises the capacity WITHOUT changing
718// the account type, the layout id is capacity-independent by design.
719//
720// Variable-stride types (`Option<T>`, `BoundedString`, `BoundedVec`) do
721// NOT implement `SeqElement` and stay on the owned-decode `BoundedVec`
722// path; only fixed-stride elements can back a `Seq`.
723
724/// Byte width of the `Seq<T>` count prefix (`u32` LE element count).
725pub const SEQ_LEN_PREFIX: usize = 4;
726
727/// A [`TailElement`] whose encoding has a **fixed stride**: every value
728/// encodes to exactly [`STRIDE`](Self::STRIDE) bytes
729/// (`STRIDE == MAX_ENCODED_LEN`, and `encode` always writes `STRIDE`).
730///
731/// This fixed width is what lets a `Seq<T>` address element `i` at
732/// `SEQ_LEN_PREFIX + i*STRIDE` with no per-element scan. Implemented for
733/// the fixed-width wire primitives (`u8..=u128`, `i16..=i128`, `bool`),
734/// `[u8; N]`, and [`Address`](crate::address::Address). Variable-length
735/// encoders (`Option<T>`, `BoundedString<N>`, `BoundedVec<T, N>`) are
736/// deliberately NOT `SeqElement`; they stay on the owned-decode path.
737pub trait SeqElement: TailElement {
738    /// Fixed on-wire stride in bytes. MUST equal the exact number of
739    /// bytes every value of this type encodes to (and decodes from).
740    const STRIDE: usize;
741}
742
743macro_rules! seq_element_fixed {
744    ( $( $ty:ty ),+ $(,)? ) => {
745        $(
746            impl SeqElement for $ty {
747                const STRIDE: usize = <$ty as TailCodec>::MAX_ENCODED_LEN;
748            }
749        )+
750    };
751}
752
753// Fixed-width wire primitives: their `MAX_ENCODED_LEN` IS their exact
754// encoded length (see the `tail_codec_int!` / `u8` / `bool` impls above).
755seq_element_fixed!(u8, u16, u32, u64, u128, i16, i32, i64, i128, bool);
756
757// `Address`: `repr(transparent)` over `[u8; 32]`, stride 32.
758impl SeqElement for crate::address::Address {
759    const STRIDE: usize = 32;
760}
761
762// NOTE: `[u8; N]` is intentionally NOT a blanket `SeqElement`. `TailElement`
763// requires `Default`, and `[T; N]` implements `Default` only for `N <= 32`
764// (there is no const-generic `Default` for arrays), so a blanket impl would
765// not type-check. Programs needing a fixed-width byte element use
766// `Address` (32 bytes) or implement `SeqElement` for their own concrete
767// `[u8; K]` (any `K <= 32`).
768
769/// Live capacity (max element count) of a `Seq<T>` tail region of
770/// `region_len` bytes: `(region_len - 4) / STRIDE`. Zero if the region
771/// cannot even hold the count prefix.
772#[inline(always)]
773pub const fn seq_capacity_for<T: SeqElement>(region_len: usize) -> usize {
774    if region_len < SEQ_LEN_PREFIX {
775        return 0;
776    }
777    (region_len - SEQ_LEN_PREFIX) / T::STRIDE
778}
779
780/// Account allocation (tail region bytes) needed for a `Seq<T>` of
781/// capacity `n`: `4 + n*STRIDE`.
782#[inline(always)]
783pub const fn seq_region_bytes_for<T: SeqElement>(n: usize) -> usize {
784    SEQ_LEN_PREFIX + n * T::STRIDE
785}
786
787// -- Read cursor ------------------------------------------------------
788
789/// Streaming **read** cursor over a `Seq<T>` tail region.
790///
791/// Borrows the tail-region bytes (`[count:u32][elems...]`) and decodes ONE
792/// element per [`get`](Self::get) / iterator step; it never builds a
793/// `[T; N]`. Cheap to construct and copy.
794#[derive(Clone, Copy)]
795pub struct TailSeq<'a, T: SeqElement> {
796    /// Tail region: `region[0..4]` is the count, elements follow.
797    region: &'a [u8],
798    _marker: PhantomData<T>,
799}
800
801impl<'a, T: SeqElement> TailSeq<'a, T> {
802    /// Overlay a read cursor on `region` (the tail bytes starting at the
803    /// count prefix). Rejects a region too small for the prefix, or a
804    /// stored count exceeding the region's capacity (corrupt tail).
805    #[inline]
806    pub fn from_region(region: &'a [u8]) -> Result<Self, ProgramError> {
807        if region.len() < SEQ_LEN_PREFIX {
808            return Err(ProgramError::AccountDataTooSmall);
809        }
810        let this = Self {
811            region,
812            _marker: PhantomData,
813        };
814        if this.len() as usize > this.capacity() {
815            return Err(ProgramError::InvalidAccountData);
816        }
817        Ok(this)
818    }
819
820    /// Number of live elements (the `u32` count prefix).
821    #[inline]
822    pub fn len(&self) -> u32 {
823        let mut bytes = [0u8; 4];
824        bytes.copy_from_slice(&self.region[..SEQ_LEN_PREFIX]);
825        u32::from_le_bytes(bytes)
826    }
827
828    /// Whether the sequence is empty.
829    #[inline]
830    pub fn is_empty(&self) -> bool {
831        self.len() == 0
832    }
833
834    /// Live element capacity from the region length: `(len - 4) / STRIDE`.
835    #[inline]
836    pub fn capacity(&self) -> usize {
837        seq_capacity_for::<T>(self.region.len())
838    }
839
840    /// Decode the element at `index` (one element, no full-tail decode).
841    #[inline]
842    pub fn get(&self, index: usize) -> Result<T, ProgramError> {
843        if index >= self.len() as usize {
844            return Err(ProgramError::InvalidArgument);
845        }
846        let start = SEQ_LEN_PREFIX + index * T::STRIDE;
847        let end = start + T::STRIDE;
848        let bytes = self
849            .region
850            .get(start..end)
851            .ok_or(ProgramError::InvalidAccountData)?;
852        let (value, _consumed) = T::decode(bytes)?;
853        Ok(value)
854    }
855
856    /// Iterate the live elements, decoding one at a time. Each item is a
857    /// `Result` because a corrupt slot can fail to decode (e.g. a `bool`
858    /// byte outside `{0, 1}`); a well-formed `Seq` yields only `Ok`.
859    #[inline]
860    pub fn iter(&self) -> TailSeqIter<'a, T> {
861        TailSeqIter {
862            region: self.region,
863            len: self.len() as usize,
864            pos: 0,
865            _marker: PhantomData,
866        }
867    }
868}
869
870/// Iterator over a [`TailSeq`], yielding one decoded element per step.
871pub struct TailSeqIter<'a, T: SeqElement> {
872    region: &'a [u8],
873    len: usize,
874    pos: usize,
875    _marker: PhantomData<T>,
876}
877
878impl<T: SeqElement> Iterator for TailSeqIter<'_, T> {
879    type Item = Result<T, ProgramError>;
880
881    #[inline]
882    fn next(&mut self) -> Option<Self::Item> {
883        if self.pos >= self.len {
884            return None;
885        }
886        let start = SEQ_LEN_PREFIX + self.pos * T::STRIDE;
887        let end = start + T::STRIDE;
888        self.pos += 1;
889        let item = match self.region.get(start..end) {
890            Some(bytes) => T::decode(bytes).map(|(value, _)| value),
891            None => Err(ProgramError::InvalidAccountData),
892        };
893        Some(item)
894    }
895
896    #[inline]
897    fn size_hint(&self) -> (usize, Option<usize>) {
898        let remaining = self.len - self.pos;
899        (remaining, Some(remaining))
900    }
901}
902
903// -- Write cursor -----------------------------------------------------
904
905/// Streaming **write** cursor over a `Seq<T>` tail region.
906///
907/// Encodes/decodes ONE element at a time directly over the account bytes;
908/// [`push`](Self::push) is O(1) (write one element at the tail, bump the
909/// count). The capacity is derived from the LIVE region length, so a tail
910/// that was grown via `realloc` can hold more elements with no type
911/// change. `push` returns [`AccountDataTooSmall`](ProgramError::AccountDataTooSmall)
912/// when the region is full, grow the account first.
913pub struct TailSeqMut<'a, T: SeqElement> {
914    /// Tail region: `region[0..4]` is the count, elements follow.
915    region: &'a mut [u8],
916    _marker: PhantomData<T>,
917}
918
919impl<'a, T: SeqElement> TailSeqMut<'a, T> {
920    /// Overlay a write cursor on `region` (the tail bytes starting at the
921    /// count prefix). Rejects a region too small for the prefix, or a
922    /// stored count exceeding the region's capacity (corrupt tail).
923    #[inline]
924    pub fn from_region(region: &'a mut [u8]) -> Result<Self, ProgramError> {
925        if region.len() < SEQ_LEN_PREFIX {
926            return Err(ProgramError::AccountDataTooSmall);
927        }
928        let this = Self {
929            region,
930            _marker: PhantomData,
931        };
932        if this.len() as usize > this.capacity() {
933            return Err(ProgramError::InvalidAccountData);
934        }
935        Ok(this)
936    }
937
938    /// Number of live elements (the `u32` count prefix).
939    #[inline]
940    pub fn len(&self) -> u32 {
941        let mut bytes = [0u8; 4];
942        bytes.copy_from_slice(&self.region[..SEQ_LEN_PREFIX]);
943        u32::from_le_bytes(bytes)
944    }
945
946    /// Whether the sequence is empty.
947    #[inline]
948    pub fn is_empty(&self) -> bool {
949        self.len() == 0
950    }
951
952    /// Live element capacity from the region length: `(len - 4) / STRIDE`.
953    #[inline]
954    pub fn capacity(&self) -> usize {
955        seq_capacity_for::<T>(self.region.len())
956    }
957
958    /// Whether the sequence has reached its live capacity.
959    #[inline]
960    pub fn is_full(&self) -> bool {
961        self.len() as usize >= self.capacity()
962    }
963
964    /// Remaining element slots before a grow is required.
965    #[inline]
966    pub fn remaining_capacity(&self) -> usize {
967        self.capacity().saturating_sub(self.len() as usize)
968    }
969
970    /// Decode the element at `index` (one element, no full-tail decode).
971    #[inline]
972    pub fn get(&self, index: usize) -> Result<T, ProgramError> {
973        if index >= self.len() as usize {
974            return Err(ProgramError::InvalidArgument);
975        }
976        let start = SEQ_LEN_PREFIX + index * T::STRIDE;
977        let end = start + T::STRIDE;
978        let bytes = self
979            .region
980            .get(start..end)
981            .ok_or(ProgramError::InvalidAccountData)?;
982        let (value, _consumed) = T::decode(bytes)?;
983        Ok(value)
984    }
985
986    /// Overwrite the element at `index` in place. `index` must be `<
987    /// len`.
988    #[inline]
989    pub fn set(&mut self, index: usize, value: T) -> Result<(), ProgramError> {
990        if index >= self.len() as usize {
991            return Err(ProgramError::InvalidArgument);
992        }
993        let start = SEQ_LEN_PREFIX + index * T::STRIDE;
994        let end = start + T::STRIDE;
995        let slot = self
996            .region
997            .get_mut(start..end)
998            .ok_or(ProgramError::AccountDataTooSmall)?;
999        let _ = value.encode(slot)?;
1000        Ok(())
1001    }
1002
1003    /// Append `value` at the tail and bump the count, O(1). Returns
1004    /// [`AccountDataTooSmall`](ProgramError::AccountDataTooSmall) when the
1005    /// region is at capacity (grow the account, then push again).
1006    #[inline]
1007    pub fn push(&mut self, value: T) -> Result<(), ProgramError> {
1008        let len = self.len() as usize;
1009        if len >= self.capacity() {
1010            return Err(ProgramError::AccountDataTooSmall);
1011        }
1012        let start = SEQ_LEN_PREFIX + len * T::STRIDE;
1013        let end = start + T::STRIDE;
1014        let slot = self
1015            .region
1016            .get_mut(start..end)
1017            .ok_or(ProgramError::AccountDataTooSmall)?;
1018        let _ = value.encode(slot)?;
1019        // Bump the count LAST, so a mid-encode failure above leaves the
1020        // stored length pointing only at fully-written elements.
1021        let new_len = (len as u32) + 1;
1022        self.region[..SEQ_LEN_PREFIX].copy_from_slice(&new_len.to_le_bytes());
1023        Ok(())
1024    }
1025
1026    /// Remove the element at `index`, moving the last element into its
1027    /// slot (O(1), does not preserve order). Returns the removed value.
1028    #[inline]
1029    pub fn swap_remove(&mut self, index: usize) -> Result<T, ProgramError> {
1030        let len = self.len() as usize;
1031        if index >= len {
1032            return Err(ProgramError::InvalidArgument);
1033        }
1034        let removed = self.get(index)?;
1035        let last = len - 1;
1036        let last_start = SEQ_LEN_PREFIX + last * T::STRIDE;
1037        if index != last {
1038            let idx_start = SEQ_LEN_PREFIX + index * T::STRIDE;
1039            // Disjoint ranges (index != last), so copy_within is well
1040            // defined; it moves the last element's bytes over the removed
1041            // slot.
1042            self.region
1043                .copy_within(last_start..last_start + T::STRIDE, idx_start);
1044        }
1045        // Zero the now-unused final slot so stale bytes never masquerade
1046        // as a live element after a later grow.
1047        if let Some(tail) = self.region.get_mut(last_start..last_start + T::STRIDE) {
1048            for byte in tail.iter_mut() {
1049                *byte = 0;
1050            }
1051        }
1052        self.region[..SEQ_LEN_PREFIX].copy_from_slice(&(last as u32).to_le_bytes());
1053        Ok(removed)
1054    }
1055
1056    /// Borrow this cursor as a read cursor (shared reborrow).
1057    #[inline]
1058    pub fn as_seq(&self) -> TailSeq<'_, T> {
1059        TailSeq {
1060            region: self.region,
1061            _marker: PhantomData,
1062        }
1063    }
1064}
1065
1066// -- Borrow-registry-integrated guards --------------------------------
1067//
1068// A `TailSeq`/`TailSeqMut` cursor borrows the tail bytes, but under a
1069// `Context` those bytes come from an account borrow that must be held
1070// (and a segment-registry lease that must stay registered) for as long as
1071// the cursor lives. These guards OWN the account byte borrow (narrowed to
1072// the tail region) plus the one segment lease covering the tail range, and
1073// hand out the cursor via `seq()` / `seq_mut()`, the same
1074// guard-owns-the-borrow, method-yields-the-view shape as
1075// [`SegmentsMut`](crate::SegmentsMut). Dropping a guard releases both the
1076// byte borrow and the single registry lease. `Context::tail_seq_ref` /
1077// `Context::tail_seq_mut` construct them.
1078
1079/// Read guard over a `Seq<T>` tail acquired through a `Context`: owns the
1080/// shared account byte borrow (narrowed to the tail region) and the
1081/// segment-registry lease, yielding a [`TailSeq`] cursor via
1082/// [`seq`](Self::seq).
1083pub struct SeqTailRead<'a, T: SeqElement> {
1084    region: Ref<'a, [u8]>,
1085    _lease: SegmentLease<'a>,
1086    _marker: PhantomData<T>,
1087}
1088
1089impl<'a, T: SeqElement> SeqTailRead<'a, T> {
1090    /// Assemble from a region-narrowed shared byte borrow and its lease.
1091    ///
1092    /// `#[doc(hidden)]` cross-crate constructor; user code reaches for
1093    /// `Context::tail_seq_ref` / the generated `<field>()` accessor.
1094    #[doc(hidden)]
1095    #[inline]
1096    pub fn new(region: Ref<'a, [u8]>, lease: SegmentLease<'a>) -> Self {
1097        Self {
1098            region,
1099            _lease: lease,
1100            _marker: PhantomData,
1101        }
1102    }
1103
1104    /// Borrow the tail as a streaming read cursor.
1105    #[inline]
1106    pub fn seq(&self) -> Result<TailSeq<'_, T>, ProgramError> {
1107        TailSeq::from_region(&self.region)
1108    }
1109}
1110
1111/// Write guard over a `Seq<T>` tail acquired through a `Context`: owns the
1112/// exclusive account byte borrow (narrowed to the tail region) and the
1113/// segment-registry lease, yielding a [`TailSeqMut`] cursor via
1114/// [`seq_mut`](Self::seq_mut).
1115pub struct SeqTailWrite<'a, T: SeqElement> {
1116    region: RefMut<'a, [u8]>,
1117    _lease: SegmentLease<'a>,
1118    _marker: PhantomData<T>,
1119}
1120
1121impl<'a, T: SeqElement> SeqTailWrite<'a, T> {
1122    /// Assemble from a region-narrowed exclusive byte borrow and its
1123    /// lease.
1124    ///
1125    /// `#[doc(hidden)]` cross-crate constructor; user code reaches for
1126    /// `Context::tail_seq_mut` / the generated `<field>_mut()` accessor.
1127    #[doc(hidden)]
1128    #[inline]
1129    pub fn new(region: RefMut<'a, [u8]>, lease: SegmentLease<'a>) -> Self {
1130        Self {
1131            region,
1132            _lease: lease,
1133            _marker: PhantomData,
1134        }
1135    }
1136
1137    /// Borrow the tail as a streaming write cursor (O(1) `push`, `set`,
1138    /// `swap_remove`, capacity from the live region length).
1139    #[inline]
1140    pub fn seq_mut(&mut self) -> Result<TailSeqMut<'_, T>, ProgramError> {
1141        TailSeqMut::from_region(&mut self.region)
1142    }
1143
1144    /// Borrow the tail as a streaming read cursor.
1145    #[inline]
1146    pub fn seq(&self) -> Result<TailSeq<'_, T>, ProgramError> {
1147        TailSeq::from_region(&self.region)
1148    }
1149}
1150
1151// ── Framework helpers used by `#[hopper::state(dynamic_tail = T)]` ──
1152
1153/// Read the tail's u32-LE length prefix.
1154///
1155/// `body_end` is the byte offset immediately after the layout's fixed
1156/// body (i.e. `TYPE_OFFSET + WIRE_SIZE` for a layout with no header
1157/// beyond the 16-byte Hopper prefix, otherwise `HEADER_LEN +
1158/// WIRE_SIZE`). Returns `AccountDataTooSmall` if the account has
1159/// fewer than 4 tail bytes available.
1160#[inline]
1161pub fn read_tail_len(data: &[u8], body_end: usize) -> Result<u32, ProgramError> {
1162    let end = body_end
1163        .checked_add(4)
1164        .ok_or(ProgramError::AccountDataTooSmall)?;
1165    if data.len() < end {
1166        return Err(ProgramError::AccountDataTooSmall);
1167    }
1168    let mut bytes = [0u8; 4];
1169    bytes.copy_from_slice(&data[body_end..end]);
1170    Ok(u32::from_le_bytes(bytes))
1171}
1172
1173/// Return a slice referencing just the tail payload bytes (excluding
1174/// the 4-byte length prefix). Length-bounded by the u32 prefix.
1175#[inline]
1176pub fn tail_payload(data: &[u8], body_end: usize) -> Result<&[u8], ProgramError> {
1177    let len = read_tail_len(data, body_end)? as usize;
1178    let start = body_end + 4;
1179    let end = start
1180        .checked_add(len)
1181        .ok_or(ProgramError::InvalidAccountData)?;
1182    if data.len() < end {
1183        return Err(ProgramError::InvalidAccountData);
1184    }
1185    Ok(&data[start..end])
1186}
1187
1188/// Return the account bytes available after the tail length prefix.
1189///
1190/// This is useful before a grow/realloc path: if the encoded payload to write
1191/// is larger than this value, the caller must resize the account before
1192/// calling `write_tail`.
1193#[inline]
1194pub fn tail_capacity(data: &[u8], body_end: usize) -> Result<usize, ProgramError> {
1195    let start = body_end
1196        .checked_add(4)
1197        .ok_or(ProgramError::AccountDataTooSmall)?;
1198    if data.len() < start {
1199        return Err(ProgramError::AccountDataTooSmall);
1200    }
1201    Ok(data.len() - start)
1202}
1203
1204/// Borrow one bounded UTF-8 string from a compact dynamic-tail payload.
1205///
1206/// The returned `usize` is the number of bytes consumed from `input`, so a
1207/// generated view can walk subsequent compact-tail fields without decoding the
1208/// whole tail into an owned value.
1209#[inline]
1210pub fn borrow_bounded_str<const N: usize>(input: &[u8]) -> Result<(&str, usize), ProgramError> {
1211    if input.len() < 2 {
1212        return Err(ProgramError::InvalidAccountData);
1213    }
1214    let len = u16::from_le_bytes([input[0], input[1]]) as usize;
1215    if len > N || input.len() < 2 + len {
1216        return Err(ProgramError::InvalidAccountData);
1217    }
1218    let bytes = &input[2..2 + len];
1219    let value = core::str::from_utf8(bytes).map_err(|_| ProgramError::InvalidAccountData)?;
1220    Ok((value, 2 + len))
1221}
1222
1223/// Borrow one bounded address vector from a compact dynamic-tail payload.
1224///
1225/// This is the zero-copy read path for the common multisig/authority-list case
1226/// that Quasar represents as `Vec<'a, Address, N>`. `Address` is
1227/// `repr(transparent)` over `[u8; 32]` and alignment-1, so the slice cast is
1228/// layout-safe after the length and capacity checks below.
1229#[inline]
1230pub fn borrow_address_slice<const N: usize>(
1231    input: &[u8],
1232) -> Result<(&[crate::address::Address], usize), ProgramError> {
1233    if input.len() < 2 {
1234        return Err(ProgramError::InvalidAccountData);
1235    }
1236    let len = u16::from_le_bytes([input[0], input[1]]) as usize;
1237    if len > N {
1238        return Err(ProgramError::InvalidAccountData);
1239    }
1240    let byte_len = len
1241        .checked_mul(32)
1242        .ok_or(ProgramError::InvalidAccountData)?;
1243    let end = 2usize
1244        .checked_add(byte_len)
1245        .ok_or(ProgramError::InvalidAccountData)?;
1246    if input.len() < end {
1247        return Err(ProgramError::InvalidAccountData);
1248    }
1249    let bytes = &input[2..end];
1250    let ptr = bytes.as_ptr() as *const crate::address::Address;
1251    // SAFETY: Address has alignment 1 and is transparent over [u8; 32]. The
1252    // byte range length is exactly len * 32, checked above.
1253    let values = unsafe { core::slice::from_raw_parts(ptr, len) };
1254    Ok((values, end))
1255}
1256
1257/// Decode the tail as `T: TailCodec`, checking that the encoded length
1258/// exactly matches the u32 prefix. Extra bytes beyond `T`'s decode
1259/// are a malformed-encoding signal.
1260#[inline]
1261pub fn read_tail<T: TailCodec>(data: &[u8], body_end: usize) -> Result<T, ProgramError> {
1262    let payload = tail_payload(data, body_end)?;
1263    let (value, consumed) = T::decode(payload)?;
1264    if consumed != payload.len() {
1265        return Err(ProgramError::InvalidAccountData);
1266    }
1267    Ok(value)
1268}
1269
1270/// Encode `tail` into the account's tail slot, rewriting the u32
1271/// length prefix. Returns `AccountDataTooSmall` when the existing
1272/// account byte buffer can't fit the encoded payload. in that case
1273/// the caller should `realloc` first.
1274#[inline]
1275pub fn write_tail<T: TailCodec>(
1276    data: &mut [u8],
1277    body_end: usize,
1278    tail: &T,
1279) -> Result<usize, ProgramError> {
1280    let prefix_end = body_end
1281        .checked_add(4)
1282        .ok_or(ProgramError::AccountDataTooSmall)?;
1283    if data.len() < prefix_end {
1284        return Err(ProgramError::AccountDataTooSmall);
1285    }
1286    let written = tail.encode(&mut data[prefix_end..])?;
1287    if written > u32::MAX as usize {
1288        return Err(ProgramError::InvalidAccountData);
1289    }
1290    data[body_end..prefix_end].copy_from_slice(&(written as u32).to_le_bytes());
1291    Ok(written)
1292}
1293
1294/// Write an already-encoded dynamic-tail payload.
1295///
1296/// This is used by generated bare-final-tail accounts: bounded fields are
1297/// encoded first, then `TailStr` / `TailBytes` contributes the remaining raw
1298/// payload without its own field-level length prefix.
1299#[inline]
1300pub fn write_tail_payload(
1301    data: &mut [u8],
1302    body_end: usize,
1303    payload: &[u8],
1304) -> Result<usize, ProgramError> {
1305    let prefix_end = body_end
1306        .checked_add(4)
1307        .ok_or(ProgramError::AccountDataTooSmall)?;
1308    let payload_end = prefix_end
1309        .checked_add(payload.len())
1310        .ok_or(ProgramError::AccountDataTooSmall)?;
1311    if data.len() < payload_end || payload.len() > u32::MAX as usize {
1312        return Err(ProgramError::AccountDataTooSmall);
1313    }
1314    data[prefix_end..payload_end].copy_from_slice(payload);
1315    data[body_end..prefix_end].copy_from_slice(&(payload.len() as u32).to_le_bytes());
1316    Ok(payload.len())
1317}
1318
1319#[cfg(test)]
1320mod tests {
1321    use super::*;
1322
1323    #[test]
1324    fn u32_roundtrip() {
1325        let mut buf = [0u8; 8];
1326        let n = 0xDEAD_BEEFu32.encode(&mut buf).unwrap();
1327        assert_eq!(n, 4);
1328        let (back, consumed) = u32::decode(&buf).unwrap();
1329        assert_eq!(consumed, 4);
1330        assert_eq!(back, 0xDEAD_BEEF);
1331    }
1332
1333    #[test]
1334    fn u64_roundtrip() {
1335        let mut buf = [0u8; 8];
1336        0x0123_4567_89AB_CDEFu64.encode(&mut buf).unwrap();
1337        let (back, _) = u64::decode(&buf).unwrap();
1338        assert_eq!(back, 0x0123_4567_89AB_CDEF);
1339    }
1340
1341    #[test]
1342    fn bool_encode_decode() {
1343        let mut buf = [0u8; 1];
1344        true.encode(&mut buf).unwrap();
1345        assert_eq!(buf[0], 1);
1346        assert_eq!(bool::decode(&buf).unwrap(), (true, 1));
1347        false.encode(&mut buf).unwrap();
1348        assert_eq!(buf[0], 0);
1349        assert_eq!(bool::decode(&buf).unwrap(), (false, 1));
1350    }
1351
1352    #[test]
1353    fn bool_rejects_garbage() {
1354        let buf = [2u8];
1355        assert!(bool::decode(&buf).is_err());
1356    }
1357
1358    #[test]
1359    fn byte_array_roundtrip() {
1360        let src: [u8; 8] = *b"HOPPER!!";
1361        let mut buf = [0u8; 16];
1362        let n = src.encode(&mut buf).unwrap();
1363        assert_eq!(n, 8);
1364        let (back, consumed) = <[u8; 8]>::decode(&buf).unwrap();
1365        assert_eq!(consumed, 8);
1366        assert_eq!(back, src);
1367    }
1368
1369    #[test]
1370    fn option_none_encodes_to_one_byte() {
1371        let mut buf = [0u8; 16];
1372        let n = Option::<u64>::None.encode(&mut buf).unwrap();
1373        assert_eq!(n, 1);
1374        assert_eq!(buf[0], 0);
1375        let (back, c) = <Option<u64>>::decode(&buf).unwrap();
1376        assert_eq!(back, None);
1377        assert_eq!(c, 1);
1378    }
1379
1380    #[test]
1381    fn option_some_includes_inner_payload() {
1382        let mut buf = [0u8; 16];
1383        let n = Option::<u64>::Some(0xAAAA_BBBB_CCCC_DDDD)
1384            .encode(&mut buf)
1385            .unwrap();
1386        assert_eq!(n, 9);
1387        assert_eq!(buf[0], 1);
1388        let (back, c) = <Option<u64>>::decode(&buf).unwrap();
1389        assert_eq!(back, Some(0xAAAA_BBBB_CCCC_DDDD));
1390        assert_eq!(c, 9);
1391    }
1392
1393    #[test]
1394    fn option_rejects_invalid_tag() {
1395        let buf = [7u8, 0, 0, 0, 0, 0, 0, 0, 0];
1396        assert!(<Option<u64>>::decode(&buf).is_err());
1397    }
1398
1399    #[test]
1400    fn tail_length_prefix_roundtrip() {
1401        // Simulate an account body: 16-byte "header" + 8-byte body +
1402        // 4-byte length prefix + tail bytes. body_end = 24.
1403        let mut data = [0u8; 64];
1404        let body_end = 24usize;
1405        let tail_value: u64 = 0x1234_5678_9ABC_DEF0;
1406        let written = write_tail(&mut data, body_end, &tail_value).unwrap();
1407        assert_eq!(written, 8);
1408        let read_len = read_tail_len(&data, body_end).unwrap();
1409        assert_eq!(read_len, 8);
1410        let back: u64 = read_tail::<u64>(&data, body_end).unwrap();
1411        assert_eq!(back, tail_value);
1412    }
1413
1414    #[test]
1415    fn tail_decode_rejects_excess_payload() {
1416        // If the tail encodes as 4 bytes but the length prefix claims
1417        // 8, the decode must refuse rather than silently succeed.
1418        let mut data = [0u8; 32];
1419        // body_end = 16; prefix says 8 bytes; payload is u32 (4 bytes) +
1420        // garbage (4 bytes). Decoding as u32 leaves 4 bytes unconsumed
1421        // which is caught by `read_tail`.
1422        let body_end = 16usize;
1423        data[body_end..body_end + 4].copy_from_slice(&8u32.to_le_bytes());
1424        // Fill payload with something that decodes as u32=0x11223344
1425        // and then trailing garbage.
1426        data[body_end + 4..body_end + 8].copy_from_slice(&0x1122_3344u32.to_le_bytes());
1427        data[body_end + 8..body_end + 12].copy_from_slice(&0xFFu32.to_le_bytes());
1428        // u32 decodes 4 bytes but prefix claims 8. expect error.
1429        let result = read_tail::<u32>(&data, body_end);
1430        assert!(result.is_err());
1431    }
1432
1433    #[test]
1434    fn tail_bounds_check_on_short_buffer() {
1435        let data = [0u8; 10];
1436        assert!(read_tail_len(&data, 16).is_err());
1437        assert!(tail_payload(&data, 16).is_err());
1438    }
1439
1440    #[test]
1441    fn max_encoded_len_matches_actual_encode_size() {
1442        let mut buf = [0u8; 32];
1443        assert_eq!(0u32.encode(&mut buf).unwrap(), u32::MAX_ENCODED_LEN);
1444        assert_eq!(0u64.encode(&mut buf).unwrap(), u64::MAX_ENCODED_LEN);
1445        assert_eq!(true.encode(&mut buf).unwrap(), bool::MAX_ENCODED_LEN);
1446        assert_eq!(
1447            [0u8; 7].encode(&mut buf).unwrap(),
1448            <[u8; 7]>::MAX_ENCODED_LEN
1449        );
1450        assert_eq!(Option::<u32>::None.encode(&mut buf).unwrap(), 1);
1451        assert_eq!(
1452            Option::<u32>::Some(0).encode(&mut buf).unwrap(),
1453            <Option<u32>>::MAX_ENCODED_LEN
1454        );
1455    }
1456
1457    #[test]
1458    fn bounded_string_roundtrip() {
1459        let label = BoundedString::<32>::from_str("multisig").unwrap();
1460        let mut buf = [0u8; BoundedString::<32>::MAX_ENCODED_LEN];
1461        let written = label.encode(&mut buf).unwrap();
1462        assert_eq!(written, 10);
1463        let (back, consumed) = BoundedString::<32>::decode(&buf).unwrap();
1464        assert_eq!(consumed, written);
1465        assert_eq!(back.as_str().unwrap(), "multisig");
1466    }
1467
1468    #[test]
1469    fn bounded_string_capacity_helpers() {
1470        let mut label = HopperString::<8>::from_str("ops").unwrap();
1471        assert_eq!(label.remaining_capacity(), 5);
1472        assert!(!label.is_full());
1473        label.set_str("12345678").unwrap();
1474        assert!(label.is_full());
1475        label.clear();
1476        assert!(label.is_empty());
1477        assert_eq!(label.as_bytes(), b"");
1478    }
1479
1480    #[test]
1481    fn bounded_vec_roundtrip() {
1482        let mut vec = BoundedVec::<u64, 4>::empty();
1483        vec.push(7).unwrap();
1484        vec.push(9).unwrap();
1485        let mut buf = [0u8; BoundedVec::<u64, 4>::MAX_ENCODED_LEN];
1486        let written = vec.encode(&mut buf).unwrap();
1487        assert_eq!(written, 18);
1488        let (back, consumed) = BoundedVec::<u64, 4>::decode(&buf).unwrap();
1489        assert_eq!(consumed, written);
1490        assert_eq!(back.as_slice(), &[7, 9]);
1491    }
1492
1493    #[test]
1494    fn bounded_vec_set_helpers_preserve_order() {
1495        let mut vec = HopperVec::<u64, 4>::empty();
1496        assert_eq!(vec.remaining_capacity(), 4);
1497        assert!(vec.push_unique(7).unwrap());
1498        assert!(!vec.push_unique(7).unwrap());
1499        vec.push(9).unwrap();
1500        vec.push(11).unwrap();
1501        assert!(vec.contains(&9));
1502        assert!(vec.remove_first(&9));
1503        assert_eq!(vec.as_slice(), &[7, 11]);
1504        assert_eq!(vec.pop(), Some(11));
1505        assert_eq!(vec.as_slice(), &[7]);
1506        vec.clear();
1507        assert!(vec.is_empty());
1508    }
1509
1510    // ── Seq<T> streaming cursors ─────────────────────────────────────
1511
1512    use crate::address::Address;
1513
1514    #[test]
1515    fn seq_element_stride_matches_encoded_len() {
1516        assert_eq!(<u8 as SeqElement>::STRIDE, 1);
1517        assert_eq!(<u16 as SeqElement>::STRIDE, 2);
1518        assert_eq!(<u32 as SeqElement>::STRIDE, 4);
1519        assert_eq!(<u64 as SeqElement>::STRIDE, 8);
1520        assert_eq!(<bool as SeqElement>::STRIDE, 1);
1521        assert_eq!(<Address as SeqElement>::STRIDE, 32);
1522    }
1523
1524    #[test]
1525    fn seq_capacity_is_region_derived() {
1526        // 4-byte prefix + room for elements; capacity = (len - 4) / STRIDE.
1527        assert_eq!(seq_capacity_for::<u64>(4 + 8 * 3), 3);
1528        assert_eq!(seq_capacity_for::<u64>(4 + 8 * 3 + 5), 3); // partial slot ignored
1529        assert_eq!(seq_capacity_for::<u64>(4), 0);
1530        assert_eq!(seq_capacity_for::<u64>(0), 0); // too small for prefix
1531        assert_eq!(seq_region_bytes_for::<Address>(10), 4 + 32 * 10);
1532    }
1533
1534    #[test]
1535    fn seq_push_get_iter_roundtrip() {
1536        // Region for 4 u64 elements.
1537        let mut region = [0u8; 4 + 8 * 4];
1538        let mut seq = TailSeqMut::<u64>::from_region(&mut region).unwrap();
1539        assert_eq!(seq.capacity(), 4);
1540        assert_eq!(seq.len(), 0);
1541        assert!(seq.is_empty());
1542        assert_eq!(seq.remaining_capacity(), 4);
1543
1544        seq.push(10).unwrap();
1545        seq.push(20).unwrap();
1546        seq.push(30).unwrap();
1547        assert_eq!(seq.len(), 3);
1548        assert_eq!(seq.remaining_capacity(), 1);
1549        assert_eq!(seq.get(0).unwrap(), 10);
1550        assert_eq!(seq.get(2).unwrap(), 30);
1551        assert!(seq.get(3).is_err()); // out of live range
1552
1553        // set overwrites in place.
1554        seq.set(1, 99).unwrap();
1555        assert_eq!(seq.get(1).unwrap(), 99);
1556        assert!(seq.set(3, 1).is_err()); // index >= len
1557
1558        // A read cursor over the same bytes sees the same elements.
1559        let read = TailSeq::<u64>::from_region(&region).unwrap();
1560        assert_eq!(read.len(), 3);
1561        let collected: Result<std::vec::Vec<u64>, _> = read.iter().collect();
1562        assert_eq!(collected.unwrap(), std::vec![10, 99, 30]);
1563    }
1564
1565    #[test]
1566    fn seq_push_at_capacity_is_account_data_too_small() {
1567        let mut region = [0u8; 4 + 8 * 2]; // capacity 2
1568        let mut seq = TailSeqMut::<u64>::from_region(&mut region).unwrap();
1569        seq.push(1).unwrap();
1570        seq.push(2).unwrap();
1571        assert!(seq.is_full());
1572        assert_eq!(seq.remaining_capacity(), 0);
1573        assert_eq!(seq.push(3), Err(ProgramError::AccountDataTooSmall));
1574        // The rejected push left the count and bytes untouched.
1575        assert_eq!(seq.len(), 2);
1576        assert_eq!(seq.get(0).unwrap(), 1);
1577        assert_eq!(seq.get(1).unwrap(), 2);
1578    }
1579
1580    #[test]
1581    fn seq_swap_remove_moves_last_into_hole() {
1582        let mut region = [0u8; 4 + 8 * 4];
1583        let mut seq = TailSeqMut::<u64>::from_region(&mut region).unwrap();
1584        for v in [10u64, 20, 30, 40] {
1585            seq.push(v).unwrap();
1586        }
1587        // Remove the middle: last (40) fills the hole, order not preserved.
1588        assert_eq!(seq.swap_remove(1).unwrap(), 20);
1589        assert_eq!(seq.len(), 3);
1590        assert_eq!(seq.get(0).unwrap(), 10);
1591        assert_eq!(seq.get(1).unwrap(), 40);
1592        assert_eq!(seq.get(2).unwrap(), 30);
1593        // Removing the last element is a pure truncation.
1594        assert_eq!(seq.swap_remove(2).unwrap(), 30);
1595        assert_eq!(seq.len(), 2);
1596        // The freed slot was zeroed, and pushing reuses it cleanly.
1597        seq.push(50).unwrap();
1598        assert_eq!(seq.get(2).unwrap(), 50);
1599        assert!(seq.swap_remove(9).is_err());
1600    }
1601
1602    #[test]
1603    fn seq_of_addresses_roundtrips() {
1604        let mut region = [0u8; 4 + 32 * 3];
1605        let mut seq = TailSeqMut::<Address>::from_region(&mut region).unwrap();
1606        let a = Address::new([7u8; 32]);
1607        let b = Address::new([9u8; 32]);
1608        seq.push(a).unwrap();
1609        seq.push(b).unwrap();
1610        assert_eq!(seq.get(0).unwrap(), a);
1611        assert_eq!(seq.get(1).unwrap(), b);
1612        assert_eq!(seq.capacity(), 3);
1613    }
1614
1615    #[test]
1616    fn seq_from_region_rejects_corrupt_count_and_tiny_region() {
1617        // Region too small for the 4-byte prefix.
1618        let mut tiny = [0u8; 2];
1619        assert!(TailSeqMut::<u64>::from_region(&mut tiny).is_err());
1620        // Stored count exceeds capacity -> corrupt.
1621        let mut region = [0u8; 4 + 8 * 2];
1622        region[..4].copy_from_slice(&9999u32.to_le_bytes());
1623        assert!(TailSeqMut::<u64>::from_region(&mut region).is_err());
1624        assert!(TailSeq::<u64>::from_region(&region).is_err());
1625    }
1626}
1627
1628#[cfg(kani)]
1629mod kani_proofs {
1630    use super::*;
1631
1632    #[kani::proof]
1633    fn bounded_string_decode_never_exceeds_capacity() {
1634        let len: u16 = kani::any();
1635        let mut input = [0u8; BoundedString::<4>::MAX_ENCODED_LEN];
1636        input[..2].copy_from_slice(&len.to_le_bytes());
1637
1638        let result = BoundedString::<4>::decode(&input);
1639        if len as usize > 4 {
1640            assert!(result.is_err());
1641        } else {
1642            let (decoded, consumed) = result.unwrap();
1643            assert!(decoded.len() <= decoded.capacity());
1644            assert_eq!(consumed, 2 + decoded.len());
1645        }
1646    }
1647
1648    #[kani::proof]
1649    fn bounded_vec_mutators_preserve_capacity() {
1650        let values: [u8; 5] = kani::any();
1651        let mut vec = BoundedVec::<u8, 4>::empty();
1652
1653        let _ = vec.push(values[0]);
1654        let _ = vec.push(values[1]);
1655        let _ = vec.push(values[2]);
1656        let _ = vec.push(values[3]);
1657        let fifth = vec.push(values[4]);
1658
1659        assert!(vec.len() <= vec.capacity());
1660        assert!(fifth.is_err());
1661        let _ = vec.pop();
1662        assert!(vec.len() <= vec.capacity());
1663        vec.clear();
1664        assert_eq!(vec.len(), 0);
1665    }
1666
1667    #[kani::proof]
1668    fn tail_payload_bounds_checks_arbitrary_prefixes() {
1669        let data: [u8; 16] = kani::any();
1670        let body_end: usize = kani::any();
1671        kani::assume(body_end < data.len());
1672
1673        let result = tail_payload(&data, body_end);
1674        if let Ok(payload) = result {
1675            assert!(payload.len() <= data.len());
1676        }
1677    }
1678}