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}