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 crate::address::{address_eq, Address};
9use crate::borrow::{Ref, RefMut};
10use crate::error::ProgramError;
11use crate::raw_account::RuntimeAccount;
12use crate::{ProgramResult, MAX_PERMITTED_DATA_INCREASE, NOT_BORROWED};
13
14// ── AccountView ──────────────────────────────────────────────────────
15
16/// Zero-copy view over a Solana account in the BPF input buffer.
17///
18/// `AccountView` stores a raw pointer to the `RuntimeAccount` header.
19/// All accessor methods read directly from the input buffer with no copies.
20#[repr(C)]
21#[cfg_attr(feature = "copy", derive(Copy))]
22#[derive(Clone, PartialEq, Eq)]
23pub struct AccountView {
24    raw: *mut RuntimeAccount,
25}
26
27// SAFETY: AccountView is safe to send between threads in test contexts.
28// On BPF there is only one thread.
29unsafe impl Send for AccountView {}
30unsafe impl Sync for AccountView {}
31
32impl AccountView {
33    /// Construct an AccountView from a raw pointer.
34    ///
35    /// # Safety
36    ///
37    /// `raw` must point to a valid `RuntimeAccount` in the BPF input buffer
38    /// (or a test allocation with the same layout), followed by at least
39    /// `(*raw).data_len` bytes of account data.
40    #[inline(always)]
41    pub const unsafe fn new_unchecked(raw: *mut RuntimeAccount) -> Self {
42        Self { raw }
43    }
44
45    #[inline(always)]
46    pub(crate) const fn raw_ptr(&self) -> *mut RuntimeAccount {
47        self.raw
48    }
49
50    // ── Getters ──────────────────────────────────────────────────────
51
52    /// The account's public key.
53    #[inline(always)]
54    pub fn address(&self) -> &Address {
55        // SAFETY: raw always points to a valid RuntimeAccount.
56        unsafe { &(*self.raw).address }
57    }
58
59    /// The owning program's address.
60    ///
61    /// # Safety
62    ///
63    /// The returned reference is invalidated if the account is assigned
64    /// to a new owner or closed. The caller must ensure no concurrent
65    /// mutation occurs.
66    #[inline(always)]
67    pub unsafe fn owner(&self) -> &Address {
68        // SAFETY: raw is valid; caller promises no concurrent mutation.
69        unsafe { &(*self.raw).owner }
70    }
71
72    /// Whether this account signed the transaction.
73    #[inline(always)]
74    pub fn is_signer(&self) -> bool {
75        // SAFETY: raw is valid.
76        unsafe { (*self.raw).is_signer != 0 }
77    }
78
79    /// Whether this account is writable in the transaction.
80    #[inline(always)]
81    pub fn is_writable(&self) -> bool {
82        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
83        unsafe { (*self.raw).is_writable != 0 }
84    }
85
86    /// Whether this account contains an executable program.
87    #[inline(always)]
88    pub fn executable(&self) -> bool {
89        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
90        unsafe { (*self.raw).executable != 0 }
91    }
92
93    /// Current data length in bytes.
94    #[inline(always)]
95    pub fn data_len(&self) -> usize {
96        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
97        unsafe { (*self.raw).data_len as usize }
98    }
99
100    /// Resize delta (difference between current and original data length).
101    #[inline(always)]
102    pub fn resize_delta(&self) -> i32 {
103        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
104        unsafe { (*self.raw).resize_delta }
105    }
106
107    /// Current lamport balance.
108    #[inline(always)]
109    pub fn lamports(&self) -> u64 {
110        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
111        unsafe { (*self.raw).lamports }
112    }
113
114    /// Whether the account data is empty (data_len == 0).
115    #[inline(always)]
116    pub fn is_data_empty(&self) -> bool {
117        self.data_len() == 0
118    }
119
120    /// Set the lamport balance.
121    #[inline(always)]
122    pub fn set_lamports(&self, lamports: u64) {
123        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
124        unsafe {
125            (*self.raw).lamports = lamports;
126        }
127    }
128
129    // ── Ownership ────────────────────────────────────────────────────
130
131    /// Check whether this account is owned by the given program.
132    #[inline(always)]
133    pub fn owned_by(&self, program: &Address) -> bool {
134        // SAFETY: owner field is valid for the lifetime of the input buffer.
135        unsafe { address_eq(&(*self.raw).owner, program) }
136    }
137
138    /// Assign a new owner.
139    ///
140    /// # Safety
141    ///
142    /// The caller must ensure the account is writable and that ownership
143    /// transfer is authorized by the current owner program.
144    #[inline(always)]
145    pub unsafe fn assign(&self, new_owner: &Address) {
146        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
147        unsafe {
148            (*self.raw).owner = new_owner.clone();
149        }
150    }
151
152    // ── Borrow tracking ─────────────────────────────────────────────
153
154    /// Whether the account data is currently borrowed (shared or exclusive).
155    #[inline(always)]
156    pub fn is_borrowed(&self) -> bool {
157        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
158        unsafe { (*self.raw).borrow_state != NOT_BORROWED }
159    }
160
161    /// Whether the account data is exclusively (mutably) borrowed.
162    #[inline(always)]
163    pub fn is_borrowed_mut(&self) -> bool {
164        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
165        unsafe { (*self.raw).borrow_state == 0 }
166    }
167
168    /// Check that the account can be shared-borrowed.
169    #[inline(always)]
170    pub fn check_borrow(&self) -> Result<(), ProgramError> {
171        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
172        let state = unsafe { (*self.raw).borrow_state };
173        if state == 0 {
174            // Exclusively borrowed -- cannot share.
175            Err(ProgramError::AccountBorrowFailed)
176        } else {
177            Ok(())
178        }
179    }
180
181    /// Check that the account can be exclusively borrowed.
182    #[inline(always)]
183    pub fn check_borrow_mut(&self) -> Result<(), ProgramError> {
184        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
185        let state = unsafe { (*self.raw).borrow_state };
186        if state != NOT_BORROWED {
187            // Already borrowed (shared or exclusive).
188            Err(ProgramError::AccountBorrowFailed)
189        } else {
190            Ok(())
191        }
192    }
193
194    // ── Unchecked data access ────────────────────────────────────────
195
196    /// Borrow account data without borrow tracking.
197    ///
198    /// # Safety
199    ///
200    /// The caller must ensure no mutable borrow is active.
201    #[inline(always)]
202    pub unsafe fn borrow_unchecked(&self) -> &[u8] {
203        let data_ptr = self.data_ptr_unchecked();
204        let len = self.data_len();
205        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
206        unsafe { core::slice::from_raw_parts(data_ptr, len) }
207    }
208
209    /// Mutably borrow account data without borrow tracking.
210    ///
211    /// # Safety
212    ///
213    /// The caller must ensure no other borrows (shared or exclusive) are active.
214    #[inline(always)]
215    pub unsafe fn borrow_unchecked_mut(&self) -> &mut [u8] {
216        let data_ptr = self.data_ptr_unchecked();
217        let len = self.data_len();
218        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
219        unsafe { core::slice::from_raw_parts_mut(data_ptr, len) }
220    }
221
222    // ── Checked data access ──────────────────────────────────────────
223
224    /// Try to obtain a shared borrow of the account data.
225    ///
226    /// Returns `Err(AccountBorrowFailed)` if the data is exclusively borrowed.
227    #[inline(always)]
228    pub fn try_borrow(&self) -> Result<Ref<'_, [u8]>, ProgramError> {
229        self.check_borrow()?;
230        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
231        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
232        let state = unsafe { *state_ptr };
233        let new_state = if state == NOT_BORROWED { 1 } else { state + 1 };
234        if new_state == 0 {
235            // Overflow into exclusive-borrow sentinel.
236            return Err(ProgramError::AccountBorrowFailed);
237        }
238        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
239        unsafe {
240            *state_ptr = new_state;
241        }
242        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
243        let data = unsafe { self.borrow_unchecked() };
244        Ok(Ref::new(data, state_ptr))
245    }
246
247    /// Try to obtain an exclusive (mutable) borrow of the account data.
248    ///
249    /// Returns `Err(AccountBorrowFailed)` if the data is already borrowed.
250    #[inline(always)]
251    pub fn try_borrow_mut(&self) -> Result<RefMut<'_, [u8]>, ProgramError> {
252        self.check_borrow_mut()?;
253        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
254        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
255        unsafe {
256            *state_ptr = 0;
257        } // Mark exclusive.
258          // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
259        let data = unsafe { self.borrow_unchecked_mut() };
260        Ok(RefMut::new(data, state_ptr))
261    }
262
263    // ── Typed segment and raw access ───────────────────────────────
264
265    /// Project a typed segment from account data with native borrow tracking.
266    #[inline(always)]
267    pub fn segment_ref<T: crate::pod::Pod>(
268        &self,
269        offset: u32,
270        size: u32,
271    ) -> Result<Ref<'_, T>, ProgramError> {
272        let expected_size = core::mem::size_of::<T>() as u32;
273        if size != expected_size {
274            return Err(ProgramError::InvalidArgument);
275        }
276
277        let end = offset
278            .checked_add(size)
279            .ok_or(ProgramError::ArithmeticOverflow)?;
280        if end as usize > self.data_len() {
281            return Err(ProgramError::AccountDataTooSmall);
282        }
283
284        self.check_borrow()?;
285        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
286        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
287        let state = unsafe { *state_ptr };
288        let new_state = if state == NOT_BORROWED { 1 } else { state + 1 };
289        if new_state == 0 {
290            return Err(ProgramError::AccountBorrowFailed);
291        }
292        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
293        unsafe {
294            *state_ptr = new_state;
295        }
296
297        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
298        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *const T };
299        Ok(Ref::new(unsafe { &*ptr }, state_ptr))
300    }
301
302    /// Acquire a shared segment borrow without size/bounds validation.
303    ///
304    /// # Safety
305    ///
306    /// The caller must have already verified:
307    /// - `offset + size_of::<T>()` does not overflow
308    /// - `offset + size_of::<T>() <= data_len()`
309    #[inline(always)]
310    pub unsafe fn segment_ref_unchecked<T: crate::pod::Pod>(
311        &self,
312        offset: u32,
313    ) -> Result<Ref<'_, T>, ProgramError> {
314        self.check_borrow()?;
315        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
316        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
317        let state = unsafe { *state_ptr };
318        let new_state = if state == NOT_BORROWED { 1 } else { state + 1 };
319        if new_state == 0 {
320            return Err(ProgramError::AccountBorrowFailed);
321        }
322        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
323        unsafe {
324            *state_ptr = new_state;
325        }
326
327        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
328        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *const T };
329        Ok(Ref::new(unsafe { &*ptr }, state_ptr))
330    }
331
332    /// Project a mutable typed segment from account data with native borrow tracking.
333    #[inline(always)]
334    pub fn segment_mut<T: crate::pod::Pod>(
335        &self,
336        offset: u32,
337        size: u32,
338    ) -> Result<RefMut<'_, T>, ProgramError> {
339        self.require_writable()?;
340
341        let expected_size = core::mem::size_of::<T>() as u32;
342        if size != expected_size {
343            return Err(ProgramError::InvalidArgument);
344        }
345
346        let end = offset
347            .checked_add(size)
348            .ok_or(ProgramError::ArithmeticOverflow)?;
349        if end as usize > self.data_len() {
350            return Err(ProgramError::AccountDataTooSmall);
351        }
352
353        self.check_borrow_mut()?;
354        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
355        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
356        unsafe {
357            *state_ptr = 0;
358        }
359
360        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
361        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *mut T };
362        Ok(RefMut::new(unsafe { &mut *ptr }, state_ptr))
363    }
364
365    /// Acquire an exclusive segment borrow without size/bounds/writable validation.
366    ///
367    /// # Safety
368    ///
369    /// The caller must have already verified:
370    /// - The account is writable
371    /// - `offset + size_of::<T>()` does not overflow
372    /// - `offset + size_of::<T>() <= data_len()`
373    #[inline(always)]
374    pub unsafe fn segment_mut_unchecked<T: crate::pod::Pod>(
375        &self,
376        offset: u32,
377    ) -> Result<RefMut<'_, T>, ProgramError> {
378        self.check_borrow_mut()?;
379        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
380        let state_ptr = unsafe { &mut (*self.raw).borrow_state as *mut u8 };
381        unsafe {
382            *state_ptr = 0;
383        }
384
385        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
386        let ptr = unsafe { self.data_ptr_unchecked().add(offset as usize) as *mut T };
387        Ok(RefMut::new(unsafe { &mut *ptr }, state_ptr))
388    }
389
390    /// Explicit raw typed read of the account buffer.
391    #[inline(always)]
392    ///
393    /// # Safety
394    ///
395    /// Caller must uphold the invariants documented for this unsafe API before invoking it.
396    pub unsafe fn raw_ref<T: crate::pod::Pod>(&self) -> Result<Ref<'_, T>, ProgramError> {
397        self.segment_ref::<T>(0, core::mem::size_of::<T>() as u32)
398    }
399
400    /// Explicit raw typed write of the account buffer.
401    #[inline(always)]
402    ///
403    /// # Safety
404    ///
405    /// Caller must uphold the invariants documented for this unsafe API before invoking it.
406    pub unsafe fn raw_mut<T: crate::pod::Pod>(&self) -> Result<RefMut<'_, T>, ProgramError> {
407        self.segment_mut::<T>(0, core::mem::size_of::<T>() as u32)
408    }
409
410    // ── Resize ───────────────────────────────────────────────────────
411
412    /// Resize the account data to `new_len` bytes.
413    ///
414    /// Returns `Err(InvalidRealloc)` if the new length exceeds the
415    /// permitted increase from the original allocation.
416    #[inline(always)]
417    pub fn resize(&self, new_len: usize) -> Result<(), ProgramError> {
418        let original_len = (self.data_len() as i64 - self.resize_delta() as i64) as usize;
419        if new_len > original_len + MAX_PERMITTED_DATA_INCREASE {
420            return Err(ProgramError::InvalidRealloc);
421        }
422        let delta = new_len as i64 - original_len as i64;
423        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
424        unsafe {
425            (*self.raw).data_len = new_len as u64;
426            (*self.raw).resize_delta = delta as i32;
427        }
428        Ok(())
429    }
430
431    /// Resize without bounds checking.
432    ///
433    /// # Safety
434    ///
435    /// The caller must guarantee `new_len <= original_len + MAX_PERMITTED_DATA_INCREASE`.
436    #[inline(always)]
437    pub unsafe fn resize_unchecked(&self, new_len: usize) {
438        let original_len = (self.data_len() as i64 - self.resize_delta() as i64) as usize;
439        let delta = new_len as i64 - original_len as i64;
440        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
441        unsafe {
442            (*self.raw).data_len = new_len as u64;
443            (*self.raw).resize_delta = delta as i32;
444        }
445    }
446
447    // ── Close ────────────────────────────────────────────────────────
448
449    /// Solana System Program address (all-zero pubkey).
450    ///
451    /// Closing an account transfers ownership back to the System
452    /// Program, which is the canonical "no-owner" state on Solana.
453    /// The byte value `[0u8; 32]` and `Address::default()` are
454    /// equivalent, but using this named constant makes the intent
455    /// explicit, per the Hopper Safety Audit which flagged the
456    /// `Address::default()` spelling as documentation drift.
457    pub const SYSTEM_PROGRAM_ID: Address = Address::new_from_array([0u8; 32]);
458
459    /// Close the account: zero lamports and data, reassign owner to
460    /// the System Program.
461    ///
462    /// # Caveat
463    ///
464    /// This low-level routine does **not** verify the caller has
465    /// authority to close the account, Solana's runtime enforces
466    /// owner/writable rules at transaction commit time regardless, but
467    /// higher-level APIs (e.g. `hopper_runtime::AccountView::close_to`)
468    /// should pre-check those rules. See `account.rs::close_to` for
469    /// the safe wrapper.
470    #[inline(always)]
471    pub fn close(&self) -> ProgramResult {
472        self.set_lamports(0);
473        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
474        unsafe {
475            let len = self.data_len();
476            if len > 0 {
477                // Use the SVM's JIT-compiled memset for optimal CU cost.
478                crate::mem::memset(self.data_ptr_unchecked(), 0, len);
479            }
480            (*self.raw).data_len = 0;
481            (*self.raw).owner = Self::SYSTEM_PROGRAM_ID;
482        }
483        Ok(())
484    }
485
486    /// Close without borrow checks.
487    ///
488    /// # Safety
489    ///
490    /// The caller must ensure no active borrows exist.
491    #[inline(always)]
492    pub unsafe fn close_unchecked(&self) {
493        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
494        unsafe {
495            (*self.raw).lamports = 0;
496            (*self.raw).data_len = 0;
497            (*self.raw).owner = Self::SYSTEM_PROGRAM_ID;
498        }
499    }
500
501    // ── Raw pointers ─────────────────────────────────────────────────
502
503    /// Raw pointer to the `RuntimeAccount` header.
504    #[inline(always)]
505    pub const fn account_ptr(&self) -> *const RuntimeAccount {
506        self.raw as *const RuntimeAccount
507    }
508
509    /// Raw pointer to the first byte of account data.
510    ///
511    /// The data starts immediately after the 88-byte `RuntimeAccount` header.
512    /// This is an expert-only substrate escape hatch: constructing the pointer
513    /// is safe, but dereferencing it is unsafe and bypasses Hopper Native's
514    /// borrow-state checks, segment registry, and writable checks. Normal code
515    /// should use `try_borrow`, `try_borrow_mut`, `segment_ref`, or
516    /// `segment_mut`. Framework code should route user-facing raw access
517    /// through the documented unsafe runtime APIs (`Context::as_mut_ptr` /
518    /// `Context::as_ptr`) instead of exposing this method directly.
519    #[doc(hidden)]
520    #[inline(always)]
521    pub fn data_ptr_unchecked(&self) -> *mut u8 {
522        // SAFETY: Adding the struct size to the base pointer yields the
523        // first data byte. The runtime guarantees this memory is valid.
524        unsafe { (self.raw as *mut u8).add(core::mem::size_of::<RuntimeAccount>()) }
525    }
526
527    // ── Hopper Innovations ───────────────────────────────────────────
528
529    /// Validate that this account is a signer, returning a typed error.
530    #[inline(always)]
531    pub fn require_signer(&self) -> ProgramResult {
532        if self.is_signer() {
533            Ok(())
534        } else {
535            Err(ProgramError::MissingRequiredSignature)
536        }
537    }
538
539    /// Validate that this account is writable.
540    #[inline(always)]
541    pub fn require_writable(&self) -> ProgramResult {
542        if self.is_writable() {
543            Ok(())
544        } else {
545            Err(ProgramError::Immutable)
546        }
547    }
548
549    /// Validate that this account is owned by the given program.
550    #[inline(always)]
551    pub fn require_owned_by(&self, program: &Address) -> ProgramResult {
552        if self.owned_by(program) {
553            Ok(())
554        } else {
555            Err(ProgramError::IncorrectProgramId)
556        }
557    }
558
559    /// Validate signer + writable (common "payer" pattern).
560    #[inline(always)]
561    pub fn require_payer(&self) -> ProgramResult {
562        self.require_signer()?;
563        self.require_writable()
564    }
565
566    /// Read the Hopper account discriminator (first byte of data).
567    ///
568    /// Returns 0 if the account has no data.
569    #[inline(always)]
570    pub fn disc(&self) -> u8 {
571        if self.data_len() == 0 {
572            return 0;
573        }
574        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
575        unsafe { *self.data_ptr_unchecked() }
576    }
577
578    /// Read the Hopper account version (second byte of data).
579    ///
580    /// Returns 0 if the account has fewer than 2 bytes.
581    #[inline(always)]
582    pub fn version(&self) -> u8 {
583        if self.data_len() < 2 {
584            return 0;
585        }
586        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
587        unsafe { *self.data_ptr_unchecked().add(1) }
588    }
589
590    /// Read the 8-byte layout_id from the Hopper account header
591    /// (bytes 4..12 of account data, per the canonical header format).
592    ///
593    /// Returns `None` if the account has fewer than 12 bytes.
594    #[inline(always)]
595    pub fn layout_id(&self) -> Option<&[u8; 8]> {
596        if self.data_len() < 12 {
597            return None;
598        }
599        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
600        unsafe { Some(&*(self.data_ptr_unchecked().add(4) as *const [u8; 8])) }
601    }
602
603    /// Verify that this account has the given discriminator.
604    #[inline(always)]
605    pub fn require_disc(&self, expected: u8) -> ProgramResult {
606        if self.disc() == expected {
607            Ok(())
608        } else {
609            Err(ProgramError::InvalidAccountData)
610        }
611    }
612
613    // -- Chainable validation (Steel-inspired, improved) ---------------
614    //
615    // Return `Result<&Self>` so callers can chain:
616    //
617    //   account
618    //       .check_signer()?
619    //       .check_writable()?
620    //       .check_owned_by(&MY_PROGRAM_ID)?;
621    //
622    // Validated once, used everywhere. This pattern exists in Steel but
623    // not in pinocchio, Anchor, or Quasar.
624
625    /// Chainable signer check.
626    #[inline(always)]
627    pub fn check_signer(&self) -> Result<&Self, ProgramError> {
628        if self.is_signer() {
629            Ok(self)
630        } else {
631            Err(ProgramError::MissingRequiredSignature)
632        }
633    }
634
635    /// Chainable writable check.
636    #[inline(always)]
637    pub fn check_writable(&self) -> Result<&Self, ProgramError> {
638        if self.is_writable() {
639            Ok(self)
640        } else {
641            Err(ProgramError::Immutable)
642        }
643    }
644
645    /// Chainable ownership check.
646    #[inline(always)]
647    pub fn check_owned_by(&self, program: &Address) -> Result<&Self, ProgramError> {
648        if self.owned_by(program) {
649            Ok(self)
650        } else {
651            Err(ProgramError::IncorrectProgramId)
652        }
653    }
654
655    /// Chainable discriminator check.
656    #[inline(always)]
657    pub fn check_disc(&self, expected: u8) -> Result<&Self, ProgramError> {
658        if self.disc() == expected {
659            Ok(self)
660        } else {
661            Err(ProgramError::InvalidAccountData)
662        }
663    }
664
665    /// Chainable non-empty data check.
666    #[inline(always)]
667    pub fn check_has_data(&self) -> Result<&Self, ProgramError> {
668        if !self.is_data_empty() {
669            Ok(self)
670        } else {
671            Err(ProgramError::AccountDataTooSmall)
672        }
673    }
674
675    /// Chainable executable check.
676    #[inline(always)]
677    pub fn check_executable(&self) -> Result<&Self, ProgramError> {
678        if self.executable() {
679            Ok(self)
680        } else {
681            Err(ProgramError::InvalidArgument)
682        }
683    }
684
685    /// Chainable address check.
686    #[inline(always)]
687    pub fn check_address(&self, expected: &Address) -> Result<&Self, ProgramError> {
688        if address_eq(self.address(), expected) {
689            Ok(self)
690        } else {
691            Err(ProgramError::InvalidArgument)
692        }
693    }
694
695    /// Chainable minimum data length check.
696    #[inline(always)]
697    pub fn check_data_len(&self, min_len: usize) -> Result<&Self, ProgramError> {
698        if self.data_len() >= min_len {
699            Ok(self)
700        } else {
701            Err(ProgramError::AccountDataTooSmall)
702        }
703    }
704
705    // -- Safe owner access ---------------------------------------------
706
707    /// Read the owner address as a copy (32-byte value).
708    ///
709    /// Unlike `owner()` (which is unsafe due to reference invalidation
710    /// if `assign()` is called), this returns a copy that is always safe.
711    /// Costs 32 bytes of stack space but eliminates aliasing hazards.
712    #[inline(always)]
713    pub fn read_owner(&self) -> Address {
714        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
715        unsafe { (*self.raw).owner.clone() }
716    }
717
718    // -- Packed flags --------------------------------------------------
719
720    /// Read the first 4 bytes of the account header as a single u32.
721    ///
722    /// Layout (little-endian): `[borrow_state, is_signer, is_writable, executable]`
723    ///
724    /// This is the fastest way to extract multiple account properties at once
725    ///, a single aligned u32 read instead of 3-4 separate byte loads.
726    #[inline(always)]
727    fn header_u32(&self) -> u32 {
728        // SAFETY: RuntimeAccount is #[repr(C)] with first 4 fields as u8,
729        // totalling 4 bytes at the start. Reading as u32 is safe because
730        // the struct is at least 88 bytes and the BPF input buffer is
731        // sufficiently aligned.
732        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
733        unsafe { *(self.raw as *const u32) }
734    }
735
736    /// Pack the account's boolean flags into a single byte for fast
737    /// comparison.
738    ///
739    /// Bit layout:
740    /// - bit 0: is_signer
741    /// - bit 1: is_writable
742    /// - bit 2: executable
743    /// - bit 3: has data (data_len > 0)
744    ///
745    /// Use with `expect_flags()` for single-instruction multi-check:
746    ///
747    /// ```ignore
748    /// // Require: signer + writable + has data
749    /// account.expect_flags(0b1011)?;
750    /// ```
751    #[inline(always)]
752    pub fn flags(&self) -> u8 {
753        // Single u32 read extracts [borrow_state, is_signer, is_writable, executable].
754        // On little-endian: is_signer = bits 8-15, is_writable = bits 16-23, executable = bits 24-31.
755        let h = self.header_u32();
756        let mut f: u8 = 0;
757        if h & 0x0000_FF00 != 0 {
758            f |= 0b0001;
759        } // is_signer
760        if h & 0x00FF_0000 != 0 {
761            f |= 0b0010;
762        } // is_writable
763        if h & 0xFF00_0000 != 0 {
764            f |= 0b0100;
765        } // executable
766        if !self.is_data_empty() {
767            f |= 0b1000;
768        }
769        f
770    }
771
772    /// Check that the account's flags contain all the required bits.
773    ///
774    /// `required` is a bitmask of flags that must be set. See `flags()`.
775    #[inline(always)]
776    pub fn expect_flags(&self, required: u8) -> ProgramResult {
777        if self.flags() & required == required {
778            Ok(())
779        } else {
780            Err(ProgramError::InvalidArgument)
781        }
782    }
783}
784
785impl core::fmt::Debug for AccountView {
786    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
787        f.debug_struct("AccountView")
788            .field("address", self.address())
789            .field("lamports", &self.lamports())
790            .field("data_len", &self.data_len())
791            .field("is_signer", &self.is_signer())
792            .field("is_writable", &self.is_writable())
793            .finish()
794    }
795}
796
797// ── RemainingAccounts ────────────────────────────────────────────────
798
799/// Iterator over remaining (unstructured) accounts after the known ones.
800pub struct RemainingAccounts<'a> {
801    accounts: &'a [AccountView],
802    cursor: usize,
803}
804
805impl<'a> RemainingAccounts<'a> {
806    /// Create from a slice of the remaining accounts.
807    #[inline(always)]
808    pub fn new(accounts: &'a [AccountView]) -> Self {
809        Self {
810            accounts,
811            cursor: 0,
812        }
813    }
814
815    /// Number of accounts remaining.
816    #[inline(always)]
817    pub fn remaining(&self) -> usize {
818        self.accounts.len() - self.cursor
819    }
820
821    /// Take the next account, or return `NotEnoughAccountKeys`.
822    #[inline(always)]
823    pub fn next(&mut self) -> Result<&'a AccountView, ProgramError> {
824        if self.cursor >= self.accounts.len() {
825            return Err(ProgramError::NotEnoughAccountKeys);
826        }
827        let account = &self.accounts[self.cursor];
828        self.cursor += 1;
829        Ok(account)
830    }
831
832    /// Take the next account that is a signer.
833    #[inline(always)]
834    pub fn next_signer(&mut self) -> Result<&'a AccountView, ProgramError> {
835        let account = self.next()?;
836        account.require_signer()?;
837        Ok(account)
838    }
839
840    /// Take the next account that is writable.
841    #[inline(always)]
842    pub fn next_writable(&mut self) -> Result<&'a AccountView, ProgramError> {
843        let account = self.next()?;
844        account.require_writable()?;
845        Ok(account)
846    }
847
848    /// Take the next account owned by the given program.
849    #[inline(always)]
850    pub fn next_owned_by(&mut self, program: &Address) -> Result<&'a AccountView, ProgramError> {
851        let account = self.next()?;
852        account.require_owned_by(program)?;
853        Ok(account)
854    }
855}