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