Skip to main content

hopper_native/
account_view.rs

1//! RuntimeAccount memory layout and AccountView zero-copy wrapper.
2//!
3//! `RuntimeAccount` maps 1:1 onto the BPF input buffer layout that the
4//! Solana runtime writes for each account. `AccountView` is a thin
5//! pointer to a `RuntimeAccount` in that buffer, providing safe accessors
6//! for address, owner, flags, lamports, and data.
7
8use core::marker::PhantomData;
9
10use crate::address::{address_eq, Address};
11use crate::borrow::{Ref, RefMut};
12use crate::error::ProgramError;
13use crate::raw_account::RuntimeAccount;
14use crate::{ProgramResult, MAX_PERMITTED_DATA_INCREASE, NOT_BORROWED};
15
16// ── AccountView ──────────────────────────────────────────────────────
17
18/// Zero-copy view over a Solana account in the BPF input buffer.
19///
20/// `AccountView` stores a raw pointer to the `RuntimeAccount` header.
21/// All accessor methods read directly from the input buffer with no copies.
22#[repr(C)]
23#[cfg_attr(feature = "copy", derive(Copy))]
24#[derive(Clone, PartialEq, Eq)]
25pub struct AccountView<'info> {
26    raw: *mut RuntimeAccount,
27    _marker: PhantomData<&'info RuntimeAccount>,
28}
29
30// SAFETY: On Solana execution is single-threaded. Host tools and fuzzers
31// should not rely on cross-thread sharing of raw account pointers.
32#[cfg(target_os = "solana")]
33unsafe impl<'info> Send for AccountView<'info> {}
34#[cfg(target_os = "solana")]
35unsafe impl<'info> Sync for AccountView<'info> {}
36
37impl<'info> AccountView<'info> {
38    /// Construct an AccountView from a raw pointer.
39    ///
40    /// # Safety
41    ///
42    /// `raw` must point to a valid `RuntimeAccount` in the BPF input buffer
43    /// (or a test allocation with the same layout), followed by at least
44    /// `(*raw).data_len` bytes of account data. Before any safe resize method
45    /// is called, the four-byte `resize_delta` ABI slot must contain the
46    /// little-endian data length from instruction entry. Hopper's entrypoint
47    /// parsers establish that baseline before returning a view; manual test or
48    /// harness allocations must populate it themselves.
49    #[inline(always)]
50    pub const unsafe fn new_unchecked(raw: *mut RuntimeAccount) -> Self {
51        Self {
52            raw,
53            _marker: PhantomData,
54        }
55    }
56
57    #[inline(always)]
58    pub(crate) const fn raw_ptr(&self) -> *mut RuntimeAccount {
59        self.raw
60    }
61
62    // ── Getters ──────────────────────────────────────────────────────
63
64    /// The account's public key.
65    #[inline(always)]
66    pub fn address(&self) -> &Address {
67        // SAFETY: raw always points to a valid RuntimeAccount.
68        unsafe { &(*self.raw).address }
69    }
70
71    /// The owning program's address.
72    ///
73    /// # Safety
74    ///
75    /// The returned reference is invalidated if the account is assigned
76    /// to a new owner or closed. The caller must ensure no concurrent
77    /// mutation occurs.
78    #[inline(always)]
79    pub unsafe fn owner(&self) -> &Address {
80        // SAFETY: raw is valid; caller promises no concurrent mutation.
81        unsafe { &(*self.raw).owner }
82    }
83
84    /// Whether this account signed the transaction.
85    #[inline(always)]
86    pub fn is_signer(&self) -> bool {
87        // SAFETY: raw is valid.
88        unsafe { (*self.raw).is_signer != 0 }
89    }
90
91    /// Whether this account is writable in the transaction.
92    #[inline(always)]
93    pub fn is_writable(&self) -> bool {
94        // 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.
95        unsafe { (*self.raw).is_writable != 0 }
96    }
97
98    /// Whether this account contains an executable program.
99    #[inline(always)]
100    pub fn executable(&self) -> bool {
101        // 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.
102        unsafe { (*self.raw).executable != 0 }
103    }
104
105    /// Current data length in bytes.
106    #[inline(always)]
107    pub fn data_len(&self) -> usize {
108        // 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.
109        unsafe { (*self.raw).data_len as usize }
110    }
111
112    /// Original data length captured by the entrypoint for this invocation.
113    ///
114    /// Solana reserves the four bytes at header offset 4 for this value. It
115    /// must remain unchanged across local resizes and CPI so every resize is
116    /// checked against one invocation-wide baseline.
117    #[inline(always)]
118    pub fn original_data_len(&self) -> usize {
119        // SAFETY: `raw` is valid. The entrypoint initializes this ABI padding
120        // slot from `data_len` before making the account view available.
121        u32::from_le(unsafe { (*self.raw).resize_delta }) as usize
122    }
123
124    /// Difference between the current and original data length.
125    #[inline(always)]
126    pub fn resize_delta(&self) -> i32 {
127        (self.data_len() as i64 - self.original_data_len() as i64) as i32
128    }
129
130    /// Capture the invocation-wide resize baseline in the ABI padding slot.
131    ///
132    /// # Safety
133    ///
134    /// This must run exactly while materializing a canonical account from the
135    /// loader input, before that account can be resized locally or through CPI.
136    #[inline(always)]
137    pub(crate) unsafe fn initialize_original_data_len(&self) {
138        // SAFETY: the caller guarantees `raw` is a canonical loader account
139        // header and initialization happens before the view escapes.
140        unsafe {
141            (*self.raw).resize_delta = ((*self.raw).data_len as u32).to_le();
142        }
143    }
144
145    /// Current lamport balance.
146    #[inline(always)]
147    pub fn lamports(&self) -> u64 {
148        // 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.
149        unsafe { (*self.raw).lamports }
150    }
151
152    /// Whether the account data is empty (data_len == 0).
153    #[inline(always)]
154    pub fn is_data_empty(&self) -> bool {
155        self.data_len() == 0
156    }
157
158    /// Set the lamport balance.
159    #[inline(always)]
160    pub fn set_lamports(&self, lamports: u64) {
161        // 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.
162        unsafe {
163            (*self.raw).lamports = lamports;
164        }
165    }
166
167    // ── Ownership ────────────────────────────────────────────────────
168
169    /// Check whether this account is owned by the given program.
170    #[inline(always)]
171    pub fn owned_by(&self, program: &Address) -> bool {
172        // SAFETY: owner field is valid for the lifetime of the input buffer.
173        unsafe { address_eq(&(*self.raw).owner, program) }
174    }
175
176    /// Assign a new owner.
177    ///
178    /// # Safety
179    ///
180    /// The caller must ensure the account is writable and that ownership
181    /// transfer is authorized by the current owner program.
182    #[inline(always)]
183    pub unsafe fn assign(&self, new_owner: &Address) {
184        // 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.
185        unsafe {
186            (*self.raw).owner = new_owner.clone();
187        }
188    }
189
190    // ── Borrow tracking ─────────────────────────────────────────────
191
192    /// Whether the account data is currently borrowed (shared or exclusive).
193    #[inline(always)]
194    pub fn is_borrowed(&self) -> bool {
195        // 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.
196        unsafe { (*self.raw).borrow_state != NOT_BORROWED }
197    }
198
199    /// Whether the account data is exclusively (mutably) borrowed.
200    #[inline(always)]
201    pub fn is_borrowed_mut(&self) -> bool {
202        // 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.
203        unsafe { (*self.raw).borrow_state == 0 }
204    }
205
206    /// Check that the account can be shared-borrowed.
207    #[inline(always)]
208    pub fn check_borrow(&self) -> Result<(), ProgramError> {
209        // 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.
210        let state = unsafe { (*self.raw).borrow_state };
211        if state == 0 {
212            // Exclusively borrowed -- cannot share.
213            Err(ProgramError::AccountBorrowFailed)
214        } else {
215            Ok(())
216        }
217    }
218
219    /// Check that the account can be exclusively borrowed.
220    #[inline(always)]
221    pub fn check_borrow_mut(&self) -> Result<(), ProgramError> {
222        // 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.
223        let state = unsafe { (*self.raw).borrow_state };
224        if state != NOT_BORROWED {
225            // Already borrowed (shared or exclusive).
226            Err(ProgramError::AccountBorrowFailed)
227        } else {
228            Ok(())
229        }
230    }
231
232    /// Acquire a shared data borrow, returning the borrow-state pointer for a
233    /// [`Ref`] guard to release on drop.
234    ///
235    /// Mirrors the `try_borrow` state transition (increment with the 254 cap
236    /// so the count can never wrap into a sentinel) without materializing the
237    /// data slice; used by projection/lens paths that form their own typed
238    /// reference into the data region.
239    #[inline(always)]
240    pub(crate) fn acquire_shared(&self) -> Result<*mut u8, ProgramError> {
241        self.check_borrow()?;
242        // SAFETY: `self.raw` is a valid `RuntimeAccount`; `borrow_state` is its
243        // first byte. Taking a `*mut u8` to it creates no aliasing reference.
244        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
245        // SAFETY: read/write of the borrow byte on the single-threaded SVM,
246        // after `check_borrow` confirmed a shared borrow is compatible.
247        let state = unsafe { *state_ptr };
248        let new_state = if state == NOT_BORROWED { 1 } else { state + 1 };
249        if new_state == 0 || new_state == NOT_BORROWED {
250            // See `try_borrow`: cap the shared count at 254.
251            return Err(ProgramError::AccountBorrowFailed);
252        }
253        // SAFETY: as above; the new count was just validated.
254        unsafe {
255            *state_ptr = new_state;
256        }
257        Ok(state_ptr)
258    }
259
260    // ── Unchecked data access ────────────────────────────────────────
261
262    /// Borrow account data without borrow tracking.
263    ///
264    /// # Safety
265    ///
266    /// The caller must ensure no mutable borrow is active.
267    #[inline(always)]
268    pub unsafe fn borrow_unchecked(&self) -> &[u8] {
269        let data_ptr = self.data_ptr_unchecked();
270        let len = self.data_len();
271        // 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.
272        unsafe { core::slice::from_raw_parts(data_ptr, len) }
273    }
274
275    /// Mutably borrow account data without borrow tracking.
276    ///
277    /// # Safety
278    ///
279    /// The caller must ensure no other borrows (shared or exclusive) are active.
280    //
281    // `mut_from_ref` fires because this returns `&mut [u8]` from `&self`. That
282    // is intentional: account data lives behind a raw pointer the SVM owns, so
283    // the `AccountView` only models shared access to that region while exposing
284    // interior mutability through the documented `unsafe` contract above
285    // (Pinocchio uses the same shape). Aliasing is the caller's invariant, not
286    // the borrow checker's, that is exactly what the `unsafe` marker conveys.
287    #[allow(clippy::mut_from_ref)]
288    #[inline(always)]
289    pub unsafe fn borrow_unchecked_mut(&self) -> &mut [u8] {
290        let data_ptr = self.data_ptr_unchecked();
291        let len = self.data_len();
292        // SAFETY: `data_ptr_unchecked()` and `data_len()` describe the exact
293        // SVM-owned data region for this account, and the caller guarantees no
294        // overlapping borrow is live for the returned lifetime.
295        unsafe { core::slice::from_raw_parts_mut(data_ptr, len) }
296    }
297
298    // ── Checked data access ──────────────────────────────────────────
299
300    /// Try to obtain a shared borrow of the account data.
301    ///
302    /// Returns `Err(AccountBorrowFailed)` if the data is exclusively borrowed.
303    #[inline(always)]
304    pub fn try_borrow(&self) -> Result<Ref<'_, [u8]>, ProgramError> {
305        self.check_borrow()?;
306        // SAFETY: `self.raw` is a valid `RuntimeAccount` for this account
307        // (entrypoint invariant); `borrow_state` is its first byte. Taking a
308        // `*mut u8` to it does not create an aliasing reference.
309        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
310        // SAFETY: `state_ptr` points at this account's borrow-state byte and is
311        // only read after `check_borrow()` confirmed the borrow is compatible.
312        let state = unsafe { *state_ptr };
313        let new_state = if state == NOT_BORROWED { 1 } else { state + 1 };
314        if new_state == 0 || new_state == NOT_BORROWED {
315            // `0` would alias the exclusive-borrow sentinel; `NOT_BORROWED`
316            // (0xFF) would silently reset tracking on the 255th concurrent
317            // shared borrow, after which a mutable borrow could be granted
318            // while shared refs are still live. Cap the count at 254.
319            return Err(ProgramError::AccountBorrowFailed);
320        }
321        // SAFETY: single-threaded SVM execution; we hold the only path that
322        // writes this byte and have just validated the new shared count.
323        unsafe {
324            *state_ptr = new_state;
325        }
326        // SAFETY: the shared count was incremented above, so no exclusive
327        // borrow is outstanding; the returned `Ref` decrements it on drop.
328        let data = unsafe { self.borrow_unchecked() };
329        Ok(Ref::new(data, state_ptr))
330    }
331
332    /// Try to obtain an exclusive (mutable) borrow of the account data.
333    ///
334    /// Returns `Err(AccountBorrowFailed)` if the data is already borrowed.
335    #[inline(always)]
336    pub fn try_borrow_mut(&self) -> Result<RefMut<'_, [u8]>, ProgramError> {
337        self.check_borrow_mut()?;
338        // SAFETY: `self.raw` is a valid `RuntimeAccount`; `borrow_state` is its
339        // first byte. The `*mut u8` does not create an aliasing reference.
340        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
341        // SAFETY: `check_borrow_mut()` confirmed the account was NOT_BORROWED,
342        // so writing the exclusive sentinel (0) cannot stomp a live borrow.
343        unsafe {
344            *state_ptr = 0;
345        } // Mark exclusive.
346          // SAFETY: state is now exclusive, so no other borrow is live; the
347          // returned `RefMut` restores NOT_BORROWED on drop.
348        let data = unsafe { self.borrow_unchecked_mut() };
349        Ok(RefMut::new(data, state_ptr))
350    }
351
352    // ── Typed segment and raw access ───────────────────────────────
353
354    /// Project a typed segment from account data with native borrow tracking.
355    #[inline(always)]
356    pub fn segment_ref<T: crate::pod::Pod>(
357        &self,
358        offset: u32,
359        size: u32,
360    ) -> Result<Ref<'_, T>, ProgramError> {
361        let expected_size = core::mem::size_of::<T>() as u32;
362        if size != expected_size {
363            return Err(ProgramError::InvalidArgument);
364        }
365
366        let end = offset
367            .checked_add(size)
368            .ok_or(ProgramError::ArithmeticOverflow)?;
369        if end as usize > self.data_len() {
370            return Err(ProgramError::AccountDataTooSmall);
371        }
372
373        self.check_borrow()?;
374        // 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.
375        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
376        let state = unsafe { *state_ptr };
377        let new_state = if state == NOT_BORROWED { 1 } else { state + 1 };
378        if new_state == 0 || new_state == NOT_BORROWED {
379            // See `try_borrow`: cap the shared count at 254 so it can never
380            // wrap into the NOT_BORROWED sentinel.
381            return Err(ProgramError::AccountBorrowFailed);
382        }
383        // 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.
384        unsafe {
385            *state_ptr = new_state;
386        }
387
388        // 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.
389        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *const T };
390        Ok(Ref::new(unsafe { &*ptr }, state_ptr))
391    }
392
393    /// Acquire a shared segment borrow without size/bounds validation.
394    ///
395    /// # Safety
396    ///
397    /// The caller must have already verified:
398    /// - `offset + size_of::<T>()` does not overflow
399    /// - `offset + size_of::<T>() <= data_len()`
400    /// - no exclusive borrow overlapping `[offset, offset + size_of::<T>())`
401    ///   is live for the returned reference's lifetime (this method performs
402    ///   no borrow tracking)
403    #[inline(always)]
404    pub unsafe fn segment_ref_unchecked<T: crate::pod::Pod>(
405        &self,
406        offset: u32,
407    ) -> Result<Ref<'_, T>, ProgramError> {
408        // 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.
409        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *const T };
410        Ok(Ref::new_external(unsafe { &*ptr }))
411    }
412
413    /// Project a mutable typed segment from account data with native borrow tracking.
414    #[inline(always)]
415    pub fn segment_mut<T: crate::pod::Pod>(
416        &self,
417        offset: u32,
418        size: u32,
419    ) -> Result<RefMut<'_, T>, ProgramError> {
420        self.require_writable()?;
421
422        let expected_size = core::mem::size_of::<T>() as u32;
423        if size != expected_size {
424            return Err(ProgramError::InvalidArgument);
425        }
426
427        let end = offset
428            .checked_add(size)
429            .ok_or(ProgramError::ArithmeticOverflow)?;
430        if end as usize > self.data_len() {
431            return Err(ProgramError::AccountDataTooSmall);
432        }
433
434        self.check_borrow_mut()?;
435        // 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.
436        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
437        unsafe {
438            *state_ptr = 0;
439        }
440
441        // 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.
442        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *mut T };
443        Ok(RefMut::new(unsafe { &mut *ptr }, state_ptr))
444    }
445
446    /// Acquire an exclusive segment borrow without size/bounds/writable validation.
447    ///
448    /// # Safety
449    ///
450    /// The caller must have already verified:
451    /// - The account is writable
452    /// - `offset + size_of::<T>()` does not overflow
453    /// - `offset + size_of::<T>() <= data_len()`
454    /// - no other borrow (shared or exclusive) overlapping
455    ///   `[offset, offset + size_of::<T>())` is live for the returned
456    ///   reference's lifetime (this method performs no borrow tracking)
457    #[inline(always)]
458    pub unsafe fn segment_mut_unchecked<T: crate::pod::Pod>(
459        &self,
460        offset: u32,
461    ) -> Result<RefMut<'_, T>, ProgramError> {
462        // 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.
463        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *mut T };
464        Ok(RefMut::new_external(unsafe { &mut *ptr }))
465    }
466
467    /// Explicit raw typed read of the account buffer.
468    #[inline(always)]
469    ///
470    /// # Safety
471    ///
472    /// Caller must uphold the invariants documented for this unsafe API before invoking it.
473    pub unsafe fn raw_ref<T: crate::pod::Pod>(&self) -> Result<Ref<'_, T>, ProgramError> {
474        self.segment_ref::<T>(0, core::mem::size_of::<T>() as u32)
475    }
476
477    /// Explicit raw typed write of the account buffer.
478    #[inline(always)]
479    ///
480    /// # Safety
481    ///
482    /// Caller must uphold the invariants documented for this unsafe API before invoking it.
483    pub unsafe fn raw_mut<T: crate::pod::Pod>(&self) -> Result<RefMut<'_, T>, ProgramError> {
484        self.segment_mut::<T>(0, core::mem::size_of::<T>() as u32)
485    }
486
487    // ── Resize ───────────────────────────────────────────────────────
488
489    /// Check every precondition of [`resize`](Self::resize) without changing
490    /// the account: the account must be writable, no data borrow may be live,
491    /// and `new_len` may exceed the entry-time length by at most
492    /// [`MAX_PERMITTED_DATA_INCREASE`]. A no-op resize to the current length
493    /// always passes.
494    ///
495    /// Callers that move lamports before resizing (rent top-ups) run this
496    /// first so a refused resize cannot leave the transfer behind.
497    #[inline(always)]
498    pub fn check_resize(&self, new_len: usize) -> Result<(), ProgramError> {
499        if new_len == self.data_len() {
500            return Ok(());
501        }
502        self.require_writable()?;
503        self.check_borrow_mut()?;
504        if new_len.saturating_sub(self.original_data_len()) > MAX_PERMITTED_DATA_INCREASE {
505            return Err(ProgramError::InvalidRealloc);
506        }
507        Ok(())
508    }
509
510    /// Resize the account data to `new_len` bytes, zeroing any newly
511    /// exposed region.
512    ///
513    /// Returns `Err(InvalidRealloc)` if the new length exceeds the
514    /// permitted increase from the original allocation.
515    ///
516    /// When the account grows, the bytes in `[old_len, new_len)` are
517    /// zero-filled. The Solana loader zeroes the realloc reserve once at
518    /// the start of an instruction, but a shrink-then-grow within a
519    /// single instruction can re-expose previously written bytes; zeroing
520    /// on growth makes that impossible. Use [`resize_raw`](Self::resize_raw)
521    /// for the hot path when the caller will overwrite the grown region
522    /// in full and has measured the saved `memset`.
523    #[inline(always)]
524    pub fn resize(&self, new_len: usize) -> Result<(), ProgramError> {
525        let old_len = self.data_len();
526        if new_len == old_len {
527            return Ok(());
528        }
529
530        self.check_resize(new_len)?;
531        // SAFETY: `data_ptr_unchecked()` is the account data base; the loader
532        // guarantees `[old_len, new_len)` is within the realloc-reserve
533        // capacity once the `InvalidRealloc` bound above has passed.
534        unsafe {
535            if new_len > old_len {
536                crate::mem::memset(self.data_ptr_unchecked().add(old_len), 0, new_len - old_len);
537            }
538            (*self.raw).data_len = new_len as u64;
539        }
540        Ok(())
541    }
542
543    /// Resize without zero-filling the newly exposed region.
544    ///
545    /// Same bounds check as [`resize`](Self::resize) but skips the
546    /// zero-fill on growth. Prefer `resize` unless the caller immediately
547    /// overwrites the entire grown region; otherwise stale bytes from an
548    /// earlier shrink within the same instruction can leak into the new
549    /// region.
550    #[inline(always)]
551    pub fn resize_raw(&self, new_len: usize) -> Result<(), ProgramError> {
552        if new_len == self.data_len() {
553            return Ok(());
554        }
555
556        self.check_resize(new_len)?;
557        // SAFETY: bounds validated above; only header fields are written.
558        unsafe {
559            (*self.raw).data_len = new_len as u64;
560        }
561        Ok(())
562    }
563
564    /// Resize without bounds checking or zero-filling.
565    ///
566    /// # Safety
567    ///
568    /// The caller must guarantee that the account is writable, no data borrow
569    /// is live, and
570    /// `new_len.saturating_sub(original_data_len) <= MAX_PERMITTED_DATA_INCREASE`.
571    /// The caller is also responsible for any zero-fill of the grown region
572    /// (see [`resize`](Self::resize) for why that matters).
573    #[inline(always)]
574    pub unsafe fn resize_unchecked(&self, new_len: usize) {
575        // 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.
576        unsafe {
577            (*self.raw).data_len = new_len as u64;
578        }
579    }
580
581    // ── Close ────────────────────────────────────────────────────────
582
583    /// Solana System Program address (all-zero pubkey).
584    ///
585    /// Closing an account transfers ownership back to the System
586    /// Program, which is the canonical "no-owner" state on Solana.
587    /// The byte value `[0u8; 32]` and `Address::default()` are
588    /// equivalent, but using this named constant makes the intent
589    /// explicit and avoids the ambiguous `Address::default()` spelling.
590    pub const SYSTEM_PROGRAM_ID: Address = Address::new_from_array([0u8; 32]);
591
592    /// Close the account: zero lamports and data, reassign owner to
593    /// the System Program.
594    ///
595    /// Fails with `AccountBorrowFailed` if any data borrow (shared or
596    /// exclusive) is outstanding: closing memsets the entire data region,
597    /// which would mutate memory a live `Ref`/`RefMut` still points at.
598    /// Use [`close_unchecked`](Self::close_unchecked) only when the caller
599    /// can prove no borrow is live.
600    ///
601    /// # Caveat
602    ///
603    /// This low-level routine does **not** verify the caller has
604    /// ownership or application authority to close the account. Writability
605    /// and outstanding data borrows are checked locally. Solana's runtime
606    /// also enforces account modification rules, but
607    /// higher-level APIs (e.g. `hopper_runtime::AccountView::close_to`)
608    /// should pre-check those rules. See `account.rs::close_to` for
609    /// the safe wrapper.
610    #[inline(always)]
611    pub fn close(&self) -> ProgramResult {
612        // Zeroing the data region below would mutate bytes a live borrow
613        // still references; refuse rather than invalidate it.
614        self.require_writable()?;
615        self.check_borrow_mut()?;
616        self.set_lamports(0);
617        // SAFETY: no data borrow is outstanding (checked above); `data_ptr_unchecked`
618        // and `data_len` describe this account's SVM-owned data region.
619        unsafe {
620            let len = self.data_len();
621            if len > 0 {
622                // Use the SVM's JIT-compiled memset for optimal CU cost.
623                crate::mem::memset(self.data_ptr_unchecked(), 0, len);
624            }
625            (*self.raw).data_len = 0;
626            (*self.raw).owner = Self::SYSTEM_PROGRAM_ID;
627        }
628        Ok(())
629    }
630
631    /// Close without borrow checks.
632    ///
633    /// # Safety
634    ///
635    /// The caller must ensure no active borrows exist.
636    #[inline(always)]
637    pub unsafe fn close_unchecked(&self) {
638        // 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.
639        unsafe {
640            (*self.raw).lamports = 0;
641            (*self.raw).data_len = 0;
642            (*self.raw).owner = Self::SYSTEM_PROGRAM_ID;
643        }
644    }
645
646    // ── Raw pointers ─────────────────────────────────────────────────
647
648    /// Raw pointer to the `RuntimeAccount` header.
649    #[inline(always)]
650    pub const fn account_ptr(&self) -> *const RuntimeAccount {
651        self.raw as *const RuntimeAccount
652    }
653
654    /// Raw pointer to the first byte of account data.
655    ///
656    /// The data starts immediately after the 88-byte `RuntimeAccount` header.
657    /// This is an expert-only substrate escape hatch: constructing the pointer
658    /// is safe, but dereferencing it is unsafe and bypasses Hopper Native's
659    /// borrow-state checks, segment registry, and writable checks. Normal code
660    /// should use `try_borrow`, `try_borrow_mut`, `segment_ref`, or
661    /// `segment_mut`. Framework code should route user-facing raw access
662    /// through the documented unsafe runtime APIs (`Context::as_mut_ptr` /
663    /// `Context::as_ptr`) instead of exposing this method directly.
664    #[doc(hidden)]
665    #[inline(always)]
666    pub fn data_ptr_unchecked(&self) -> *mut u8 {
667        // SAFETY: Adding the struct size to the base pointer yields the
668        // first data byte. The runtime guarantees this memory is valid.
669        unsafe { (self.raw as *mut u8).add(core::mem::size_of::<RuntimeAccount>()) }
670    }
671
672    // ── Hopper Innovations ───────────────────────────────────────────
673
674    /// Validate that this account is a signer, returning a typed error.
675    #[inline(always)]
676    pub fn require_signer(&self) -> ProgramResult {
677        if self.is_signer() {
678            Ok(())
679        } else {
680            Err(ProgramError::MissingRequiredSignature)
681        }
682    }
683
684    /// Validate that this account is writable.
685    #[inline(always)]
686    pub fn require_writable(&self) -> ProgramResult {
687        if self.is_writable() {
688            Ok(())
689        } else {
690            Err(ProgramError::Immutable)
691        }
692    }
693
694    /// Validate that this account is owned by the given program.
695    #[inline(always)]
696    pub fn require_owned_by(&self, program: &Address) -> ProgramResult {
697        if self.owned_by(program) {
698            Ok(())
699        } else {
700            Err(ProgramError::IncorrectProgramId)
701        }
702    }
703
704    /// Validate signer + writable (common "payer" pattern).
705    #[inline(always)]
706    pub fn require_payer(&self) -> ProgramResult {
707        self.require_signer()?;
708        self.require_writable()
709    }
710
711    /// Read the Hopper account discriminator (first byte of data).
712    ///
713    /// Returns 0 if the account has no data.
714    #[inline(always)]
715    pub fn disc(&self) -> u8 {
716        if self.data_len() == 0 {
717            return 0;
718        }
719        // 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.
720        unsafe { *self.data_ptr_unchecked() }
721    }
722
723    /// Read the Hopper account version (second byte of data).
724    ///
725    /// Returns 0 if the account has fewer than 2 bytes.
726    #[inline(always)]
727    pub fn version(&self) -> u8 {
728        if self.data_len() < 2 {
729            return 0;
730        }
731        // 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.
732        unsafe { *self.data_ptr_unchecked().add(1) }
733    }
734
735    /// Read the 8-byte layout_id from the Hopper account header
736    /// (bytes 4..12 of account data, per the canonical header format).
737    ///
738    /// Returns `None` if the account has fewer than 12 bytes.
739    #[inline(always)]
740    pub fn layout_id(&self) -> Option<&[u8; 8]> {
741        if self.data_len() < 12 {
742            return None;
743        }
744        // 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.
745        unsafe { Some(&*(self.data_ptr_unchecked().add(4) as *const [u8; 8])) }
746    }
747
748    /// Verify that this account has the given discriminator.
749    #[inline(always)]
750    pub fn require_disc(&self, expected: u8) -> ProgramResult {
751        if self.disc() == expected {
752            Ok(())
753        } else {
754            Err(ProgramError::InvalidAccountData)
755        }
756    }
757
758    // -- Chainable validation ---------------
759    //
760    // Return `Result<&Self>` so callers can chain:
761    //
762    //   account
763    //       .check_signer()?
764    //       .check_writable()?
765    //       .check_owned_by(&MY_PROGRAM_ID)?;
766    //
767    // Validated once, used everywhere. This pattern exists in Steel but
768    // not in pinocchio, Anchor, or Quasar.
769
770    /// Chainable signer check.
771    #[inline(always)]
772    pub fn check_signer(&self) -> Result<&Self, ProgramError> {
773        if self.is_signer() {
774            Ok(self)
775        } else {
776            Err(ProgramError::MissingRequiredSignature)
777        }
778    }
779
780    /// Chainable writable check.
781    #[inline(always)]
782    pub fn check_writable(&self) -> Result<&Self, ProgramError> {
783        if self.is_writable() {
784            Ok(self)
785        } else {
786            Err(ProgramError::Immutable)
787        }
788    }
789
790    /// Chainable ownership check.
791    #[inline(always)]
792    pub fn check_owned_by(&self, program: &Address) -> Result<&Self, ProgramError> {
793        if self.owned_by(program) {
794            Ok(self)
795        } else {
796            Err(ProgramError::IncorrectProgramId)
797        }
798    }
799
800    /// Chainable discriminator check.
801    #[inline(always)]
802    pub fn check_disc(&self, expected: u8) -> Result<&Self, ProgramError> {
803        if self.disc() == expected {
804            Ok(self)
805        } else {
806            Err(ProgramError::InvalidAccountData)
807        }
808    }
809
810    /// Chainable non-empty data check.
811    #[inline(always)]
812    pub fn check_has_data(&self) -> Result<&Self, ProgramError> {
813        if !self.is_data_empty() {
814            Ok(self)
815        } else {
816            Err(ProgramError::AccountDataTooSmall)
817        }
818    }
819
820    /// Chainable executable check.
821    #[inline(always)]
822    pub fn check_executable(&self) -> Result<&Self, ProgramError> {
823        if self.executable() {
824            Ok(self)
825        } else {
826            Err(ProgramError::InvalidArgument)
827        }
828    }
829
830    /// Chainable address check.
831    #[inline(always)]
832    pub fn check_address(&self, expected: &Address) -> Result<&Self, ProgramError> {
833        if address_eq(self.address(), expected) {
834            Ok(self)
835        } else {
836            Err(ProgramError::InvalidArgument)
837        }
838    }
839
840    /// Chainable minimum data length check.
841    #[inline(always)]
842    pub fn check_data_len(&self, min_len: usize) -> Result<&Self, ProgramError> {
843        if self.data_len() >= min_len {
844            Ok(self)
845        } else {
846            Err(ProgramError::AccountDataTooSmall)
847        }
848    }
849
850    // -- Safe owner access ---------------------------------------------
851
852    /// Read the owner address as a copy (32-byte value).
853    ///
854    /// Unlike `owner()` (which is unsafe due to reference invalidation
855    /// if `assign()` is called), this returns a copy that is always safe.
856    /// Costs 32 bytes of stack space but eliminates aliasing hazards.
857    #[inline(always)]
858    pub fn read_owner(&self) -> Address {
859        // 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.
860        unsafe { (*self.raw).owner.clone() }
861    }
862
863    // -- Packed flags --------------------------------------------------
864
865    /// Read the first 4 bytes of the account header as a single u32.
866    ///
867    /// Layout (little-endian): `[borrow_state, is_signer, is_writable, executable]`
868    ///
869    /// This is the fastest way to extract multiple account properties at once
870    ///, a single aligned u32 read instead of 3-4 separate byte loads.
871    #[inline(always)]
872    fn header_u32(&self) -> u32 {
873        // SAFETY: RuntimeAccount is #[repr(C)] with first 4 bytes as
874        // u8 fields; read_unaligned imposes no alignment requirement.
875        unsafe { core::ptr::read_unaligned(self.raw as *const u32) }
876    }
877
878    /// Pack the account's boolean flags into a single byte for fast
879    /// comparison.
880    ///
881    /// Bit layout:
882    /// - bit 0: is_signer
883    /// - bit 1: is_writable
884    /// - bit 2: executable
885    /// - bit 3: has data (data_len > 0)
886    ///
887    /// Use with `expect_flags()` for single-instruction multi-check:
888    ///
889    /// ```ignore
890    /// // Require: signer + writable + has data
891    /// account.expect_flags(0b1011)?;
892    /// ```
893    #[inline(always)]
894    pub fn flags(&self) -> u8 {
895        // Single u32 read extracts [borrow_state, is_signer, is_writable, executable].
896        // On little-endian: is_signer = bits 8-15, is_writable = bits 16-23, executable = bits 24-31.
897        let h = self.header_u32();
898        let mut f: u8 = 0;
899        if h & 0x0000_FF00 != 0 {
900            f |= 0b0001;
901        } // is_signer
902        if h & 0x00FF_0000 != 0 {
903            f |= 0b0010;
904        } // is_writable
905        if h & 0xFF00_0000 != 0 {
906            f |= 0b0100;
907        } // executable
908        if !self.is_data_empty() {
909            f |= 0b1000;
910        }
911        f
912    }
913
914    /// Check that the account's flags contain all the required bits.
915    ///
916    /// `required` is a bitmask of flags that must be set. See `flags()`.
917    #[inline(always)]
918    pub fn expect_flags(&self, required: u8) -> ProgramResult {
919        if self.flags() & required == required {
920            Ok(())
921        } else {
922            Err(ProgramError::InvalidArgument)
923        }
924    }
925
926    /// Fast fused signer/writable predicate over the packed header word.
927    ///
928    /// Answers "are the requested signer/writable bytes both set" with a
929    /// single 4-byte header read and one masked compare
930    /// (`(header_u32 & mask) == expected`), never touching `data_len`, unlike
931    /// [`flags`](Self::flags), which also folds in the has-data bit via
932    /// `is_data_empty()`. `need_signer`/`need_writable` are compile-time
933    /// literals at every call site and this is `#[inline(always)]`, so `mask`
934    /// and `expected` fold to constants and the whole check is one `and` plus
935    /// one `cmp`.
936    ///
937    /// Behaviourally identical to today's `flags()`-based signer/writable
938    /// gate: the loader serializes `is_signer`/`is_writable` as exactly `0` or
939    /// `1`, so for those bytes "equals the expected `1` pattern" and
940    /// "byte non-zero" coincide. Callers that need the precise per-condition
941    /// error must fall back to `require_signer`/`require_writable` on a `false`
942    /// return (see `hopper_runtime::AccountView::expect_signer_writable`).
943    #[inline(always)]
944    pub fn is_signer_writable(&self, need_signer: bool, need_writable: bool) -> bool {
945        let h = self.header_u32();
946        let mut mask: u32 = 0;
947        let mut expected: u32 = 0;
948        if need_signer {
949            // is_signer occupies bits 8..16 (little-endian byte 1).
950            mask |= 0x0000_FF00;
951            expected |= 0x0000_0100;
952        }
953        if need_writable {
954            // is_writable occupies bits 16..24 (little-endian byte 2).
955            mask |= 0x00FF_0000;
956            expected |= 0x0001_0000;
957        }
958        (h & mask) == expected
959    }
960}
961
962impl<'info> core::fmt::Debug for AccountView<'info> {
963    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
964        f.debug_struct("AccountView")
965            .field("address", self.address())
966            .field("lamports", &self.lamports())
967            .field("data_len", &self.data_len())
968            .field("is_signer", &self.is_signer())
969            .field("is_writable", &self.is_writable())
970            .finish()
971    }
972}
973
974// ── RemainingAccounts ────────────────────────────────────────────────
975
976/// Iterator over remaining (unstructured) accounts after the known ones.
977pub struct RemainingAccounts<'a> {
978    accounts: &'a [AccountView<'a>],
979    cursor: usize,
980}
981
982impl<'a> RemainingAccounts<'a> {
983    /// Create from a slice of the remaining accounts.
984    #[inline(always)]
985    pub fn new(accounts: &'a [AccountView<'a>]) -> Self {
986        Self {
987            accounts,
988            cursor: 0,
989        }
990    }
991
992    /// Number of accounts remaining.
993    #[inline(always)]
994    pub fn remaining(&self) -> usize {
995        self.accounts.len() - self.cursor
996    }
997
998    /// Take the next account, or return `NotEnoughAccountKeys`.
999    ///
1000    /// This is a fallible cursor advance, not an `Iterator::next`: it yields a
1001    /// `Result` so a missing account is a program error rather than a silent
1002    /// `None`, which is the wrong shape for the `Iterator` trait.
1003    #[allow(clippy::should_implement_trait)]
1004    #[inline(always)]
1005    pub fn next(&mut self) -> Result<&'a AccountView<'a>, ProgramError> {
1006        if self.cursor >= self.accounts.len() {
1007            return Err(ProgramError::NotEnoughAccountKeys);
1008        }
1009        let account = &self.accounts[self.cursor];
1010        self.cursor += 1;
1011        Ok(account)
1012    }
1013
1014    /// Take the next account that is a signer.
1015    #[inline(always)]
1016    pub fn next_signer(&mut self) -> Result<&'a AccountView<'a>, ProgramError> {
1017        let account = self.next()?;
1018        account.require_signer()?;
1019        Ok(account)
1020    }
1021
1022    /// Take the next account that is writable.
1023    #[inline(always)]
1024    pub fn next_writable(&mut self) -> Result<&'a AccountView<'a>, ProgramError> {
1025        let account = self.next()?;
1026        account.require_writable()?;
1027        Ok(account)
1028    }
1029
1030    /// Take the next account owned by the given program.
1031    #[inline(always)]
1032    pub fn next_owned_by(
1033        &mut self,
1034        program: &Address,
1035    ) -> Result<&'a AccountView<'a>, ProgramError> {
1036        let account = self.next()?;
1037        account.require_owned_by(program)?;
1038        Ok(account)
1039    }
1040}