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