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