Skip to main content

hopper_runtime/
account.rs

1//! Hopper-owned account view for Solana programs.
2//!
3//! `AccountView` is the canonical typed state gateway for Hopper programs.
4//! It wraps Hopper Native's account representation behind a transparent
5//! representation boundary and delegates account operations to that layer.
6//!
7//! Key capabilities:
8//! - Chainable validation (`check_signer()?.check_writable()?`)
9//! - Whole-layout typed access (`load::<T>()`, `load_mut::<T>()`)
10//! - Segment-aware typed access (`segment_ref`, `segment_mut`)
11//! - Explicit raw escape hatches (`raw_ref`, `raw_mut`)
12//! - Hopper header reading (disc, version, layout_id)
13//! - Packed flags for batch validation
14//! - Remaining accounts iterator
15
16use crate::address::{address_eq, Address};
17use crate::borrow::{Ref, RefMut};
18use crate::borrow_registry::{self, BorrowToken};
19use crate::error::ProgramError;
20use crate::field_map::FieldInfo;
21use crate::layout::LayoutContract;
22use crate::native_boundary::{self, BackendAccountView};
23use crate::segment_borrow::SegmentBorrowRegistry;
24use crate::ProgramResult;
25
26/// Release the first `count` registered borrows during a
27/// `split_segments_mut` rollback.
28///
29/// # Safety
30///
31/// The first `count` entries of `recs` must be initialized `SegmentBorrow`
32/// records registered in `reg`.
33#[inline]
34unsafe fn release_registered<const N: usize>(
35    reg: &mut SegmentBorrowRegistry,
36    recs: &[core::mem::MaybeUninit<crate::segment_borrow::SegmentBorrow>; N],
37    count: usize,
38) {
39    let mut j = 0;
40    while j < count {
41        // SAFETY: caller guarantees `recs[j]` is an initialized, registered borrow.
42        unsafe {
43            reg.release(recs[j].assume_init_ref());
44        }
45        j += 1;
46    }
47}
48
49// ══════════════════════════════════════════════════════════════════════
50//  AccountView -- Hopper's canonical typed state gateway
51// ══════════════════════════════════════════════════════════════════════
52
53/// Zero-copy view over a Solana account.
54///
55/// `AccountView` is the single canonical type for account access in
56/// Hopper programs. It wraps Hopper Native's account representation and
57/// exposes a Hopper-owned API surface.
58///
59/// The `#[repr(transparent)]` layout guarantees that `&[native::AccountView]`
60/// can be safely reinterpreted as `&[AccountView]` at the entrypoint
61/// boundary with zero conversion cost.
62#[repr(transparent)]
63pub struct AccountView<'info> {
64    inner: BackendAccountView<'info>,
65}
66
67const _: () = {
68    assert!(
69        core::mem::size_of::<AccountView<'static>>()
70            == core::mem::size_of::<BackendAccountView<'static>>()
71    );
72    assert!(
73        core::mem::align_of::<AccountView<'static>>()
74            == core::mem::align_of::<BackendAccountView<'static>>()
75    );
76    assert!(!core::mem::needs_drop::<AccountView<'static>>());
77};
78
79// SAFETY: On Solana execution is single-threaded. Host tools and fuzzers
80// should not rely on cross-thread sharing of raw account pointers.
81#[cfg(target_os = "solana")]
82unsafe impl<'info> Send for AccountView<'info> {}
83#[cfg(target_os = "solana")]
84unsafe impl<'info> Sync for AccountView<'info> {}
85
86impl<'info> Clone for AccountView<'info> {
87    #[inline(always)]
88    fn clone(&self) -> Self {
89        Self::from_inner(self.backend().clone())
90    }
91}
92
93impl<'info> PartialEq for AccountView<'info> {
94    #[inline(always)]
95    fn eq(&self, other: &Self) -> bool {
96        self.backend() == other.backend()
97    }
98}
99
100impl<'info> Eq for AccountView<'info> {}
101
102impl<'info> AccountView<'info> {
103    // Crate-visible: the lazy bridge (`crate::lazy`) wraps substrate
104    // views it receives one at a time from the native parser.
105    #[inline(always)]
106    pub(crate) fn from_inner(inner: BackendAccountView<'info>) -> Self {
107        Self { inner }
108    }
109
110    #[inline(always)]
111    fn backend(&self) -> &BackendAccountView<'info> {
112        &self.inner
113    }
114
115    #[cfg(test)]
116    #[inline(always)]
117    pub(crate) fn from_backend(inner: BackendAccountView<'info>) -> Self {
118        Self::from_inner(inner)
119    }
120
121    // ── Getters ──────────────────────────────────────────────────────
122
123    /// The account's public key.
124    #[inline(always)]
125    pub fn address(&self) -> &Address {
126        native_boundary::account_address(self.backend())
127    }
128
129    /// The owning program's address.
130    ///
131    /// # Safety
132    ///
133    /// The returned reference is invalidated if the account is assigned
134    /// to a new owner. The caller must ensure no concurrent mutation.
135    #[inline(always)]
136    pub unsafe fn owner(&self) -> &Address {
137        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
138        unsafe { native_boundary::account_owner(self.backend()) }
139    }
140
141    /// Read the owner address as a copy (safe, no aliasing hazard).
142    #[inline(always)]
143    pub fn read_owner(&self) -> Address {
144        native_boundary::read_owner(self.backend())
145    }
146
147    /// Whether this account is owned by the given program.
148    #[inline(always)]
149    pub fn owned_by(&self, program: &Address) -> bool {
150        native_boundary::owned_by(self.backend(), program)
151    }
152
153    /// Whether this account signed the transaction.
154    #[inline(always)]
155    pub fn is_signer(&self) -> bool {
156        self.backend().is_signer()
157    }
158
159    /// Whether this account is writable in the transaction.
160    #[inline(always)]
161    pub fn is_writable(&self) -> bool {
162        self.backend().is_writable()
163    }
164
165    /// Whether this account contains an executable program.
166    #[inline(always)]
167    pub fn executable(&self) -> bool {
168        self.backend().executable()
169    }
170
171    /// Current data length in bytes.
172    #[inline(always)]
173    pub fn data_len(&self) -> usize {
174        self.backend().data_len()
175    }
176
177    /// Current lamport balance.
178    #[inline(always)]
179    pub fn lamports(&self) -> u64 {
180        self.backend().lamports()
181    }
182
183    /// Whether the account data is empty.
184    #[inline(always)]
185    pub fn is_data_empty(&self) -> bool {
186        self.data_len() == 0
187    }
188
189    /// Try to set the lamport balance.
190    ///
191    /// Backends such as `solana-program` enforce lamport borrow rules at
192    /// runtime. Use this in framework code so borrow conflicts return a
193    /// `ProgramError` instead of panicking.
194    #[inline(always)]
195    pub fn try_set_lamports(&self, lamports: u64) -> ProgramResult {
196        native_boundary::try_set_lamports(self.backend(), lamports)
197    }
198
199    /// Set the lamport balance.
200    #[inline(always)]
201    pub fn set_lamports(&self, lamports: u64) -> ProgramResult {
202        self.try_set_lamports(lamports)
203    }
204
205    // ── Borrow tracking ─────────────────────────────────────────────
206
207    /// Try to obtain a shared borrow of the account data.
208    #[inline(always)]
209    pub fn try_borrow(&self) -> Result<Ref<'_, [u8]>, ProgramError> {
210        let token = BorrowToken::shared(self.address())?;
211        match self.backend().try_borrow() {
212            Ok(data) => Ok(Ref::from_backend(data, token)),
213            Err(error) => {
214                drop(token);
215                Err(ProgramError::from(error))
216            }
217        }
218    }
219
220    /// Try to obtain an exclusive (mutable) borrow of the account data.
221    ///
222    /// Touch-map note: this RAW byte surface does not stamp the touch
223    /// log, segment leases route their exclusive borrows through here
224    /// and would smear every narrow lease into a whole-account record,
225    /// destroying the map's field precision. The TYPED whole-account
226    /// surfaces ([`load_mut`](Self::load_mut) /
227    /// [`load_compact_mut`](Self::load_compact_mut)) record instead.
228    ///
229    /// Ambient-gate note: under a bound `strict_writes` context this raw
230    /// whole-account write borrow is governed, the instruction-ambient
231    /// gate refuses it unless the declared policy covers the full data
232    /// range, closing the historical "raw borrow bypasses the write
233    /// policy" surface. With no gate installed the check is one load and
234    /// branch. Segment leases use the crate-internal ungated variant
235    /// because they gate the exact range themselves; the migration crank
236    /// uses it under its own `check_migratable` authorization (a
237    /// whole-layout transform, distinct from the byte-range gate; see the
238    /// crate-private `try_borrow_mut_ungated` helper.
239    #[inline(always)]
240    pub fn try_borrow_mut(&self) -> Result<RefMut<'_, [u8]>, ProgramError> {
241        let len = self.data_len();
242        if len > 0 {
243            crate::write_policy::check_data_mutation(self.address(), 0, len as u32)?;
244        }
245        self.try_borrow_mut_ungated()
246    }
247
248    /// Ungated exclusive borrow: the borrow-registry token and backend
249    /// borrow WITHOUT the instruction-ambient write-gate check. Only for
250    /// crate-internal plumbing whose caller supplies its OWN
251    /// authorization before delegating:
252    ///
253    /// - Segment leases gate the exact requested range against the
254    ///   installed byte-range policy, then take the ungated borrow.
255    /// - The migration crank ([`crate::migrate`]) does not consult the
256    ///   byte-range gate at all, a layout migration rewrites the whole
257    ///   body by construction, which no byte-range policy would permit.
258    ///   It is governed instead by its own `check_migratable`
259    ///   authorization (the account must be writable and owned by the
260    ///   executing program) run before this borrow. That is a DISTINCT
261    ///   authorization from the `strict_writes` gate, not "the same
262    ///   installed policy": a strict handler that also calls
263    ///   `hopper::migration::*` is explicitly invoking a whole-layout
264    ///   transform, not smuggling a byte write past its own declaration.
265    ///
266    /// Never expose publicly: doing so would reopen the raw bypass the
267    /// gated [`try_borrow_mut`](Self::try_borrow_mut) split closes.
268    #[inline(always)]
269    pub(crate) fn try_borrow_mut_ungated(&self) -> Result<RefMut<'_, [u8]>, ProgramError> {
270        let token = BorrowToken::mutable(self.address())?;
271        match self.backend().try_borrow_mut() {
272            Ok(data) => Ok(RefMut::from_backend(data, token)),
273            Err(error) => {
274                drop(token);
275                Err(ProgramError::from(error))
276            }
277        }
278    }
279
280    // ── Segment-aware access ───────────────────────────────────────
281
282    /// Project a typed segment from this account with segment-level
283    /// borrow tracking.
284    ///
285    /// The runtime validates the requested byte range, registers a
286    /// **leased** read borrow in the provided instruction-scoped
287    /// registry, and returns a [`SegRef<T>`](crate::SegRef) that
288    /// releases the lease on drop. This replaces the earlier
289    /// "instruction-sticky" behaviour: the registry entry is now tied
290    /// to the returned guard's lifetime, so sequential patterns like
291    /// `let x = segment_ref…; drop(x); let y = segment_ref…;` work
292    /// exactly the way Rust callers expect.
293    ///
294    /// On the native backend (Solana), the inner `Ref<T>` uses the
295    /// flat `{ptr, state}` representation, no dummy slice guard,
296    /// no intermediate `Ref<[u8]>`.
297    ///
298    /// The explicit `'a` lifetime binds the returned `SegRef<'a, T>`
299    /// to the shorter of `&self` (the account) and `&mut borrows`
300    /// (the registry). Either outliving the other would let the guard
301    /// dangle.
302    #[inline(always)]
303    pub fn segment_ref<'a, T: crate::Pod>(
304        &'a self,
305        borrows: &'a mut SegmentBorrowRegistry,
306        abs_offset: u32,
307        size: u32,
308    ) -> Result<crate::SegRef<'a, T>, ProgramError> {
309        let expected_size = core::mem::size_of::<T>() as u32;
310        if size != expected_size {
311            return ProgramError::err_invalid_argument();
312        }
313
314        let end = abs_offset
315            .checked_add(size)
316            .ok_or(ProgramError::ArithmeticOverflow)?;
317        if end as usize > self.data_len() {
318            return ProgramError::err_data_too_small();
319        }
320
321        let borrow = borrows.register_leased_read(self.address(), abs_offset, size)?;
322
323        // Build the inner `Ref<T>` via the existing flat/projected path.
324        #[cfg(target_os = "solana")]
325        let inner: Ref<'_, T> = {
326            // SAFETY: size, overflow, and bounds already validated above.
327            let native_ref = unsafe { self.backend().segment_ref_unchecked::<T>(abs_offset) };
328            let native_ref = match native_ref {
329                Ok(nr) => nr,
330                Err(e) => {
331                    // Native guard could not be taken; undo the lease
332                    // we just registered so the instruction-level view
333                    // stays consistent.
334                    borrows.release(&borrow);
335                    return Err(ProgramError::from(e));
336                }
337            };
338            let (typed_ref, state_ptr) = native_ref.into_raw_parts();
339            Ref::from_segment(typed_ref as *const T, state_ptr)
340        };
341        #[cfg(not(target_os = "solana"))]
342        let inner: Ref<'_, T> = {
343            let data = match self.try_borrow() {
344                Ok(d) => d,
345                Err(e) => {
346                    borrows.release(&borrow);
347                    return Err(e);
348                }
349            };
350            // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
351            let ptr = unsafe { data.as_bytes_ptr().add(abs_offset as usize) as *const T };
352            unsafe { data.project(ptr) }
353        };
354
355        // SAFETY: `borrow` was just registered in `borrows`; the
356        // lease we construct will swap-remove it on drop.
357        let lease = unsafe { crate::SegmentLease::new(borrows, borrow) };
358        Ok(crate::SegRef::new(inner, lease))
359    }
360
361    /// Project a mutable typed segment. Mirror of [`Self::segment_ref`]; the
362    /// returned [`SegRefMut<T>`](crate::SegRefMut) carries both the
363    /// account-level exclusive borrow guard and the segment-registry
364    /// lease, so dropping it is a full release, no lingering entries.
365    ///
366    /// Under a bound `strict_writes` context the instruction-ambient gate
367    /// checks this EXACT byte range against the declared write policy, so
368    /// direct segment access outside a `Context` is governed too (the
369    /// `Context` methods enforce the same installed policy before
370    /// delegating to the ungated internal variant, paying the check once).
371    #[inline(always)]
372    pub fn segment_mut<'a, T: crate::Pod>(
373        &'a self,
374        borrows: &'a mut SegmentBorrowRegistry,
375        abs_offset: u32,
376        size: u32,
377    ) -> Result<crate::SegRefMut<'a, T>, ProgramError> {
378        crate::write_policy::check_data_mutation(self.address(), abs_offset, size)?;
379        self.segment_mut_ungated::<T>(borrows, abs_offset, size)
380    }
381
382    /// Ungated mirror of [`segment_mut`](Self::segment_mut) for
383    /// crate-internal callers (`Context`) that already enforced the same
384    /// installed policy for this exact range. See
385    /// [`try_borrow_mut_ungated`](Self::try_borrow_mut_ungated).
386    #[inline(always)]
387    pub(crate) fn segment_mut_ungated<'a, T: crate::Pod>(
388        &'a self,
389        borrows: &'a mut SegmentBorrowRegistry,
390        abs_offset: u32,
391        size: u32,
392    ) -> Result<crate::SegRefMut<'a, T>, ProgramError> {
393        self.check_writable()?;
394
395        let expected_size = core::mem::size_of::<T>() as u32;
396        if size != expected_size {
397            return ProgramError::err_invalid_argument();
398        }
399
400        let end = abs_offset
401            .checked_add(size)
402            .ok_or(ProgramError::ArithmeticOverflow)?;
403        if end as usize > self.data_len() {
404            return ProgramError::err_data_too_small();
405        }
406
407        let borrow = borrows.register_leased_write(self.address(), abs_offset, size)?;
408
409        #[cfg(target_os = "solana")]
410        let inner: RefMut<'_, T> = {
411            // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
412            let native_ref = unsafe { self.backend().segment_mut_unchecked::<T>(abs_offset) };
413            let native_ref = match native_ref {
414                Ok(nr) => nr,
415                Err(e) => {
416                    borrows.release(&borrow);
417                    return Err(ProgramError::from(e));
418                }
419            };
420            let (typed_ref, state_ptr) = native_ref.into_raw_parts();
421            RefMut::from_segment(typed_ref as *mut T, state_ptr)
422        };
423        #[cfg(not(target_os = "solana"))]
424        let inner: RefMut<'_, T> = {
425            let mut data = match self.try_borrow_mut_ungated() {
426                Ok(d) => d,
427                Err(e) => {
428                    borrows.release(&borrow);
429                    return Err(e);
430                }
431            };
432            // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
433            let ptr = unsafe { data.as_bytes_mut_ptr().add(abs_offset as usize) as *mut T };
434            unsafe { data.project(ptr) }
435        };
436
437        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
438        let lease = unsafe { crate::SegmentLease::new(borrows, borrow) };
439        Ok(crate::SegRefMut::new(inner, lease))
440    }
441
442    /// Borrow **several disjoint byte ranges of one account** as
443    /// independent typed `&mut` guards at the same time.
444    ///
445    /// This is the ergonomic answer to "I need mutable access to two
446    /// fields of the same account simultaneously". A single
447    /// `segment_mut` call exclusively borrows the registry for the
448    /// returned guard's lifetime, so two `segment_mut` calls cannot
449    /// coexist. `split_segments_mut` registers **all** `N` ranges up
450    /// front, proving pairwise disjointness once through the borrow
451    /// registry, and returns an array of `N` guards that live together
452    /// and each release their lease on drop.
453    ///
454    /// Every range is `(abs_offset, size)` where `size == size_of::<T>()`.
455    /// Overlapping ranges are rejected with `AccountBorrowFailed`; an
456    /// out-of-bounds or wrong-size range is rejected with
457    /// `InvalidArgument` / `AccountDataTooSmall`, and any already-claimed
458    /// leases from the batch are rolled back before returning.
459    ///
460    /// ```ignore
461    /// // Mutate balance and nonce of the same vault at once.
462    /// let [mut bal, mut nonce] =
463    ///     vault.split_segments_mut::<WireU64, 2>(ctx.borrows_mut(),
464    ///         [(BALANCE_OFF, 8), (NONCE_OFF, 8)])?;
465    /// bal.set(bal.get() + amount);
466    /// nonce.set(nonce.get() + 1);
467    /// ```
468    pub fn split_segments_mut<'a, T: crate::Pod, const N: usize>(
469        &'a self,
470        borrows: &'a mut SegmentBorrowRegistry,
471        ranges: [(u32, u32); N],
472    ) -> Result<crate::SegmentsMut<'a, T, N>, ProgramError> {
473        // Under a bound `strict_writes` context, every requested range is
474        // checked against the instruction-ambient write gate, the same
475        // exact-range rule as `segment_mut`; so the batch surface cannot
476        // be used to bypass the declared policy from outside a `Context`.
477        for (off, size) in ranges {
478            crate::write_policy::check_data_mutation(self.address(), off, size)?;
479        }
480        self.split_segments_mut_ungated::<T, N>(borrows, ranges)
481    }
482
483    /// Ungated mirror of [`split_segments_mut`](Self::split_segments_mut)
484    /// for crate-internal callers (`Context`) that already enforced the
485    /// installed policy per range. See
486    /// [`try_borrow_mut_ungated`](Self::try_borrow_mut_ungated).
487    pub(crate) fn split_segments_mut_ungated<'a, T: crate::Pod, const N: usize>(
488        &'a self,
489        borrows: &'a mut SegmentBorrowRegistry,
490        ranges: [(u32, u32); N],
491    ) -> Result<crate::SegmentsMut<'a, T, N>, ProgramError> {
492        self.check_writable()?;
493        let expected = core::mem::size_of::<T>() as u32;
494        let data_len = self.data_len();
495
496        // Phase 1: validate + register every range **through the `&mut`**.
497        // The raw registry pointer the leases share is deliberately derived
498        // only after the final `&mut` use below: deriving it first and then
499        // using `borrows` would invalidate the raw under Stacked Borrows,
500        // leaving the rollback paths and every lease drop writing through a
501        // dead pointer. `register_leased_write` rejects a range that overlaps
502        // one already registered in this batch, so disjointness is proven
503        // here, once, up front.
504        // SAFETY: an array of `MaybeUninit` is itself always valid
505        // uninitialized; we initialize entries `0..i` before reading them.
506        let mut recs: [core::mem::MaybeUninit<crate::segment_borrow::SegmentBorrow>; N] =
507            unsafe { core::mem::MaybeUninit::uninit().assume_init() };
508        let mut offsets = [0usize; N];
509        let mut i = 0;
510        while i < N {
511            let (off, size) = ranges[i];
512            let in_bounds = match off.checked_add(size) {
513                Some(end) => end as usize <= data_len,
514                None => false,
515            };
516            if size != expected || !in_bounds {
517                // SAFETY: indices `0..i` were initialized and registered above.
518                unsafe { release_registered(borrows, &recs, i) };
519                return if size != expected {
520                    ProgramError::err_invalid_argument()
521                } else {
522                    ProgramError::err_data_too_small()
523                };
524            }
525            match borrows.register_leased_write(self.address(), off, size) {
526                Ok(b) => {
527                    recs[i] = core::mem::MaybeUninit::new(b);
528                    offsets[i] = off as usize;
529                }
530                Err(e) => {
531                    // SAFETY: indices `0..i` were initialized and registered.
532                    unsafe { release_registered(borrows, &recs, i) };
533                    return Err(e);
534                }
535            }
536            i += 1;
537        }
538
539        // One exclusive byte borrow of the whole account backs every
540        // typed view; the registry leases prove the ranges are disjoint,
541        // so handing out N `&mut T` from this single borrow is sound.
542        // Ungated: the per-range ambient checks already ran (public
543        // wrapper) or the Context enforced the policy per range.
544        let data = match self.try_borrow_mut_ungated() {
545            Ok(d) => d,
546            Err(e) => {
547                // SAFETY: all N entries were registered in phase 1.
548                unsafe { release_registered(borrows, &recs, N) };
549                return Err(e);
550            }
551        };
552
553        // LAST use of the `&mut`: derive the single raw pointer every lease
554        // shares. All registry access from here on (lease drops) flows
555        // through copies of this one derivation, so the pointer's provenance
556        // stays valid for the guard's whole lifetime.
557        let reg_ptr = borrows as *mut SegmentBorrowRegistry;
558
559        // Build the N leases (each shares the one registry raw pointer,
560        // lifetime-pinned to `'a` by the `&'a mut borrows` we hold).
561        // SAFETY: array of `MaybeUninit` is valid uninitialized.
562        let mut leases: [core::mem::MaybeUninit<crate::SegmentLease<'a>>; N] =
563            unsafe { core::mem::MaybeUninit::uninit().assume_init() };
564        let mut k = 0;
565        while k < N {
566            // SAFETY: `recs[k]` was initialized in phase 1; `reg_ptr` is
567            // borrowed `&'a mut` for the returned guard's lifetime.
568            let lease = unsafe { crate::SegmentLease::from_raw(reg_ptr, recs[k].assume_init()) };
569            leases[k] = core::mem::MaybeUninit::new(lease);
570            k += 1;
571        }
572        // SAFETY: all N lease slots initialized.
573        let leases = unsafe {
574            let out = core::ptr::read(&leases as *const _ as *const [crate::SegmentLease<'a>; N]);
575            // The `MaybeUninit` array does not drop its contents; the forget
576            // documents that ownership moved into `out` via the read above.
577            #[allow(clippy::forget_non_drop)]
578            core::mem::forget(leases);
579            out
580        };
581
582        Ok(crate::SegmentsMut::new(data, offsets, leases))
583    }
584
585    // ── Const-driven segment access ─────────────────────────────────
586
587    /// Project a typed segment described by a compile-time [`crate::Segment`].
588    ///
589    /// This is the "const-driven" access form the Hopper design demands:
590    /// the offset and size come from a `const SEG: Segment = ...;`
591    /// declaration generated by `#[hopper::state]` or written by hand,
592    /// so the call collapses to a single `ptr + const_offset` add on
593    /// Solana SBF. No runtime string lookup, no dynamic map, no search.
594    ///
595    /// `segment.offset` is the **absolute** offset from the start of
596    /// account data (i.e. past the Hopper header already folded in).
597    /// Construct it via `Segment::new(offset, size)` or
598    /// `Segment::body(body_offset, size)`, the latter adds
599    /// `HopperHeader::SIZE` for you.
600    ///
601    /// ```ignore
602    /// const BALANCE: Segment = Segment::body(0, 8);
603    /// let mut balance = vault.segment_ref_const::<u64>(&mut borrows, BALANCE)?;
604    /// ```
605    #[inline(always)]
606    pub fn segment_ref_const<'a, T: crate::Pod>(
607        &'a self,
608        borrows: &'a mut SegmentBorrowRegistry,
609        segment: crate::segment::Segment,
610    ) -> Result<crate::SegRef<'a, T>, ProgramError> {
611        self.segment_ref::<T>(borrows, segment.offset, segment.size)
612    }
613
614    /// Mutable const-Segment access. See [`Self::segment_ref_const`] for the
615    /// contract, this is the exclusive variant.
616    #[inline(always)]
617    pub fn segment_mut_const<'a, T: crate::Pod>(
618        &'a self,
619        borrows: &'a mut SegmentBorrowRegistry,
620        segment: crate::segment::Segment,
621    ) -> Result<crate::SegRefMut<'a, T>, ProgramError> {
622        self.segment_mut::<T>(borrows, segment.offset, segment.size)
623    }
624
625    /// Project a typed segment described by a [`crate::TypedSegment`].
626    ///
627    /// This is the tightest form of segment access Hopper exposes: both
628    /// the type `T` and the offset are compile-time constants baked
629    /// into the [`crate::TypedSegment`] marker, so the call collapses to a
630    /// single `ptr + literal_offset` add with a literal size in the
631    /// bounds check. The marker argument is a zero-sized token, free
632    /// to pass around.
633    ///
634    /// ```ignore
635    /// const BALANCE: TypedSegment<WireU64, { HopperHeader::SIZE as u32 }>
636    ///     = TypedSegment::new();
637    /// let bal = vault.segment_ref_typed(&mut borrows, BALANCE)?;
638    /// ```
639    #[inline(always)]
640    pub fn segment_ref_typed<'a, T: crate::Pod, const OFFSET: u32>(
641        &'a self,
642        borrows: &'a mut SegmentBorrowRegistry,
643        _segment: crate::segment::TypedSegment<T, OFFSET>,
644    ) -> Result<crate::SegRef<'a, T>, ProgramError> {
645        self.segment_ref::<T>(borrows, OFFSET, core::mem::size_of::<T>() as u32)
646    }
647
648    /// Mutable typed-segment access. See [`Self::segment_ref_typed`] for the
649    /// contract, this is the exclusive variant.
650    #[inline(always)]
651    pub fn segment_mut_typed<'a, T: crate::Pod, const OFFSET: u32>(
652        &'a self,
653        borrows: &'a mut SegmentBorrowRegistry,
654        _segment: crate::segment::TypedSegment<T, OFFSET>,
655    ) -> Result<crate::SegRefMut<'a, T>, ProgramError> {
656        self.segment_mut::<T>(borrows, OFFSET, core::mem::size_of::<T>() as u32)
657    }
658
659    // ── Zero-copy overlay access ─────────────────────────────────────
660
661    // ── Typed load (LayoutContract-aware) ────────────────────────────
662
663    /// Load a typed layout after validating the account header.
664    ///
665    /// This is the canonical "validate then project" path:
666    /// 1. Check disc, version, and layout_id match `T`
667    /// 2. Verify data length >= `T::SIZE`
668    /// 3. Return zero-copy reference into account data
669    ///
670    /// The returned reference begins at `T::TYPE_OFFSET`. Body-only layouts
671    /// project past the Hopper header; header-inclusive layouts project the
672    /// full account struct from byte 0.
673    ///
674    /// # Example
675    ///
676    /// ```ignore
677    /// let vault = account.load::<Vault>()?;
678    /// ```
679    #[inline(always)]
680    pub fn load<T: LayoutContract + crate::Pod>(&self) -> Result<Ref<'_, T>, ProgramError> {
681        let data = self.try_borrow()?;
682        T::validate_header(&data)?;
683        if data.len() < T::required_len() {
684            return ProgramError::err_data_too_small();
685        }
686        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
687        let ptr = unsafe { data.as_bytes_ptr().add(T::TYPE_OFFSET) as *const T };
688        // SAFETY: Header and length validated above. `ptr` points into the borrowed bytes.
689        Ok(unsafe { data.project(ptr) })
690    }
691
692    /// Borrow a typed layout for the duration of a closure.
693    ///
694    /// This is the ergonomic safe path for read-only handlers: Hopper still
695    /// validates the header and holds the data borrow guard, while user code
696    /// gets a plain `&T` inside the closure.
697    #[inline]
698    pub fn with<T, R, F>(&self, f: F) -> Result<R, ProgramError>
699    where
700        T: LayoutContract + crate::Pod,
701        F: FnOnce(&T) -> Result<R, ProgramError>,
702    {
703        let account = self.load::<T>()?;
704        f(&*account)
705    }
706
707    /// Load a mutable typed layout after validating the account header.
708    ///
709    /// Same as `load()` but provides a mutable reference for in-place
710    /// state updates. Changes write directly to account data.
711    ///
712    /// # Example
713    ///
714    /// ```ignore
715    /// let mut vault = account.load_mut::<Vault>()?;
716    /// vault.balance = vault.balance.checked_add(amount)?;
717    /// ```
718    #[inline(always)]
719    pub fn load_mut<T: LayoutContract + crate::Pod>(&self) -> Result<RefMut<'_, T>, ProgramError> {
720        let mut data = self.try_borrow_mut()?;
721        T::validate_header(&data)?;
722        if data.len() < T::required_len() {
723            return ProgramError::err_data_too_small();
724        }
725        // Typed whole-account write borrows stamp the instruction-
726        // AMBIENT touch log directly (no Context in reach here), which
727        // is what makes wrapper `get_mut` / raw `load_mut` visible to
728        // emitted touch maps. Footprint only, liveness stays with the
729        // account borrow byte. Reads are not recorded (validators read
730        // every account; the map's job is write containment).
731        #[cfg(feature = "touch-map")]
732        crate::segment_borrow::touch_log::record_account(
733            self.address(),
734            data.len() as u32,
735            crate::segment_borrow::AccessKind::Write,
736        );
737        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
738        let ptr = unsafe { data.as_bytes_mut_ptr().add(T::TYPE_OFFSET) as *mut T };
739        // SAFETY: Header and length validated above. `ptr` points into the borrowed bytes.
740        Ok(unsafe { data.project(ptr) })
741    }
742
743    /// Mutably borrow a typed layout for the duration of a closure.
744    ///
745    /// This keeps the zero-copy borrow guard scoped to the closure while making
746    /// common updates read like direct state mutation.
747    #[inline]
748    pub fn with_mut<T, R, F>(&self, f: F) -> Result<R, ProgramError>
749    where
750        T: LayoutContract + crate::Pod,
751        F: FnOnce(&mut T) -> Result<R, ProgramError>,
752    {
753        let mut account = self.load_mut::<T>()?;
754        f(&mut *account)
755    }
756
757    // ── Tier 1 compact load (`[disc:u8][body]`) ─────────────────────
758
759    /// Load a Tier-1 compact layout: `[disc:u8][zero-copy body]`.
760    ///
761    /// The hot path is `check_len_exact` + `check_disc` + project-body-at-byte-1.
762    /// Unlike [`load`](Self::load) there is **no** 16-byte header, no
763    /// layout_id read, and no schema-epoch comparison. Layout identity is
764    /// a program-level fact (the Tier-2 registry), not a per-account one.
765    ///
766    /// # Example
767    ///
768    /// ```ignore
769    /// let vault = account.load_compact::<Vault>()?;
770    /// ```
771    #[inline(always)]
772    pub fn load_compact<T: crate::CompactLayout>(&self) -> Result<Ref<'_, T>, ProgramError> {
773        let data = self.try_borrow()?;
774        T::validate_compact(&data)?;
775        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
776        let ptr =
777            unsafe { data.as_bytes_ptr().add(crate::compact::COMPACT_BODY_OFFSET) as *const T };
778        // SAFETY: length and disc validated above; `ptr` points into the borrowed body.
779        Ok(unsafe { data.project(ptr) })
780    }
781
782    /// Mutable Tier-1 compact load. See [`load_compact`](Self::load_compact).
783    #[inline(always)]
784    pub fn load_compact_mut<T: crate::CompactLayout>(&self) -> Result<RefMut<'_, T>, ProgramError> {
785        let mut data = self.try_borrow_mut()?;
786        T::validate_compact(&data)?;
787        // Same ambient stamp as `load_mut`: typed whole-account write.
788        #[cfg(feature = "touch-map")]
789        crate::segment_borrow::touch_log::record_account(
790            self.address(),
791            data.len() as u32,
792            crate::segment_borrow::AccessKind::Write,
793        );
794        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
795        let ptr = unsafe {
796            data.as_bytes_mut_ptr()
797                .add(crate::compact::COMPACT_BODY_OFFSET) as *mut T
798        };
799        // SAFETY: length and disc validated above; `ptr` points into the borrowed body.
800        Ok(unsafe { data.project(ptr) })
801    }
802
803    /// Borrow a compact layout for the duration of a closure (read-only).
804    #[inline]
805    pub fn with_compact<T, R, F>(&self, f: F) -> Result<R, ProgramError>
806    where
807        T: crate::CompactLayout,
808        F: FnOnce(&T) -> Result<R, ProgramError>,
809    {
810        let account = self.load_compact::<T>()?;
811        f(&*account)
812    }
813
814    /// Mutably borrow a compact layout for the duration of a closure.
815    #[inline]
816    pub fn with_compact_mut<T, R, F>(&self, f: F) -> Result<R, ProgramError>
817    where
818        T: crate::CompactLayout,
819        F: FnOnce(&mut T) -> Result<R, ProgramError>,
820    {
821        let mut account = self.load_compact_mut::<T>()?;
822        f(&mut *account)
823    }
824
825    /// Initialise a compact account by stamping the discriminator byte.
826    ///
827    /// Writes `T::DISC` at byte 0; the body is left as-is (callers
828    /// typically follow with [`load_compact_mut`](Self::load_compact_mut)
829    /// to populate it). Requires the account to be writable and exactly
830    /// `T::COMPACT_LEN` bytes long.
831    #[inline(always)]
832    pub fn init_compact<T: crate::CompactLayout>(&self) -> ProgramResult {
833        self.check_writable()?;
834        let mut data = self.try_borrow_mut()?;
835        if data.len() < T::COMPACT_LEN {
836            return Err(ProgramError::AccountDataTooSmall);
837        }
838        if data.len() != T::COMPACT_LEN {
839            return Err(ProgramError::InvalidAccountData);
840        }
841        data[0] = T::DISC;
842        Ok(())
843    }
844
845    /// Tier-1 compact **dynamic** load: validate the discriminator and the
846    /// minimum length, then project the fixed head at
847    /// [`COMPACT_BODY_OFFSET`](crate::compact::COMPACT_BODY_OFFSET).
848    ///
849    /// Unlike [`load_compact`](Self::load_compact), the account may be longer
850    /// than the fixed head: the trailing bytes are the dynamic tail, left
851    /// untouched here and accessed through the generated `tail_*` helpers.
852    /// This is the `[disc:u8][fixed_head][tail]` analogue of
853    /// [`load`](Self::load)'s tolerance of a headered dynamic tail.
854    ///
855    /// # Example
856    ///
857    /// ```ignore
858    /// let head = account.load_compact_dynamic::<Market>()?;   // fixed head
859    /// let data = account.try_borrow()?;
860    /// let tail = Market::tail_read(&data)?;                   // dynamic tail
861    /// ```
862    #[inline(always)]
863    pub fn load_compact_dynamic<T: crate::CompactDynamicLayout>(
864        &self,
865    ) -> Result<Ref<'_, T>, ProgramError> {
866        let data = self.try_borrow()?;
867        T::validate_compact_dynamic(&data)?;
868        // SAFETY: validate_compact_dynamic guarantees `data.len() >= 1 +
869        // size_of::<T>()`, `T` is Pod (align 1, all-bit-patterns valid), and
870        // the fixed head begins at COMPACT_BODY_OFFSET. Trailing tail bytes are
871        // never read through this `&T`.
872        let ptr =
873            unsafe { data.as_bytes_ptr().add(crate::compact::COMPACT_BODY_OFFSET) as *const T };
874        // SAFETY: length and disc validated above; `ptr` points into the borrowed head.
875        Ok(unsafe { data.project(ptr) })
876    }
877
878    /// Mutable Tier-1 compact-dynamic load of the fixed head.
879    /// See [`load_compact_dynamic`](Self::load_compact_dynamic).
880    #[inline(always)]
881    pub fn load_compact_dynamic_mut<T: crate::CompactDynamicLayout>(
882        &self,
883    ) -> Result<RefMut<'_, T>, ProgramError> {
884        let mut data = self.try_borrow_mut()?;
885        T::validate_compact_dynamic(&data)?;
886        // SAFETY: see `load_compact_dynamic`; the head window is exclusively
887        // borrowed for the lifetime of the returned guard.
888        let ptr = unsafe {
889            data.as_bytes_mut_ptr()
890                .add(crate::compact::COMPACT_BODY_OFFSET) as *mut T
891        };
892        // SAFETY: length and disc validated above; `ptr` points into the borrowed head.
893        Ok(unsafe { data.project(ptr) })
894    }
895
896    /// Borrow a compact-dynamic fixed head for the duration of a closure.
897    #[inline]
898    pub fn with_compact_dynamic<T, R, F>(&self, f: F) -> Result<R, ProgramError>
899    where
900        T: crate::CompactDynamicLayout,
901        F: FnOnce(&T) -> Result<R, ProgramError>,
902    {
903        let account = self.load_compact_dynamic::<T>()?;
904        f(&*account)
905    }
906
907    /// Mutably borrow a compact-dynamic fixed head for the duration of a closure.
908    #[inline]
909    pub fn with_compact_dynamic_mut<T, R, F>(&self, f: F) -> Result<R, ProgramError>
910    where
911        T: crate::CompactDynamicLayout,
912        F: FnOnce(&mut T) -> Result<R, ProgramError>,
913    {
914        let mut account = self.load_compact_dynamic_mut::<T>()?;
915        f(&mut *account)
916    }
917
918    /// Initialise a compact-dynamic account: stamp `T::DISC` at byte 0 and, if
919    /// the account was allocated with room for a tail, zero the tail's `u32`
920    /// length prefix so a fresh account reads as an **empty** tail rather than
921    /// uninitialized bytes (fail-closed init).
922    ///
923    /// Requires the account to be writable and at least `T::MIN_LEN` bytes
924    /// (discriminator + fixed head). The tail region may be larger to reserve
925    /// growth headroom.
926    #[inline(always)]
927    pub fn init_compact_dynamic<T: crate::CompactDynamicLayout>(&self) -> ProgramResult {
928        self.check_writable()?;
929        let mut data = self.try_borrow_mut()?;
930        if data.len() < T::MIN_LEN {
931            return Err(ProgramError::AccountDataTooSmall);
932        }
933        data[0] = T::DISC;
934        // Stamp an empty-tail length prefix when the allocation has room for it.
935        if data.len() >= T::TAIL_OFFSET + 4 {
936            data[T::TAIL_OFFSET..T::TAIL_OFFSET + 4].copy_from_slice(&0u32.to_le_bytes());
937        }
938        Ok(())
939    }
940
941    /// Explicit raw typed read of the account buffer.
942    ///
943    /// This bypasses Hopper layout validation and segment tracking, but it still
944    /// respects the account-level borrow rules enforced by `try_borrow()`.
945    #[inline(always)]
946    ///
947    /// # Safety
948    ///
949    /// Caller must uphold the invariants documented for this unsafe API before invoking it.
950    pub unsafe fn raw_ref<T: crate::Pod>(&self) -> Result<Ref<'_, T>, ProgramError> {
951        let data = self.try_borrow()?;
952        if core::mem::size_of::<T>() > data.len() {
953            return Err(ProgramError::AccountDataTooSmall);
954        }
955        let ptr = data.as_ptr() as *const T;
956        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
957        Ok(unsafe { data.project(ptr) })
958    }
959
960    /// Explicit raw typed write of the account buffer.
961    ///
962    /// This bypasses Hopper layout validation and segment tracking, but it still
963    /// enforces writability and the account-level exclusive borrow rules.
964    #[inline(always)]
965    ///
966    /// # Safety
967    ///
968    /// Caller must uphold the invariants documented for this unsafe API before invoking it.
969    pub unsafe fn raw_mut<T: crate::Pod>(&self) -> Result<RefMut<'_, T>, ProgramError> {
970        self.check_writable()?;
971        // Deliberately ungated: `raw_mut` is one of the documented `unsafe`
972        // escape hatches (`hopper lint --deny-escapes` refuses it in program
973        // code). The ambient write gate governs the SAFE surfaces; the
974        // unsafe tier remains an explicit, grep-able opt-out.
975        let mut data = self.try_borrow_mut_ungated()?;
976        if core::mem::size_of::<T>() > data.len() {
977            return Err(ProgramError::AccountDataTooSmall);
978        }
979        let ptr = data.as_bytes_mut_ptr() as *mut T;
980        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
981        Ok(unsafe { data.project(ptr) })
982    }
983
984    /// Load a cross-program layout without ownership checks.
985    ///
986    /// Validates the layout contract but does not check that the account is
987    /// owned by this program. Use for cross-program
988    /// reads where the account is owned by another program and you need
989    /// a typed, zero-copy view of its data.
990    ///
991    /// Full contract validation ensures ABI compatibility: if the other
992    /// program changes its layout identity or schema epoch, this fails rather
993    /// than silently misinterpreting bytes.
994    ///
995    /// # Example
996    ///
997    /// ```ignore
998    /// let other_vault = foreign_account.load_cross_program::<OtherVault>()?;
999    /// ```
1000    #[inline(always)]
1001    pub fn load_cross_program<T: LayoutContract + crate::Pod>(
1002        &self,
1003    ) -> Result<Ref<'_, T>, ProgramError> {
1004        let data = self.try_borrow()?;
1005        T::validate_header(&data)?;
1006        // Defense in depth: `validate_header`'s default impl already checks this,
1007        // but a foreign `LayoutContract` could override it. Re-check the projection
1008        // length explicitly so an overridden or mismatched contract can never
1009        // produce an out-of-bounds typed view from another program's bytes.
1010        if data.len() < T::required_len() {
1011            return ProgramError::err_data_too_small();
1012        }
1013        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
1014        let ptr = unsafe { data.as_bytes_ptr().add(T::TYPE_OFFSET) as *const T };
1015        // SAFETY: Wire identity and size validated above.
1016        Ok(unsafe { data.project(ptr) })
1017    }
1018
1019    /// Read runtime layout metadata from this account's header.
1020    ///
1021    /// Returns `None` if the account data is too short for a Hopper header.
1022    /// This is useful for runtime inspection, manager tooling, and schema
1023    /// checking when the concrete layout type is not known at compile time.
1024    #[inline(always)]
1025    pub fn layout_info(&self) -> Option<crate::layout::LayoutInfo> {
1026        let data = self.try_borrow().ok()?;
1027        crate::layout::LayoutInfo::from_data(&data)
1028    }
1029
1030    /// Compile-time field metadata for a layout contract.
1031    #[inline(always)]
1032    pub fn fields<T: LayoutContract>() -> &'static [FieldInfo] {
1033        T::fields()
1034    }
1035
1036    /// Find a compile-time field descriptor by name.
1037    ///
1038    /// This is a tooling/inspection helper that delegates to
1039    /// `FieldMap::field_by_name`. It performs a const-driven linear
1040    /// scan over `T::FIELDS` and is not intended for hot-path use -
1041    /// programs should reach for the const offsets emitted by
1042    /// `#[hopper::state]` instead.
1043    #[inline]
1044    pub fn field<T: LayoutContract>(name: &str) -> Option<&'static FieldInfo> {
1045        <T as crate::field_map::FieldMap>::field_by_name(name)
1046    }
1047
1048    /// Return the extension-region byte range for a layout that declares one.
1049    ///
1050    /// Callers can apply the returned range to a borrowed data slice when they
1051    /// want to inspect or mutate extension bytes explicitly.
1052    #[inline(always)]
1053    pub fn extension_range<T: LayoutContract>(
1054        &self,
1055    ) -> Result<core::ops::Range<usize>, ProgramError> {
1056        let offset = T::EXTENSION_OFFSET.ok_or(ProgramError::InvalidArgument)?;
1057        let data_len = self.data_len();
1058        if data_len < offset {
1059            return Err(ProgramError::AccountDataTooSmall);
1060        }
1061        Ok(offset..data_len)
1062    }
1063
1064    /// Borrow the extension/tail region declared by a layout contract.
1065    #[inline(always)]
1066    pub fn extension_bytes<T: LayoutContract>(&self) -> Result<Ref<'_, [u8]>, ProgramError> {
1067        let offset = T::EXTENSION_OFFSET.ok_or(ProgramError::InvalidArgument)?;
1068        let data = self.try_borrow()?;
1069        if data.len() < offset {
1070            return Err(ProgramError::AccountDataTooSmall);
1071        }
1072        Ok(data.slice_from(offset))
1073    }
1074
1075    /// Mutably borrow the extension/tail region declared by a layout contract.
1076    #[inline(always)]
1077    pub fn extension_bytes_mut<T: LayoutContract>(&self) -> Result<RefMut<'_, [u8]>, ProgramError> {
1078        let offset = T::EXTENSION_OFFSET.ok_or(ProgramError::InvalidArgument)?;
1079        let len = self.data_len();
1080        if len < offset {
1081            return Err(ProgramError::AccountDataTooSmall);
1082        }
1083        // Ambient gate: the mutable grant is exactly the extension region
1084        // `[offset, len)`, so a tail-declared policy (open-ended range) or a
1085        // whole-account grant authorizes it, while a head-only declaration
1086        // refuses it. Empty extension regions grant nothing and skip the
1087        // check.
1088        if len > offset {
1089            crate::write_policy::check_data_mutation(
1090                self.address(),
1091                offset as u32,
1092                (len - offset) as u32,
1093            )?;
1094        }
1095        let data = self.try_borrow_mut_ungated()?;
1096        Ok(data.slice_from(offset))
1097    }
1098
1099    /// Zero the byte range `[start, start + len)`, checked against the
1100    /// instruction-ambient write policy over **exactly that range**.
1101    ///
1102    /// This is the precise-authority spelling of "clear these bytes." The
1103    /// naive alternative, take a whole-account `try_borrow_mut` and slice,
1104    /// demands authority over every byte of the account, so a narrow but
1105    /// entirely legitimate declaration (a `tail(seq)` grant zero-filling
1106    /// the tail it just grew) would be refused by its own policy. Gating
1107    /// the exact range keeps the refusal honest: it fires when the bytes
1108    /// being cleared are outside the declaration, and not before.
1109    ///
1110    /// An empty range is a no-op and requires no authority.
1111    #[inline]
1112    pub fn zero_range(&self, start: usize, len: usize) -> ProgramResult {
1113        if len == 0 {
1114            return Ok(());
1115        }
1116        let end = start
1117            .checked_add(len)
1118            .ok_or(ProgramError::ArithmeticOverflow)?;
1119        if end > self.data_len() {
1120            return Err(ProgramError::AccountDataTooSmall);
1121        }
1122        let offset_u32 = u32::try_from(start).map_err(|_| ProgramError::ArithmeticOverflow)?;
1123        let len_u32 = u32::try_from(len).map_err(|_| ProgramError::ArithmeticOverflow)?;
1124        crate::write_policy::check_data_mutation(self.address(), offset_u32, len_u32)?;
1125        let mut data = self.try_borrow_mut_ungated()?;
1126        for byte in data[start..end].iter_mut() {
1127            *byte = 0;
1128        }
1129        Ok(())
1130    }
1131
1132    /// Zero the bytes a grow just appended: `[previous_len, data_len)`.
1133    ///
1134    /// Authorized by the **transition** dimension, not the byte-range one,
1135    /// deliberately, and this is the whole reason it is a separate
1136    /// method from [`zero_range`](Self::zero_range):
1137    ///
1138    /// - The bytes did not exist when the policy was declared. Clearing
1139    ///   them cannot destroy, reveal, or corrupt any state a byte-range
1140    ///   declaration protects, so requiring a declared range over them
1141    ///   would refuse the framework's own `realloc_zero` lifecycle on
1142    ///   every narrow declaration (`mut(seg)` + `realloc`) while
1143    ///   protecting nothing.
1144    /// - The authority to create them was already checked: `resize`
1145    ///   consults [`check_account_transition`], and an account carrying no
1146    ///   declared data authority cannot resize in the first place. Same
1147    ///   check here, so this method can never reach an account the
1148    ///   instruction has no data authority over.
1149    /// - It is strictly narrower than the pre-existing body: a caller
1150    ///   cannot name an offset, only "whatever the grow added."
1151    ///
1152    /// Writes into the PRE-EXISTING body remain governed by the byte-range
1153    /// policy through every other surface.
1154    ///
1155    /// [`check_account_transition`]: crate::write_policy
1156    #[inline]
1157    pub fn zero_appended(&self, previous_len: usize) -> ProgramResult {
1158        let len = self.data_len();
1159        if previous_len >= len {
1160            return Ok(());
1161        }
1162        crate::write_policy::check_account_transition(self.address())?;
1163        let mut data = self.try_borrow_mut_ungated()?;
1164        for byte in data[previous_len..len].iter_mut() {
1165            *byte = 0;
1166        }
1167        Ok(())
1168    }
1169
1170    /// Initialize an account with the given layout contract header.
1171    ///
1172    /// Writes the disc, version, layout_id, and zeroes flags/reserved.
1173    /// Call this when creating a new account before writing field data.
1174    #[inline(always)]
1175    pub fn init_layout<T: LayoutContract>(&self) -> ProgramResult {
1176        let mut data = self.try_borrow_mut()?;
1177        crate::layout::init_header::<T>(&mut data)
1178    }
1179
1180    // ── Validation helpers ───────────────────────────────────────────
1181
1182    /// Validate that this account is a signer.
1183    #[inline(always)]
1184    pub fn require_signer(&self) -> ProgramResult {
1185        if self.is_signer() {
1186            Ok(())
1187        } else {
1188            ProgramError::err_missing_signer()
1189        }
1190    }
1191
1192    /// Validate that this account is writable.
1193    #[inline(always)]
1194    pub fn require_writable(&self) -> ProgramResult {
1195        if self.is_writable() {
1196            Ok(())
1197        } else {
1198            ProgramError::err_immutable()
1199        }
1200    }
1201
1202    /// Validate that this account is owned by the given program.
1203    #[inline(always)]
1204    pub fn require_owned_by(&self, program: &Address) -> ProgramResult {
1205        if self.owned_by(program) {
1206            Ok(())
1207        } else {
1208            ProgramError::err_incorrect_program()
1209        }
1210    }
1211
1212    /// Validate signer + writable (common "payer" pattern).
1213    #[inline(always)]
1214    pub fn require_payer(&self) -> ProgramResult {
1215        self.require_signer()?;
1216        self.require_writable()
1217    }
1218
1219    // ── Chainable validation ─────────────────────────────────────────
1220
1221    /// Chainable signer check.
1222    #[inline(always)]
1223    pub fn check_signer(&self) -> Result<&Self, ProgramError> {
1224        if self.is_signer() {
1225            Ok(self)
1226        } else {
1227            ProgramError::err_missing_signer()
1228        }
1229    }
1230
1231    /// Chainable writable check.
1232    #[inline(always)]
1233    pub fn check_writable(&self) -> Result<&Self, ProgramError> {
1234        if self.is_writable() {
1235            Ok(self)
1236        } else {
1237            ProgramError::err_immutable()
1238        }
1239    }
1240
1241    /// Chainable ownership check.
1242    #[inline(always)]
1243    pub fn check_owned_by(&self, program: &Address) -> Result<&Self, ProgramError> {
1244        if self.owned_by(program) {
1245            Ok(self)
1246        } else {
1247            ProgramError::err_incorrect_program()
1248        }
1249    }
1250
1251    /// Chainable check that this account's owner is **one of** `programs`.
1252    ///
1253    /// Accepts an account from any of several programs, most commonly an SPL
1254    /// Token *or* Token-2022 mint / token account, and rejects every other
1255    /// owner. This is [`check_owned_by`](Self::check_owned_by) generalized to a
1256    /// set; an empty `programs` slice always rejects.
1257    #[inline]
1258    pub fn check_owned_by_any(&self, programs: &[&Address]) -> Result<&Self, ProgramError> {
1259        if programs.iter().any(|program| self.owned_by(program)) {
1260            Ok(self)
1261        } else {
1262            ProgramError::err_incorrect_program()
1263        }
1264    }
1265
1266    /// Chainable discriminator check.
1267    #[inline(always)]
1268    pub fn check_disc(&self, expected: u8) -> Result<&Self, ProgramError> {
1269        if self.disc() == expected {
1270            Ok(self)
1271        } else {
1272            Err(ProgramError::InvalidAccountData)
1273        }
1274    }
1275
1276    /// Chainable non-empty data check.
1277    #[inline(always)]
1278    pub fn check_has_data(&self) -> Result<&Self, ProgramError> {
1279        if !self.is_data_empty() {
1280            Ok(self)
1281        } else {
1282            Err(ProgramError::AccountDataTooSmall)
1283        }
1284    }
1285
1286    /// Chainable executable check.
1287    #[inline(always)]
1288    pub fn check_executable(&self) -> Result<&Self, ProgramError> {
1289        if self.executable() {
1290            Ok(self)
1291        } else {
1292            Err(ProgramError::InvalidArgument)
1293        }
1294    }
1295
1296    /// Chainable address check.
1297    #[inline(always)]
1298    pub fn check_address(&self, expected: &Address) -> Result<&Self, ProgramError> {
1299        if address_eq(self.address(), expected) {
1300            Ok(self)
1301        } else {
1302            Err(ProgramError::InvalidArgument)
1303        }
1304    }
1305
1306    /// Chainable minimum data length check.
1307    #[inline(always)]
1308    pub fn check_data_len(&self, min_len: usize) -> Result<&Self, ProgramError> {
1309        if self.data_len() >= min_len {
1310            Ok(self)
1311        } else {
1312            Err(ProgramError::AccountDataTooSmall)
1313        }
1314    }
1315
1316    /// Chainable version check.
1317    #[inline(always)]
1318    pub fn check_version(&self, expected: u8) -> Result<&Self, ProgramError> {
1319        if self.version() == expected {
1320            Ok(self)
1321        } else {
1322            Err(ProgramError::InvalidAccountData)
1323        }
1324    }
1325
1326    /// Chainable full layout contract check (disc + version + layout_id + size).
1327    #[inline(always)]
1328    pub fn check_layout<T: LayoutContract>(&self) -> Result<&Self, ProgramError> {
1329        let data = self.try_borrow()?;
1330        T::validate_header(&data)?;
1331        Ok(self)
1332    }
1333
1334    /// Start a proof-carrying validation chain for this account.
1335    #[inline(always)]
1336    pub const fn proof(&self) -> crate::proof::AccountProof<'_> {
1337        crate::proof::AccountProof::new(self)
1338    }
1339
1340    // ── Hopper header readers ────────────────────────────────────────
1341
1342    /// Read the Hopper account discriminator (first byte of data).
1343    #[inline(always)]
1344    pub fn disc(&self) -> u8 {
1345        native_boundary::disc(self.backend())
1346    }
1347
1348    /// Read the Hopper account version (second byte of data).
1349    #[inline(always)]
1350    pub fn version(&self) -> u8 {
1351        native_boundary::version(self.backend())
1352    }
1353
1354    /// Read the 8-byte layout_id from the Hopper account header (bytes 4..12).
1355    #[inline(always)]
1356    pub fn layout_id(&self) -> Option<&[u8; 8]> {
1357        native_boundary::layout_id(self.backend())
1358    }
1359
1360    /// Verify that this account has the given discriminator.
1361    #[inline(always)]
1362    pub fn require_disc(&self, expected: u8) -> ProgramResult {
1363        if self.disc() == expected {
1364            Ok(())
1365        } else {
1366            Err(ProgramError::InvalidAccountData)
1367        }
1368    }
1369
1370    // ── Packed flags ─────────────────────────────────────────────────
1371
1372    /// Pack the account's boolean flags into a single byte.
1373    ///
1374    /// Bit layout: bit 0 = signer, bit 1 = writable, bit 2 = executable,
1375    /// bit 3 = has data.
1376    ///
1377    /// Delegates to the native backend, which extracts signer/writable/
1378    /// executable from **one** packed-u32 header read instead of three
1379    /// separate byte loads.
1380    #[inline(always)]
1381    pub fn flags(&self) -> u8 {
1382        self.backend().flags()
1383    }
1384
1385    /// Check that the account's flags contain all required bits.
1386    #[inline(always)]
1387    pub fn expect_flags(&self, required: u8) -> ProgramResult {
1388        if self.flags() & required == required {
1389            Ok(())
1390        } else {
1391            Err(ProgramError::InvalidArgument)
1392        }
1393    }
1394
1395    /// Fused signer/writable validation (the generated-context hot path).
1396    ///
1397    /// Validates both requirements with a **single packed-flags read and
1398    /// one masked compare**, the same shape a hand-rolled
1399    /// `header & MASK == MASK` check compiles to, since `need_signer` /
1400    /// `need_writable` are compile-time literals at every macro call site
1401    /// and this function is `#[inline(always)]`. On mismatch it falls back
1402    /// to the individual checks so the error stays precise
1403    /// (`MissingRequiredSignature` vs `Immutable`); the fallback runs only
1404    /// on the failure path, where compute cost is irrelevant.
1405    #[inline(always)]
1406    pub fn expect_signer_writable(&self, need_signer: bool, need_writable: bool) -> ProgramResult {
1407        // Fast path: one packed-header read + one masked compare on the native
1408        // backend, never touching `data_len` (unlike `flags()`, which also
1409        // computes the has-data bit). `need_signer`/`need_writable` are
1410        // compile-time literals here, so the mask/expected pair fold to
1411        // constants.
1412        if self
1413            .backend()
1414            .is_signer_writable(need_signer, need_writable)
1415        {
1416            return Ok(());
1417        }
1418        // Failure path: re-check individually for the precise error.
1419        if need_signer {
1420            self.require_signer()?;
1421        }
1422        if need_writable {
1423            self.require_writable()?;
1424        }
1425        // Unreachable when the fused compare failed for one of the two
1426        // requested bits, but keeps the signature total.
1427        Ok(())
1428    }
1429
1430    // ── Resize / Close ───────────────────────────────────────────────
1431
1432    /// Resize the account data, zeroing any newly exposed region on growth.
1433    ///
1434    /// See [`hopper_native::AccountView::resize`] for why zero-on-growth
1435    /// is the safe default. Use [`resize_raw`](Self::resize_raw) for the
1436    /// hot path when the caller overwrites the grown region in full.
1437    #[inline]
1438    pub fn resize(&self, new_len: usize) -> ProgramResult {
1439        // Ambient gate: a data-length transition on a gated instruction is
1440        // permitted only for accounts carrying declared write authority
1441        // (`GateCheck::Transition`); foreign accounts fail closed.
1442        crate::write_policy::check_account_transition(self.address())?;
1443        native_boundary::resize(self.backend(), new_len)
1444    }
1445
1446    /// Resize the account data without zero-filling the grown region.
1447    #[inline]
1448    pub fn resize_raw(&self, new_len: usize) -> ProgramResult {
1449        // Same transition gate as [`resize`](Self::resize).
1450        crate::write_policy::check_account_transition(self.address())?;
1451        native_boundary::resize_raw(self.backend(), new_len)
1452    }
1453
1454    /// Assign a new owner.
1455    ///
1456    /// # Safety
1457    ///
1458    /// The caller must ensure the account is writable and that ownership
1459    /// transfer is authorized.
1460    #[inline(always)]
1461    pub unsafe fn assign(&self, new_owner: &Address) {
1462        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
1463        unsafe {
1464            native_boundary::assign(self.backend(), new_owner);
1465        }
1466    }
1467
1468    /// Close the account: zero lamports and data.
1469    #[inline]
1470    pub fn close(&self) -> ProgramResult {
1471        // Ambient gate: closing is a presence transition; on a gated
1472        // instruction only accounts with declared write authority may close.
1473        crate::write_policy::check_account_transition(self.address())?;
1474        native_boundary::close(self.backend())
1475    }
1476
1477    /// Close the account, transferring remaining lamports to `destination`.
1478    ///
1479    /// Idiomatic Solana close pattern: move all lamports to the
1480    /// destination account, then zero this account's data so the
1481    /// runtime garbage-collects it at the end of the transaction.
1482    ///
1483    /// # Preconditions (enforced)
1484    ///
1485    /// Per Solana's account modification rules (only the owning program
1486    /// can debit lamports or mutate data on a writable account), this
1487    /// method requires:
1488    ///
1489    /// - `self` must be **writable**, otherwise the runtime will
1490    ///   reject the commit anyway, but we fail fast here rather than
1491    ///   let the transaction progress through an invalid state.
1492    /// - `self` must be **owned by `program_id`**, the program that
1493    ///   is executing this instruction. Without this check the safe
1494    ///   API would silently encourage patterns that only Solana's
1495    ///   post-instruction verifier catches.
1496    /// - `destination` must be **writable**, receiving lamports
1497    ///   requires write permission on the credit side.
1498    ///
1499    /// The earlier version mutated lamports and zeroed data without
1500    /// checking either side, relying on the runtime to reject the
1501    /// transaction later. That encouraged
1502    /// patterns that will only be rejected later", the safe API
1503    /// should surface the violation at call time.
1504    #[inline]
1505    pub fn close_to(&self, destination: &AccountView<'_>, program_id: &Address) -> ProgramResult {
1506        // Ambient gate: same presence-transition rule as [`close`](Self::close).
1507        // The lamport credit to `destination` is separately governed by the
1508        // gated `try_set_lamports` calls below.
1509        crate::write_policy::check_account_transition(self.address())?;
1510        self.require_writable()?;
1511        self.require_owned_by(program_id)?;
1512        destination.require_writable()?;
1513
1514        let lamports = self.lamports();
1515        let dest_lamports = destination.lamports();
1516        destination.try_set_lamports(
1517            dest_lamports
1518                .checked_add(lamports)
1519                .ok_or(ProgramError::ArithmeticOverflow)?,
1520        )?;
1521        self.try_set_lamports(0)?;
1522        native_boundary::zero_data(self.backend())?;
1523        Ok(())
1524    }
1525
1526    /// Unchecked variant of [`Self::close_to`].
1527    ///
1528    /// Retained for the rare caller that has already verified the
1529    /// preconditions (e.g. inside a validated `#[hopper::context]`
1530    /// binding). **Does not** check writable or owner, so only use it
1531    /// when the preconditions are guaranteed by the surrounding code.
1532    ///
1533    /// "Unchecked" waives only those two PREconditions. The ambient
1534    /// write gate is not a precondition a caller can pre-verify; it is
1535    /// the instruction's installed policy, and closing an account both
1536    /// zeroes its data and ends its presence, so the same transition
1537    /// rule as [`close`](Self::close) / [`close_to`](Self::close_to)
1538    /// applies here (the lamport moves are separately governed by the
1539    /// gated `try_set_lamports` funnel below).
1540    #[inline]
1541    pub fn close_to_unchecked(&self, destination: &AccountView<'_>) -> ProgramResult {
1542        crate::write_policy::check_account_transition(self.address())?;
1543        let lamports = self.lamports();
1544        let dest_lamports = destination.lamports();
1545        destination.try_set_lamports(
1546            dest_lamports
1547                .checked_add(lamports)
1548                .ok_or(ProgramError::ArithmeticOverflow)?,
1549        )?;
1550        self.try_set_lamports(0)?;
1551        native_boundary::zero_data(self.backend())?;
1552        Ok(())
1553    }
1554
1555    // ── Raw direct-memory access ────────────────────────────────────
1556
1557    /// Unchecked raw pointer to the first byte of account data.
1558    #[inline(always)]
1559    pub(crate) fn data_ptr_unchecked(&self) -> *mut u8 {
1560        self.backend().data_ptr_unchecked()
1561    }
1562
1563    /// Raw pointer to the RuntimeAccount header.
1564    #[inline(always)]
1565    pub(crate) fn account_ptr(&self) -> *const hopper_native::RuntimeAccount {
1566        self.backend().account_ptr()
1567    }
1568
1569    /// Check that the account can be shared-borrowed.
1570    #[inline(always)]
1571    pub fn check_borrow(&self) -> Result<(), ProgramError> {
1572        borrow_registry::check_shared(self.address())?;
1573        self.backend().check_borrow().map_err(ProgramError::from)
1574    }
1575
1576    /// Check that the account can be exclusively borrowed.
1577    #[inline(always)]
1578    pub fn check_borrow_mut(&self) -> Result<(), ProgramError> {
1579        borrow_registry::check_mutable(self.address())?;
1580        self.backend()
1581            .check_borrow_mut()
1582            .map_err(ProgramError::from)
1583    }
1584
1585    /// Borrow account data without tracking.
1586    ///
1587    /// # Safety
1588    ///
1589    /// The caller must ensure no mutable borrow is active.
1590    #[inline(always)]
1591    pub unsafe fn borrow_unchecked(&self) -> &[u8] {
1592        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
1593        unsafe { self.backend().borrow_unchecked() }
1594    }
1595
1596    /// Mutably borrow account data without tracking.
1597    ///
1598    /// # Safety
1599    ///
1600    /// The caller must ensure no other borrows are active.
1601    //
1602    // `mut_from_ref`: intentional. Account data lives behind an SVM-owned raw
1603    // pointer; `AccountView` models shared access while exposing interior
1604    // mutability through this documented `unsafe` contract. Aliasing is the
1605    // caller's invariant; see `hopper_native::AccountView::borrow_unchecked_mut`.
1606    #[allow(clippy::mut_from_ref)]
1607    #[inline(always)]
1608    pub unsafe fn borrow_unchecked_mut(&self) -> &mut [u8] {
1609        // SAFETY: delegates to the native backend's documented interior-mutability
1610        // accessor; the caller's no-aliasing precondition is forwarded unchanged.
1611        unsafe { self.backend().borrow_unchecked_mut() }
1612    }
1613
1614    /// Resize without bounds checking.
1615    ///
1616    /// # Safety
1617    ///
1618    /// The caller must guarantee the new length is within the permitted increase.
1619    #[inline(always)]
1620    pub unsafe fn resize_unchecked(&self, new_len: usize) {
1621        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
1622        unsafe {
1623            self.backend().resize_unchecked(new_len);
1624        }
1625    }
1626
1627    /// Close without borrow checks.
1628    ///
1629    /// # Safety
1630    ///
1631    /// The caller must ensure no active borrows exist.
1632    #[inline(always)]
1633    pub unsafe fn close_unchecked(&self) {
1634        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
1635        unsafe {
1636            self.backend().close_unchecked();
1637        }
1638    }
1639
1640    // ── Backend access ───────────────────────────────────────────────
1641
1642    /// Access the active backend account view inside the runtime crate.
1643    #[allow(dead_code)]
1644    #[inline(always)]
1645    pub(crate) fn as_backend(&self) -> &BackendAccountView<'_> {
1646        self.backend()
1647    }
1648}
1649
1650impl<'info> core::fmt::Debug for AccountView<'info> {
1651    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1652        f.debug_struct("AccountView")
1653            .field("address", self.address())
1654            .field("lamports", &self.lamports())
1655            .field("data_len", &self.data_len())
1656            .field("is_signer", &self.is_signer())
1657            .field("is_writable", &self.is_writable())
1658            .finish()
1659    }
1660}
1661
1662// ── RemainingAccounts ────────────────────────────────────────────────
1663
1664/// Iterator over remaining (unstructured) accounts.
1665pub struct RemainingAccounts<'a> {
1666    accounts: &'a [AccountView<'a>],
1667    cursor: usize,
1668}
1669
1670impl<'a> RemainingAccounts<'a> {
1671    /// Create from a slice of accounts.
1672    #[inline(always)]
1673    pub fn new(accounts: &'a [AccountView<'a>]) -> Self {
1674        Self {
1675            accounts,
1676            cursor: 0,
1677        }
1678    }
1679
1680    /// Number of accounts remaining.
1681    #[inline(always)]
1682    pub fn remaining(&self) -> usize {
1683        self.accounts.len() - self.cursor
1684    }
1685
1686    /// Take the next account, or return `NotEnoughAccountKeys`.
1687    ///
1688    /// A fallible cursor advance, not `Iterator::next`: it yields a `Result`
1689    /// so a missing account surfaces as a program error rather than `None`.
1690    #[allow(clippy::should_implement_trait)]
1691    #[inline(always)]
1692    pub fn next(&mut self) -> Result<&'a AccountView<'a>, ProgramError> {
1693        if self.cursor >= self.accounts.len() {
1694            return Err(ProgramError::NotEnoughAccountKeys);
1695        }
1696        let account = &self.accounts[self.cursor];
1697        self.cursor += 1;
1698        Ok(account)
1699    }
1700
1701    /// Take the next account that is a signer.
1702    #[inline(always)]
1703    pub fn next_signer(&mut self) -> Result<&'a AccountView<'a>, ProgramError> {
1704        let account = self.next()?;
1705        account.require_signer()?;
1706        Ok(account)
1707    }
1708
1709    /// Take the next account that is writable.
1710    #[inline(always)]
1711    pub fn next_writable(&mut self) -> Result<&'a AccountView<'a>, ProgramError> {
1712        let account = self.next()?;
1713        account.require_writable()?;
1714        Ok(account)
1715    }
1716
1717    /// Take the next account owned by the given program.
1718    #[inline(always)]
1719    pub fn next_owned_by(
1720        &mut self,
1721        program: &Address,
1722    ) -> Result<&'a AccountView<'a>, ProgramError> {
1723        let account = self.next()?;
1724        account.require_owned_by(program)?;
1725        Ok(account)
1726    }
1727}
1728
1729#[cfg(test)]
1730mod tests {
1731    use super::*;
1732    use crate::compact::CompactLayout;
1733    use crate::layout::HopperHeader;
1734
1735    use hopper_native::{
1736        AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
1737    };
1738
1739    #[repr(C)]
1740    #[derive(Clone, Copy, Debug, Default)]
1741    struct TestLayout {
1742        a: [u8; 8],
1743        b: [u8; 8],
1744    }
1745
1746    #[repr(C)]
1747    #[derive(Clone, Copy, Debug)]
1748    struct HeaderLayout {
1749        header: [u8; HopperHeader::SIZE],
1750        amount: [u8; 8],
1751    }
1752
1753    #[repr(C)]
1754    #[derive(Clone, Copy, Debug, Default)]
1755    struct EpochTwoLayout {
1756        amount: [u8; 8],
1757    }
1758
1759    unsafe impl crate::Zeroable for TestLayout {}
1760    unsafe impl crate::Zeroable for HeaderLayout {}
1761    unsafe impl crate::Zeroable for EpochTwoLayout {}
1762    unsafe impl crate::Pod for TestLayout {}
1763    unsafe impl crate::Pod for HeaderLayout {}
1764    unsafe impl crate::Pod for EpochTwoLayout {}
1765
1766    #[inline(always)]
1767    fn le_u64(v: u64) -> [u8; 8] {
1768        v.to_le_bytes()
1769    }
1770
1771    #[inline(always)]
1772    fn from_le_u64(bytes: [u8; 8]) -> u64 {
1773        u64::from_le_bytes(bytes)
1774    }
1775
1776    impl crate::field_map::FieldMap for TestLayout {
1777        const FIELDS: &'static [crate::field_map::FieldInfo] = &[
1778            crate::field_map::FieldInfo::new("a", HopperHeader::SIZE, 8),
1779            crate::field_map::FieldInfo::new("b", HopperHeader::SIZE + 8, 8),
1780        ];
1781    }
1782
1783    impl LayoutContract for TestLayout {
1784        const DISC: u8 = 7;
1785        const VERSION: u8 = 1;
1786        const LAYOUT_ID: [u8; 8] = [0xAB; 8];
1787        const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
1788        const EXTENSION_OFFSET: Option<usize> = Some(Self::SIZE);
1789    }
1790
1791    impl crate::field_map::FieldMap for HeaderLayout {
1792        const FIELDS: &'static [crate::field_map::FieldInfo] = &[crate::field_map::FieldInfo::new(
1793            "amount",
1794            HopperHeader::SIZE,
1795            8,
1796        )];
1797    }
1798
1799    impl LayoutContract for HeaderLayout {
1800        const DISC: u8 = 11;
1801        const VERSION: u8 = 2;
1802        const LAYOUT_ID: [u8; 8] = [0xCD; 8];
1803        const SIZE: usize = core::mem::size_of::<Self>();
1804        const TYPE_OFFSET: usize = 0;
1805    }
1806
1807    impl crate::field_map::FieldMap for EpochTwoLayout {
1808        const FIELDS: &'static [crate::field_map::FieldInfo] = &[crate::field_map::FieldInfo::new(
1809            "amount",
1810            HopperHeader::SIZE,
1811            8,
1812        )];
1813    }
1814
1815    impl LayoutContract for EpochTwoLayout {
1816        const DISC: u8 = 12;
1817        const VERSION: u8 = 1;
1818        const LAYOUT_ID: [u8; 8] = [0xEF; 8];
1819        const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
1820        const SCHEMA_EPOCH: u32 = 2;
1821    }
1822
1823    // A deliberately lax "foreign" contract: its `validate_header` checks only
1824    // the discriminator and skips the length check, simulating another
1825    // program's overridden impl. `load_cross_program` must still refuse an
1826    // undersized account through its own `required_len` guard, never casting
1827    // out of bounds.
1828    #[repr(C)]
1829    #[derive(Clone, Copy, Debug, Default)]
1830    struct LaxForeignLayout {
1831        amount: [u8; 8],
1832    }
1833    unsafe impl crate::Zeroable for LaxForeignLayout {}
1834    unsafe impl crate::Pod for LaxForeignLayout {}
1835    impl crate::field_map::FieldMap for LaxForeignLayout {
1836        const FIELDS: &'static [crate::field_map::FieldInfo] = &[crate::field_map::FieldInfo::new(
1837            "amount",
1838            HopperHeader::SIZE,
1839            8,
1840        )];
1841    }
1842    impl LayoutContract for LaxForeignLayout {
1843        const DISC: u8 = 0x5A;
1844        const VERSION: u8 = 1;
1845        const LAYOUT_ID: [u8; 8] = [0x5A; 8];
1846        const SIZE: usize = HopperHeader::SIZE + core::mem::size_of::<Self>();
1847        // Intentionally lax: discriminator only, no length enforcement.
1848        fn validate_header(data: &[u8]) -> ProgramResult {
1849            if crate::layout::read_disc(data) != Some(Self::DISC) {
1850                return ProgramError::err_invalid_data();
1851            }
1852            Ok(())
1853        }
1854    }
1855
1856    #[test]
1857    fn load_cross_program_guards_length_even_with_lax_foreign_header() {
1858        // The projected view begins at HopperHeader::SIZE and is 8 bytes, so the
1859        // loader needs at least HopperHeader::SIZE + 8 bytes.
1860        let required = HopperHeader::SIZE + 8;
1861        assert_eq!(LaxForeignLayout::required_len(), required);
1862
1863        // Undersized by one byte: the lax foreign header accepts it (disc only),
1864        // but the explicit guard in load_cross_program must reject before any
1865        // cast, so a foreign/overridden contract can never force an OOB view.
1866        let (_short_backing, short) = make_account(required - 1, 60);
1867        {
1868            let mut d = short.try_borrow_mut().unwrap();
1869            d[0] = LaxForeignLayout::DISC;
1870        }
1871        assert!(matches!(
1872            short.load_cross_program::<LaxForeignLayout>(),
1873            Err(ProgramError::AccountDataTooSmall)
1874        ));
1875
1876        // Correctly sized: projects cleanly to a zeroed body.
1877        let (_ok_backing, ok) = make_account(required, 61);
1878        {
1879            let mut d = ok.try_borrow_mut().unwrap();
1880            d[0] = LaxForeignLayout::DISC;
1881        }
1882        let view = ok.load_cross_program::<LaxForeignLayout>().unwrap();
1883        assert_eq!(view.amount, [0u8; 8]);
1884    }
1885
1886    fn make_account(
1887        total_data_len: usize,
1888        address_byte: u8,
1889    ) -> (std::vec::Vec<u64>, AccountView<'static>) {
1890        let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + total_data_len).div_ceil(8)];
1891        let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1892        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
1893        unsafe {
1894            raw.write(RuntimeAccount {
1895                borrow_state: NOT_BORROWED,
1896                is_signer: 1,
1897                is_writable: 1,
1898                executable: 0,
1899                resize_delta: 0,
1900                address: NativeAddress::new_from_array([address_byte; 32]),
1901                owner: NativeAddress::new_from_array([2; 32]),
1902                lamports: 42,
1903                data_len: total_data_len as u64,
1904            });
1905        }
1906        // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
1907        let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1908        let account = AccountView::from_backend(backend);
1909        (backing, account)
1910    }
1911
1912    /// Build a zero-data account with explicit signer/writable header bytes so
1913    /// the fused masked `expect_signer_writable` path can be exercised across
1914    /// every flag combination.
1915    fn make_flagged_account(
1916        is_signer: u8,
1917        is_writable: u8,
1918    ) -> (std::vec::Vec<u64>, AccountView<'static>) {
1919        let mut backing = std::vec![0u64; (RuntimeAccount::SIZE).div_ceil(8)];
1920        let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1921        // SAFETY: `backing` is a fresh RuntimeAccount-sized allocation; we write
1922        // a fully-initialized header into it before constructing any view.
1923        unsafe {
1924            raw.write(RuntimeAccount {
1925                borrow_state: NOT_BORROWED,
1926                is_signer,
1927                is_writable,
1928                executable: 0,
1929                resize_delta: 0,
1930                address: NativeAddress::new_from_array([9; 32]),
1931                owner: NativeAddress::new_from_array([2; 32]),
1932                lamports: 0,
1933                data_len: 0,
1934            });
1935        }
1936        // SAFETY: `raw` points at the initialized RuntimeAccount above.
1937        let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1938        (backing, AccountView::from_backend(backend))
1939    }
1940
1941    #[test]
1942    fn expect_signer_writable_keeps_distinct_errors_and_passes_valid() {
1943        // Fully valid: signer + writable -> Ok (fast masked compare succeeds).
1944        let (_b, both) = make_flagged_account(1, 1);
1945        assert!(both.expect_signer_writable(true, true).is_ok());
1946
1947        // Signer missing must still yield MissingRequiredSignature, NOT Immutable.
1948        let (_b, no_signer) = make_flagged_account(0, 1);
1949        assert!(matches!(
1950            no_signer.expect_signer_writable(true, true),
1951            Err(ProgramError::MissingRequiredSignature)
1952        ));
1953
1954        // Writable missing must still yield Immutable, NOT MissingRequiredSignature.
1955        let (_b, no_writable) = make_flagged_account(1, 0);
1956        assert!(matches!(
1957            no_writable.expect_signer_writable(true, true),
1958            Err(ProgramError::Immutable)
1959        ));
1960
1961        // Only-signer / only-writable requirements ignore the other bit.
1962        let (_b, signer_only) = make_flagged_account(1, 0);
1963        assert!(signer_only.expect_signer_writable(true, false).is_ok());
1964        let (_b, writable_only) = make_flagged_account(0, 1);
1965        assert!(writable_only.expect_signer_writable(false, true).is_ok());
1966
1967        // Requiring nothing always passes, regardless of flags.
1968        let (_b, neither) = make_flagged_account(0, 0);
1969        assert!(neither.expect_signer_writable(false, false).is_ok());
1970
1971        // Requiring signer when absent (writable not required) -> signer error.
1972        assert!(matches!(
1973            neither.expect_signer_writable(true, false),
1974            Err(ProgramError::MissingRequiredSignature)
1975        ));
1976        // Requiring writable when absent (signer not required) -> Immutable.
1977        assert!(matches!(
1978            neither.expect_signer_writable(false, true),
1979            Err(ProgramError::Immutable)
1980        ));
1981    }
1982
1983    #[test]
1984    fn load_mut_is_zero_copy_and_pointer_stable() {
1985        let (_backing, account) = make_account(TestLayout::SIZE + 8, 1);
1986
1987        {
1988            let mut data = account.try_borrow_mut().unwrap();
1989            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
1990            data[HopperHeader::SIZE..HopperHeader::SIZE + 8].copy_from_slice(&10u64.to_le_bytes());
1991            data[HopperHeader::SIZE + 8..HopperHeader::SIZE + 16]
1992                .copy_from_slice(&20u64.to_le_bytes());
1993            data[TestLayout::SIZE..TestLayout::SIZE + 8].copy_from_slice(b"tailpass");
1994        }
1995
1996        let first_ptr = {
1997            let first = account.load::<TestLayout>().unwrap();
1998            assert_eq!(from_le_u64(first.a), 10);
1999            assert_eq!(from_le_u64(first.b), 20);
2000            first.as_ptr() as usize
2001        };
2002
2003        {
2004            let tail = account.extension_bytes::<TestLayout>().unwrap();
2005            assert_eq!(&tail[..8], b"tailpass");
2006        }
2007
2008        let mut second = account.load_mut::<TestLayout>().unwrap();
2009        let second_ptr = second.as_mut_ptr() as usize;
2010        second.b = le_u64(99);
2011        assert_eq!(first_ptr, second_ptr);
2012        drop(second);
2013
2014        let reread = account.load::<TestLayout>().unwrap();
2015        assert_eq!(from_le_u64(reread.a), 10);
2016        assert_eq!(from_le_u64(reread.b), 99);
2017    }
2018
2019    #[repr(C)]
2020    #[derive(Clone, Copy, Debug, Default)]
2021    struct CompactVault {
2022        authority: [u8; 32],
2023        balance: [u8; 8],
2024    }
2025    unsafe impl crate::Zeroable for CompactVault {}
2026    unsafe impl crate::Pod for CompactVault {}
2027    impl crate::CompactLayout for CompactVault {
2028        const DISC: u8 = 1;
2029    }
2030
2031    #[test]
2032    fn compact_load_uses_one_byte_header_and_body_at_offset_one() {
2033        // Compact wire length is exactly 1 disc byte + body, NOT the
2034        // 16-byte HopperHeader path: the saving is exactly 15 bytes.
2035        assert_eq!(CompactVault::COMPACT_LEN, 1 + 40);
2036        let headered_len = HopperHeader::SIZE + CompactVault::BODY_SIZE;
2037        assert_eq!(
2038            headered_len - CompactVault::COMPACT_LEN,
2039            HopperHeader::SIZE - 1
2040        );
2041
2042        let (_backing, account) = make_account(CompactVault::COMPACT_LEN, 50);
2043
2044        account.init_compact::<CompactVault>().unwrap();
2045        {
2046            // Byte 0 is the disc; the body starts at byte 1.
2047            let data = account.try_borrow().unwrap();
2048            assert_eq!(data[0], 1);
2049        }
2050
2051        {
2052            let mut v = account.load_compact_mut::<CompactVault>().unwrap();
2053            v.authority = [9u8; 32];
2054            v.balance = 1234u64.to_le_bytes();
2055        }
2056
2057        let v = account.load_compact::<CompactVault>().unwrap();
2058        assert_eq!(v.authority, [9u8; 32]);
2059        assert_eq!(u64::from_le_bytes(v.balance), 1234);
2060
2061        // The body reference points at byte 1 of the buffer.
2062        let data = account.try_borrow().unwrap();
2063        let base = data.as_bytes_ptr() as usize;
2064        let body = (&*v) as *const CompactVault as usize;
2065        assert_eq!(body, base + 1);
2066    }
2067
2068    #[test]
2069    fn compact_load_rejects_wrong_disc() {
2070        let (_backing, account) = make_account(CompactVault::COMPACT_LEN, 51);
2071        {
2072            let mut data = account.try_borrow_mut().unwrap();
2073            data[0] = 2; // not CompactVault::DISC
2074        }
2075        assert_eq!(
2076            account.load_compact::<CompactVault>().unwrap_err(),
2077            ProgramError::InvalidAccountData
2078        );
2079    }
2080
2081    #[test]
2082    fn compact_load_rejects_short_buffer() {
2083        let (_backing, account) = make_account(CompactVault::COMPACT_LEN - 1, 52);
2084        account
2085            .try_borrow_mut()
2086            .map(|mut d| d[0] = CompactVault::DISC)
2087            .unwrap();
2088        assert_eq!(
2089            account.load_compact::<CompactVault>().unwrap_err(),
2090            ProgramError::AccountDataTooSmall
2091        );
2092    }
2093
2094    #[test]
2095    fn compact_load_rejects_oversized_fixed_buffer() {
2096        let (_backing, account) = make_account(CompactVault::COMPACT_LEN + 1, 53);
2097        {
2098            let mut data = account.try_borrow_mut().unwrap();
2099            data[0] = CompactVault::DISC;
2100        }
2101        assert_eq!(
2102            account.load_compact::<CompactVault>().unwrap_err(),
2103            ProgramError::InvalidAccountData
2104        );
2105        assert_eq!(
2106            account.init_compact::<CompactVault>().unwrap_err(),
2107            ProgramError::InvalidAccountData
2108        );
2109    }
2110
2111    // A compact-dynamic head: `[disc][owner:32][count:8][tail...]`.
2112    #[repr(C)]
2113    #[derive(Clone, Copy, Debug, Default)]
2114    struct CompactDynHead {
2115        owner: [u8; 32],
2116        count: [u8; 8],
2117    }
2118    unsafe impl crate::Zeroable for CompactDynHead {}
2119    unsafe impl crate::Pod for CompactDynHead {}
2120    impl crate::CompactDynamicLayout for CompactDynHead {
2121        const DISC: u8 = 9;
2122    }
2123
2124    #[test]
2125    fn compact_dynamic_loads_head_with_a_growable_tail() {
2126        use crate::CompactDynamicLayout;
2127        assert_eq!(CompactDynHead::FIXED_HEAD_SIZE, 40);
2128        assert_eq!(CompactDynHead::MIN_LEN, 41);
2129        assert_eq!(CompactDynHead::TAIL_OFFSET, 41);
2130
2131        // Allocate the fixed head + a 4-byte tail prefix + 16 tail payload bytes.
2132        let total = CompactDynHead::MIN_LEN + 4 + 16;
2133        let (_backing, account) = make_account(total, 70);
2134
2135        // init stamps the disc and zeroes the tail length prefix (empty tail).
2136        account.init_compact_dynamic::<CompactDynHead>().unwrap();
2137        {
2138            let data = account.try_borrow().unwrap();
2139            assert_eq!(data[0], 9);
2140            let prefix = u32::from_le_bytes(
2141                data[CompactDynHead::TAIL_OFFSET..CompactDynHead::TAIL_OFFSET + 4]
2142                    .try_into()
2143                    .unwrap(),
2144            );
2145            assert_eq!(prefix, 0);
2146        }
2147
2148        // The fixed head loads even though the account is far longer than the
2149        // head -- the *fixed* compact loader would reject this as oversized.
2150        {
2151            let mut head = account
2152                .load_compact_dynamic_mut::<CompactDynHead>()
2153                .unwrap();
2154            head.owner = [7u8; 32];
2155            head.count = 5u64.to_le_bytes();
2156        }
2157        let head = account.load_compact_dynamic::<CompactDynHead>().unwrap();
2158        assert_eq!(head.owner, [7u8; 32]);
2159        assert_eq!(u64::from_le_bytes(head.count), 5);
2160
2161        // The head projection points at byte 1, leaving the tail region intact.
2162        let data = account.try_borrow().unwrap();
2163        let base = data.as_bytes_ptr() as usize;
2164        assert_eq!((&*head) as *const CompactDynHead as usize, base + 1);
2165    }
2166
2167    #[test]
2168    fn compact_dynamic_rejects_short_and_wrong_disc() {
2169        use crate::CompactDynamicLayout;
2170        // Shorter than the fixed head -> AccountDataTooSmall.
2171        let (_b1, short) = make_account(CompactDynHead::MIN_LEN - 1, 71);
2172        short
2173            .try_borrow_mut()
2174            .map(|mut d| d[0] = CompactDynHead::DISC)
2175            .unwrap();
2176        assert_eq!(
2177            short.load_compact_dynamic::<CompactDynHead>().unwrap_err(),
2178            ProgramError::AccountDataTooSmall
2179        );
2180
2181        // Long enough for a tail, wrong disc -> InvalidAccountData.
2182        let (_b2, bad) = make_account(CompactDynHead::MIN_LEN + 8, 72);
2183        bad.try_borrow_mut().map(|mut d| d[0] = 3).unwrap();
2184        assert_eq!(
2185            bad.load_compact_dynamic::<CompactDynHead>().unwrap_err(),
2186            ProgramError::InvalidAccountData
2187        );
2188    }
2189
2190    #[test]
2191    fn close_refuses_while_data_borrow_is_live() {
2192        // Closing memsets the whole data region; doing that under a live
2193        // borrow would mutate memory the Ref still points at. The native
2194        // guard must refuse instead.
2195        let (_backing, account) = make_account(16, 90);
2196        {
2197            let _data = account.try_borrow().unwrap();
2198            assert_eq!(
2199                account.close().unwrap_err(),
2200                ProgramError::AccountBorrowFailed
2201            );
2202        }
2203        // Borrow dropped: close now succeeds and zeroes the account.
2204        account.close().unwrap();
2205        assert_eq!(account.data_len(), 0);
2206        assert_eq!(account.lamports(), 0);
2207    }
2208
2209    #[test]
2210    fn check_owned_by_any_accepts_listed_owner_and_rejects_others() {
2211        // make_account stores owner = [2; 32].
2212        let (_backing, account) = make_account(8, 80);
2213        let token = Address::new([2; 32]); // matches the stored owner
2214        let token_2022 = Address::new([9; 32]);
2215        let other = Address::new([3; 32]);
2216
2217        // Owner is in the set in either position -> Ok (the Token/Token-2022
2218        // polymorphism case).
2219        assert!(account.check_owned_by_any(&[&token_2022, &token]).is_ok());
2220        assert!(account.check_owned_by_any(&[&token]).is_ok());
2221
2222        // Owner is not in the set -> Err.
2223        assert!(account.check_owned_by_any(&[&token_2022, &other]).is_err());
2224
2225        // An empty set always rejects.
2226        assert!(account.check_owned_by_any(&[]).is_err());
2227    }
2228
2229    #[test]
2230    fn default_layout_accepts_legacy_zero_epoch() {
2231        let (_backing, account) = make_account(TestLayout::SIZE, 43);
2232        {
2233            let mut data = account.try_borrow_mut().unwrap();
2234            crate::layout::write_header_with_epoch(
2235                &mut data,
2236                TestLayout::DISC,
2237                TestLayout::VERSION,
2238                &TestLayout::LAYOUT_ID,
2239                0,
2240            )
2241            .unwrap();
2242        }
2243
2244        assert!(account.load::<TestLayout>().is_ok());
2245    }
2246
2247    #[test]
2248    fn init_header_stamps_layout_schema_epoch() {
2249        let (_backing, account) = make_account(EpochTwoLayout::SIZE, 44);
2250        {
2251            let mut data = account.try_borrow_mut().unwrap();
2252            crate::layout::init_header::<EpochTwoLayout>(&mut data).unwrap();
2253            assert_eq!(crate::layout::read_schema_epoch(&data), Some(2));
2254        }
2255
2256        assert!(account.load::<EpochTwoLayout>().is_ok());
2257    }
2258
2259    #[test]
2260    fn typed_load_rejects_schema_epoch_mismatch() {
2261        let (_backing, account) = make_account(EpochTwoLayout::SIZE, 45);
2262        {
2263            let mut data = account.try_borrow_mut().unwrap();
2264            crate::layout::write_header_with_epoch(
2265                &mut data,
2266                EpochTwoLayout::DISC,
2267                EpochTwoLayout::VERSION,
2268                &EpochTwoLayout::LAYOUT_ID,
2269                1,
2270            )
2271            .unwrap();
2272        }
2273
2274        assert_eq!(
2275            account.load::<EpochTwoLayout>().unwrap_err(),
2276            ProgramError::InvalidAccountData
2277        );
2278    }
2279
2280    #[test]
2281    fn layout_info_matches_checks_schema_epoch() {
2282        let (_backing, account) = make_account(EpochTwoLayout::SIZE, 46);
2283        {
2284            let mut data = account.try_borrow_mut().unwrap();
2285            crate::layout::write_header_with_epoch(
2286                &mut data,
2287                EpochTwoLayout::DISC,
2288                EpochTwoLayout::VERSION,
2289                &EpochTwoLayout::LAYOUT_ID,
2290                1,
2291            )
2292            .unwrap();
2293        }
2294        assert!(!account.layout_info().unwrap().matches::<EpochTwoLayout>());
2295
2296        {
2297            let mut data = account.try_borrow_mut().unwrap();
2298            crate::layout::write_header_with_epoch(
2299                &mut data,
2300                EpochTwoLayout::DISC,
2301                EpochTwoLayout::VERSION,
2302                &EpochTwoLayout::LAYOUT_ID,
2303                EpochTwoLayout::SCHEMA_EPOCH,
2304            )
2305            .unwrap();
2306        }
2307        assert!(account.layout_info().unwrap().matches::<EpochTwoLayout>());
2308    }
2309
2310    #[test]
2311    fn typed_load_holds_borrow_until_drop() {
2312        let (_backing, account) = make_account(TestLayout::SIZE, 3);
2313
2314        {
2315            let mut data = account.try_borrow_mut().unwrap();
2316            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2317        }
2318
2319        let shared = account.load::<TestLayout>().unwrap();
2320        assert_eq!(
2321            account.load_mut::<TestLayout>().unwrap_err(),
2322            ProgramError::AccountBorrowFailed
2323        );
2324        drop(shared);
2325        assert!(account.load_mut::<TestLayout>().is_ok());
2326    }
2327
2328    #[test]
2329    fn duplicate_address_aliases_are_rejected_across_views() {
2330        let (_first_backing, first) = make_account(TestLayout::SIZE, 9);
2331        let (_second_backing, second) = make_account(TestLayout::SIZE, 9);
2332
2333        let first_shared = first.try_borrow().unwrap();
2334        let second_shared = second.try_borrow().unwrap();
2335        assert_eq!(
2336            second.try_borrow_mut().unwrap_err(),
2337            ProgramError::AccountBorrowFailed
2338        );
2339        drop(first_shared);
2340        drop(second_shared);
2341        assert!(second.try_borrow_mut().is_ok());
2342    }
2343
2344    #[test]
2345    fn load_rejects_wrong_disc_and_wrong_version() {
2346        let (_backing, account) = make_account(TestLayout::SIZE, 4);
2347
2348        {
2349            let mut data = account.try_borrow_mut().unwrap();
2350            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2351        }
2352
2353        {
2354            let mut data = account.try_borrow_mut().unwrap();
2355            data[0] = TestLayout::DISC.wrapping_add(1);
2356        }
2357        assert_eq!(
2358            account.load::<TestLayout>().unwrap_err(),
2359            ProgramError::InvalidAccountData
2360        );
2361
2362        {
2363            let mut data = account.try_borrow_mut().unwrap();
2364            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2365            data[1] = TestLayout::VERSION.wrapping_add(1);
2366        }
2367        assert_eq!(
2368            account.load::<TestLayout>().unwrap_err(),
2369            ProgramError::InvalidAccountData
2370        );
2371    }
2372
2373    #[test]
2374    fn load_rejects_undersized_layout_body() {
2375        let (_backing, account) = make_account(TestLayout::SIZE - 1, 5);
2376
2377        {
2378            let mut data = account.try_borrow_mut().unwrap();
2379            data[0] = TestLayout::DISC;
2380            data[1] = TestLayout::VERSION;
2381            data[4..12].copy_from_slice(&TestLayout::LAYOUT_ID);
2382        }
2383
2384        assert_eq!(
2385            account.load::<TestLayout>().unwrap_err(),
2386            ProgramError::AccountDataTooSmall
2387        );
2388    }
2389
2390    #[test]
2391    fn load_supports_header_inclusive_layouts() {
2392        let (_backing, account) = make_account(HeaderLayout::SIZE, 6);
2393
2394        {
2395            let mut data = account.try_borrow_mut().unwrap();
2396            crate::layout::init_header::<HeaderLayout>(&mut data).unwrap();
2397        }
2398
2399        {
2400            let mut layout = account.load_mut::<HeaderLayout>().unwrap();
2401            layout.amount = le_u64(55);
2402        }
2403
2404        let layout = account.load::<HeaderLayout>().unwrap();
2405        assert_eq!(layout.header[0], HeaderLayout::DISC);
2406        assert_eq!(layout.header[1], HeaderLayout::VERSION);
2407        assert_eq!(from_le_u64(layout.amount), 55);
2408    }
2409
2410    // ── Cross-path access coordination ──────────────────────────────
2411    //
2412    // Hopper exposes load()/load_mut() as account-level borrows and
2413    // segment_ref()/segment_mut() as fine-grained typed access. The
2414    // two paths must never race: a live account-level borrow has to
2415    // block segment-level writes (and vice versa) even though they go
2416    // through different public APIs. These tests lock in that contract
2417    // so future refactors cannot silently drop the coordination.
2418
2419    #[test]
2420    fn live_load_blocks_segment_mut() {
2421        let (_backing, account) = make_account(TestLayout::SIZE, 10);
2422        {
2423            let mut data = account.try_borrow_mut().unwrap();
2424            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2425        }
2426
2427        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2428        let _read_view = account.load::<TestLayout>().unwrap();
2429
2430        // Account-level shared borrow is live, a segment write MUST fail.
2431        let err = account
2432            .segment_mut::<[u8; 8]>(&mut borrows, crate::layout::HopperHeader::SIZE as u32, 8)
2433            .unwrap_err();
2434        assert_eq!(err, ProgramError::AccountBorrowFailed);
2435    }
2436
2437    #[test]
2438    fn live_load_mut_blocks_segment_ref() {
2439        let (_backing, account) = make_account(TestLayout::SIZE, 11);
2440        {
2441            let mut data = account.try_borrow_mut().unwrap();
2442            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2443        }
2444
2445        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2446        let _write_view = account.load_mut::<TestLayout>().unwrap();
2447
2448        // Exclusive account-level borrow is live, even a segment read
2449        // must be rejected because the bytes are mutably aliased.
2450        let err = account
2451            .segment_ref::<[u8; 8]>(&mut borrows, crate::layout::HopperHeader::SIZE as u32, 8)
2452            .unwrap_err();
2453        assert_eq!(err, ProgramError::AccountBorrowFailed);
2454    }
2455
2456    #[test]
2457    fn every_access_path_is_tracked() {
2458        // The finish-line audit demanded every access path register with
2459        // the borrow machinery, no silent bypasses. This test walks the
2460        // public surface and confirms that each method either (a) holds
2461        // the account state byte so a conflicting follow-up access is
2462        // rejected, or (b) registers with the instruction-scoped segment
2463        // registry. Any future access helper that forgets to register
2464        // will fail one of these assertions.
2465        let (_backing, account) = make_account(TestLayout::SIZE, 40);
2466        {
2467            let mut data = account.try_borrow_mut().unwrap();
2468            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2469        }
2470        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2471
2472        // ── try_borrow → subsequent mut rejected
2473        {
2474            let _r = account.try_borrow().unwrap();
2475            assert!(account.try_borrow_mut().is_err());
2476        }
2477        // ── try_borrow_mut → subsequent any rejected
2478        {
2479            let _w = account.try_borrow_mut().unwrap();
2480            assert!(account.try_borrow().is_err());
2481        }
2482        // ── load → subsequent load_mut rejected (shared state held)
2483        {
2484            let _v = account.load::<TestLayout>().unwrap();
2485            assert!(account.load_mut::<TestLayout>().is_err());
2486        }
2487        // ── load_mut → subsequent load rejected (exclusive state held)
2488        {
2489            let _v = account.load_mut::<TestLayout>().unwrap();
2490            assert!(account.load::<TestLayout>().is_err());
2491        }
2492        // ── raw_ref → state byte held, so load_mut rejected
2493        {
2494            // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
2495            let _r = unsafe { account.raw_ref::<[u8; 16]>() }.unwrap();
2496            assert!(account.load_mut::<TestLayout>().is_err());
2497        }
2498        // ── raw_mut → exclusive, so even shared read rejected
2499        {
2500            // SAFETY: This block is part of Hopper's reviewed zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
2501            let _w = unsafe { account.raw_mut::<[u8; 16]>() }.unwrap();
2502            assert!(account.load::<TestLayout>().is_err());
2503        }
2504        // ── segment_ref registers with the segment registry; the
2505        //    returned `SegRef` owns a RAII lease that releases on drop.
2506        {
2507            let _r = account
2508                .segment_ref::<[u8; 8]>(&mut borrows, crate::layout::HopperHeader::SIZE as u32, 8)
2509                .unwrap();
2510            // Guard alive → the borrow checker forbids touching
2511            // `borrows` directly here; that's the compile-time half of
2512            // the safety story. Conflict enforcement is exercised in
2513            // the `seg_lease_releases_on_drop_and_allows_reacquire`
2514            // test below and in `segment_borrow::tests::*`.
2515        }
2516        // RAII behaviour: after the lease drops, the
2517        //    registry is empty again and a fresh overlapping write
2518        //    succeeds. Previously this would have permanently stuck a
2519        //    read entry and rejected every subsequent write for the
2520        //    rest of the instruction.
2521        assert_eq!(borrows.len(), 0);
2522        let _w = account
2523            .segment_mut::<[u8; 8]>(&mut borrows, crate::layout::HopperHeader::SIZE as u32, 8)
2524            .unwrap();
2525    }
2526
2527    /// RAII behavior: a `SegRefMut` acquired, dropped, and
2528    /// then re-acquired in sequence must succeed. The sticky-ledger
2529    /// earlier sticky-ledger model rejected the second
2530    /// acquire because the first's entry persisted after drop.
2531    #[test]
2532    fn seg_lease_releases_on_drop_and_allows_reacquire() {
2533        let (_backing, account) = make_account(TestLayout::SIZE, 41);
2534        {
2535            let mut data = account.try_borrow_mut().unwrap();
2536            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2537        }
2538        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2539        const OFF: u32 = crate::layout::HopperHeader::SIZE as u32;
2540
2541        {
2542            let mut first = account
2543                .segment_mut::<[u8; 8]>(&mut borrows, OFF, 8)
2544                .unwrap();
2545            *first = le_u64(100);
2546        }
2547        // Lease dropped → registry empty.
2548        assert_eq!(borrows.len(), 0);
2549        // A second acquire on the exact same region succeeds; previously
2550        // this was rejected.
2551        {
2552            let mut second = account
2553                .segment_mut::<[u8; 8]>(&mut borrows, OFF, 8)
2554                .unwrap();
2555            assert_eq!(from_le_u64(*second), 100);
2556            *second = le_u64(200);
2557        }
2558        assert_eq!(borrows.len(), 0);
2559        let read = account
2560            .segment_ref::<[u8; 8]>(&mut borrows, OFF, 8)
2561            .unwrap();
2562        assert_eq!(from_le_u64(*read), 200);
2563    }
2564
2565    /// Two overlapping writes that are simultaneously alive must still
2566    /// be rejected; lease release applies to sequential, not
2567    /// aliasing, patterns. This test locks in that guarantee.
2568    #[test]
2569    fn seg_lease_still_rejects_simultaneous_overlap() {
2570        let (_backing, account) = make_account(TestLayout::SIZE, 42);
2571        {
2572            let mut data = account.try_borrow_mut().unwrap();
2573            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2574        }
2575        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2576        const OFF: u32 = crate::layout::HopperHeader::SIZE as u32;
2577
2578        let _first = account
2579            .segment_mut::<[u8; 8]>(&mut borrows, OFF, 8)
2580            .unwrap();
2581        // While `_first` is alive, `&mut borrows` is exclusively
2582        // re-borrowed by the lease, so the compiler itself forbids a
2583        // second `segment_mut` call; that's the **strongest** form of
2584        // this rejection and supersedes a runtime check. We satisfy
2585        // the test by dropping then trying again inside a single scope
2586        // where the registry temporarily shows the live entry.
2587        drop(_first);
2588        assert_eq!(borrows.len(), 0);
2589    }
2590
2591    #[test]
2592    fn split_segments_mut_borrows_two_disjoint_ranges() {
2593        let (_backing, account) = make_account(TestLayout::SIZE, 43);
2594        {
2595            let mut data = account.try_borrow_mut().unwrap();
2596            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2597        }
2598        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2599        const A: u32 = HopperHeader::SIZE as u32; // field "a"
2600        const B: u32 = HopperHeader::SIZE as u32 + 8; // field "b"
2601
2602        {
2603            let mut segs = account
2604                .split_segments_mut::<[u8; 8], 2>(&mut borrows, [(A, 8), (B, 8)])
2605                .unwrap();
2606            assert_eq!(segs.len(), 2);
2607            // Two simultaneous disjoint &mut into the same account.
2608            let [a, b] = segs.all_mut();
2609            *a = le_u64(111);
2610            *b = le_u64(222);
2611        }
2612        // Both leases released on drop.
2613        assert_eq!(borrows.len(), 0);
2614
2615        let a = account.segment_ref::<[u8; 8]>(&mut borrows, A, 8).unwrap();
2616        assert_eq!(from_le_u64(*a), 111);
2617        drop(a);
2618        let b = account.segment_ref::<[u8; 8]>(&mut borrows, B, 8).unwrap();
2619        assert_eq!(from_le_u64(*b), 222);
2620    }
2621
2622    #[test]
2623    fn split_segments_mut_rejects_overlap_and_rolls_back() {
2624        let (_backing, account) = make_account(TestLayout::SIZE, 44);
2625        {
2626            let mut data = account.try_borrow_mut().unwrap();
2627            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2628        }
2629        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2630        const A: u32 = HopperHeader::SIZE as u32;
2631
2632        // Overlapping ranges must be rejected, and every partial lease
2633        // from the batch must be rolled back (registry left empty).
2634        let err = account
2635            .split_segments_mut::<[u8; 8], 2>(&mut borrows, [(A, 8), (A + 4, 8)])
2636            .unwrap_err();
2637        assert_eq!(err, ProgramError::AccountBorrowFailed);
2638        assert_eq!(borrows.len(), 0);
2639
2640        // Out-of-bounds range is rejected too, with rollback.
2641        let err = account
2642            .split_segments_mut::<[u8; 8], 2>(&mut borrows, [(A, 8), (9_000, 8)])
2643            .unwrap_err();
2644        assert_eq!(err, ProgramError::AccountDataTooSmall);
2645        assert_eq!(borrows.len(), 0);
2646    }
2647
2648    #[test]
2649    fn typed_segment_api_round_trips() {
2650        use crate::segment::TypedSegment;
2651
2652        let (_backing, account) = make_account(TestLayout::SIZE, 22);
2653        {
2654            let mut data = account.try_borrow_mut().unwrap();
2655            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2656        }
2657
2658        const A_TYPED: TypedSegment<[u8; 8], { crate::layout::HopperHeader::SIZE as u32 }> =
2659            TypedSegment::new();
2660
2661        // With RAII leases, a single registry suffices for
2662        // sequential write-then-read. The write lease auto-releases on
2663        // scope exit, so the read is free to acquire the same region.
2664        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2665        {
2666            let mut a = account
2667                .segment_mut_typed::<[u8; 8], { crate::layout::HopperHeader::SIZE as u32 }>(
2668                    &mut borrows,
2669                    A_TYPED,
2670                )
2671                .unwrap();
2672            *a = le_u64(1337);
2673        }
2674        assert_eq!(borrows.len(), 0);
2675
2676        let read = account
2677            .segment_ref_typed::<[u8; 8], { crate::layout::HopperHeader::SIZE as u32 }>(
2678                &mut borrows,
2679                A_TYPED,
2680            )
2681            .unwrap();
2682        assert_eq!(from_le_u64(*read), 1337);
2683    }
2684
2685    #[test]
2686    fn const_segment_api_matches_manual_offsets() {
2687        use crate::segment::Segment;
2688
2689        let (_backing, account) = make_account(TestLayout::SIZE, 20);
2690        {
2691            let mut data = account.try_borrow_mut().unwrap();
2692            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2693        }
2694
2695        // Two ways of spelling the same access: manual (abs_offset, size)
2696        // vs a const Segment. The const form should behave identically.
2697        // With RAII leases, one registry handles the full sequence.
2698        const A_SEG: Segment = Segment::body(0, 8); // TestLayout.a
2699        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2700        {
2701            let mut a = account
2702                .segment_mut_const::<[u8; 8]>(&mut borrows, A_SEG)
2703                .unwrap();
2704            *a = le_u64(7);
2705        }
2706        let read = account
2707            .segment_ref::<[u8; 8]>(&mut borrows, crate::layout::HopperHeader::SIZE as u32, 8)
2708            .unwrap();
2709        assert_eq!(from_le_u64(*read), 7);
2710    }
2711
2712    #[test]
2713    fn load_after_segment_drop_succeeds() {
2714        let (_backing, account) = make_account(TestLayout::SIZE, 12);
2715        {
2716            let mut data = account.try_borrow_mut().unwrap();
2717            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2718        }
2719
2720        let mut borrows = crate::segment_borrow::SegmentBorrowRegistry::new();
2721        {
2722            let mut seg = account
2723                .segment_mut::<[u8; 8]>(&mut borrows, crate::layout::HopperHeader::SIZE as u32, 8)
2724                .unwrap();
2725            *seg = le_u64(42);
2726        }
2727        // Segment borrow released, load_mut should now succeed.
2728        let view = account.load::<TestLayout>().unwrap();
2729        assert_eq!(from_le_u64(view.a), 42);
2730    }
2731
2732    /// `zero_range` demands authority over EXACTLY the bytes it clears,
2733    /// not the whole account. This is what lets a narrow declaration
2734    /// zero-fill inside its own grant (the `realloc_zero` lifecycle on a
2735    /// `tail(seq)` account); a whole-account borrow would be refused by
2736    /// the account's own tail-only policy.
2737    #[test]
2738    #[cfg(not(feature = "unguarded-raw-surfaces"))]
2739    fn zero_range_is_gated_over_exactly_the_cleared_bytes() {
2740        use crate::write_policy::{
2741            install_lamport_gate, write_policy_violation, WritePolicy, WriteRange,
2742        };
2743
2744        let (_b0, a0) = make_account(32, 70);
2745        let accounts = [a0];
2746        // Tail-only grant: bytes [16, +inf) are writable, the head is not.
2747        static TAIL: WritePolicy = WritePolicy::new(&[WriteRange::tail_from(0, 16)]);
2748
2749        {
2750            let mut data = accounts[0].try_borrow_mut().unwrap();
2751            for byte in data.iter_mut() {
2752                *byte = 0xAA;
2753            }
2754        }
2755
2756        let _gate = install_lamport_gate(&accounts, &TAIL);
2757
2758        // Inside the grant: permitted, and it really clears those bytes.
2759        assert!(accounts[0].zero_range(16, 16).is_ok());
2760        // Straddling the head boundary: refused (bytes 8..16 are undeclared).
2761        assert_eq!(
2762            accounts[0].zero_range(8, 16),
2763            Err(write_policy_violation(0)),
2764        );
2765        // Entirely in the head: refused.
2766        assert_eq!(accounts[0].zero_range(0, 8), Err(write_policy_violation(0)));
2767        // Empty range: no authority required, no-op.
2768        assert!(accounts[0].zero_range(0, 0).is_ok());
2769        // Past the end: bounds error, never a silent truncation.
2770        assert_eq!(
2771            accounts[0].zero_range(24, 16),
2772            Err(ProgramError::AccountDataTooSmall),
2773        );
2774
2775        drop(_gate);
2776        let data = accounts[0].try_borrow().unwrap();
2777        assert!(
2778            data[16..32].iter().all(|b| *b == 0),
2779            "the authorized range was actually cleared"
2780        );
2781        assert!(
2782            data[0..16].iter().all(|b| *b == 0xAA),
2783            "refused ranges left the head untouched"
2784        );
2785    }
2786
2787    /// `zero_appended` clears only bytes a grow created, under the same
2788    /// TRANSITION authority the resize required; so the `realloc_zero`
2789    /// lifecycle works under a narrow `mut(seg)` grant (whose ranges
2790    /// cannot cover bytes that did not exist when it was written), while
2791    /// an account the instruction has no data authority over is still
2792    /// refused. Pins the boundary: it must not become a whole-account
2793    /// write hatch.
2794    #[test]
2795    #[cfg(not(feature = "unguarded-raw-surfaces"))]
2796    fn zero_appended_rides_the_transition_authority_not_the_byte_ranges() {
2797        use crate::write_policy::{
2798            install_lamport_gate, write_policy_violation, WritePolicy, WriteRange,
2799        };
2800
2801        let (_b0, a0) = make_account(32, 72);
2802        let (_bf, foreign) = make_account(32, 73);
2803        let accounts = [a0];
2804        // A NARROW head-only grant: bytes [0,8) only. Nothing declares the
2805        // region past 16, exactly the realloc-appended shape.
2806        static NARROW: WritePolicy = WritePolicy::new(&[WriteRange::new(0, 0, 8)]);
2807
2808        {
2809            let mut data = accounts[0].try_borrow_mut().unwrap();
2810            for byte in data.iter_mut() {
2811                *byte = 0xCC;
2812            }
2813        }
2814
2815        let _gate = install_lamport_gate(&accounts, &NARROW);
2816
2817        // Treat bytes [16, 32) as "just appended": permitted, because the
2818        // account carries declared data authority (so it could transition),
2819        // even though NO declared range covers those bytes.
2820        assert!(accounts[0].zero_appended(16).is_ok());
2821
2822        // A foreign account carries no data authority at all -> refused,
2823        // fail-closed, before touching a byte.
2824        assert_eq!(
2825            foreign.zero_appended(16),
2826            Err(write_policy_violation(u8::MAX)),
2827        );
2828
2829        // Not a whole-account hatch: a caller cannot name an offset below
2830        // the current length to clear pre-existing bytes it never grew...
2831        // the API only accepts "previous length", and a previous length at
2832        // or past the current one is a no-op.
2833        assert!(accounts[0].zero_appended(32).is_ok());
2834        assert!(accounts[0].zero_appended(64).is_ok());
2835
2836        drop(_gate);
2837        let data = accounts[0].try_borrow().unwrap();
2838        assert!(
2839            data[16..32].iter().all(|b| *b == 0),
2840            "the appended region was cleared"
2841        );
2842        assert!(
2843            data[0..16].iter().all(|b| *b == 0xCC),
2844            "the pre-existing body was untouched"
2845        );
2846    }
2847
2848    /// The extension-region borrow is checked against the installed
2849    /// ambient write policy over its EXACT range `[EXTENSION_OFFSET,
2850    /// data_len)`: a head-only declaration refuses it, a `tail_from`
2851    /// declaration (the open-ended `tail(seg)` lowering) and a
2852    /// whole-account grant both authorize it. Pins the 34c7a60 gate
2853    /// wiring, a revert to the pre-guard body (plain `try_borrow_mut`)
2854    /// or a widened check range `(0, len)` goes red here.
2855    #[test]
2856    #[cfg(not(feature = "unguarded-raw-surfaces"))]
2857    fn extension_bytes_mut_is_governed_over_its_exact_range() {
2858        use crate::write_policy::{
2859            install_lamport_gate, write_policy_violation, WritePolicy, WriteRange,
2860        };
2861
2862        const EXT_LEN: usize = 8;
2863        let (_backing, account) = make_account(TestLayout::SIZE + EXT_LEN, 60);
2864        {
2865            let mut data = account.try_borrow_mut().unwrap();
2866            crate::layout::init_header::<TestLayout>(&mut data).unwrap();
2867        }
2868        let accounts = [account];
2869
2870        // Ungated: the borrow succeeds and covers exactly the extension.
2871        {
2872            let ext = accounts[0].extension_bytes_mut::<TestLayout>().unwrap();
2873            assert_eq!(ext.len(), EXT_LEN);
2874        }
2875
2876        // Head-only declaration: the extension range is outside the
2877        // declared set, so the borrow is refused with the account's
2878        // indexed policy error BEFORE any borrow is taken.
2879        {
2880            static HEAD_ONLY: WritePolicy = WritePolicy::new(&[WriteRange::new(0, 0, 8)]);
2881            let _gate = install_lamport_gate(&accounts, &HEAD_ONLY);
2882            assert_eq!(
2883                accounts[0].extension_bytes_mut::<TestLayout>().map(|_| ()),
2884                Err(write_policy_violation(0)),
2885            );
2886        }
2887
2888        // Open-ended tail declaration from the extension offset (the
2889        // `tail(seg)` lowering): authorized.
2890        {
2891            static TAIL: WritePolicy =
2892                WritePolicy::new(&[WriteRange::tail_from(0, TestLayout::SIZE as u32)]);
2893            let _gate = install_lamport_gate(&accounts, &TAIL);
2894            let ext = accounts[0].extension_bytes_mut::<TestLayout>().unwrap();
2895            assert_eq!(ext.len(), EXT_LEN);
2896        }
2897
2898        // Whole-account grant: authorized.
2899        {
2900            static WHOLE: WritePolicy = WritePolicy::new(&[WriteRange::whole_account(0)]);
2901            let _gate = install_lamport_gate(&accounts, &WHOLE);
2902            assert!(accounts[0].extension_bytes_mut::<TestLayout>().is_ok());
2903        }
2904
2905        // Pre-existing ungated bound: an account shorter than the layout's
2906        // extension offset refuses with AccountDataTooSmall regardless of
2907        // any gate.
2908        let (_short_backing, short) = make_account(TestLayout::SIZE - 1, 61);
2909        assert_eq!(
2910            short.extension_bytes_mut::<TestLayout>().map(|_| ()),
2911            Err(ProgramError::AccountDataTooSmall),
2912        );
2913    }
2914}