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