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