Skip to main content

hopper_core/check/
fast.rs

1//! Batched u32 header validation using a single-compare optimization.
2//!
3//! The SVM `RuntimeAccount` header packs `borrow_state`, `is_signer`,
4//! `is_writable`, and `executable` into a 4-byte prefix. We read this as
5//! a single `u32` and compare against precomputed constants, collapsing
6//! 3-4 branch instructions into ONE comparison.
7//!
8//! This saves ~4-8 CU per account validation on the hot path.
9//!
10//! ## Wire Layout (RuntimeAccount header, little-endian u32)
11//!
12//! ```text
13//! byte 0 = borrow_state (0xFF for non-duplicate)
14//! byte 1 = is_signer    (0 or 1)
15//! byte 2 = is_writable  (0 or 1)
16//! byte 3 = executable    (0 or 1)
17//! ```
18//!
19//! ## Safety Model
20//!
21//! The `read_account_header` function relies on hopper-native's `AccountView`
22//! being `#[repr(C)]` with its first field being a `*mut u8` pointing to
23//! the start of the `RuntimeAccount` in the SVM input buffer. This is
24//! verified by hopper-native's own compile-time assertions. If hopper-native changes
25//! its layout, the compile-time size assertion below will fail.
26//!
27//! This optimization is **gated to `target_os = "solana"`** only. Off-chain
28//! code uses the safe fallback via `AccountView::is_signer()` etc.
29
30use hopper_runtime::{error::ProgramError, AccountView, ProgramResult};
31
32// -- Precomputed Header Constants ------------------------------------
33
34/// Not borrowed, not a duplicate (borrow_state = 0xFF).
35const NOT_BORROWED: u32 = 0xFF;
36
37/// Non-duplicate, no flags.
38pub const HEADER_NODUP: u32 = NOT_BORROWED;
39
40/// Non-duplicate + signer.
41pub const HEADER_SIGNER: u32 = NOT_BORROWED | (1 << 8);
42
43/// Non-duplicate + writable.
44pub const HEADER_WRITABLE: u32 = NOT_BORROWED | (1 << 16);
45
46/// Non-duplicate + signer + writable.
47pub const HEADER_SIGNER_WRITABLE: u32 = NOT_BORROWED | (1 << 8) | (1 << 16);
48
49/// Non-duplicate + executable.
50pub const HEADER_EXECUTABLE: u32 = NOT_BORROWED | (1 << 24);
51
52/// Non-duplicate + writable + signer (same as SIGNER_WRITABLE, alias).
53pub const HEADER_AUTHORITY: u32 = HEADER_SIGNER_WRITABLE;
54
55// -- Fast Validation Functions ---------------------------------------
56
57/// Read the 4-byte RuntimeAccount header as a u32.
58///
59/// # Safety
60///
61/// Requires that `AccountView` is `#[repr(C)]` with its first field being a
62/// raw pointer to the RuntimeAccount data in the SVM input buffer. The first 4
63/// bytes of RuntimeAccount are `[borrow_state, is_signer, is_writable, executable]`.
64///
65/// This is a hopper-native implementation detail; changes to hopper-native's `AccountView`
66/// layout would require updating this function. The `target_os = "solana"` gate
67/// ensures this is only compiled for the SBF target where the SVM guarantees
68/// this layout.
69#[cfg(all(target_os = "solana", feature = "hopper-native-backend"))]
70#[inline(always)]
71unsafe fn read_account_header(account: &AccountView) -> u32 {
72    // SAFETY: AccountView is repr(C) with a pointer to the raw RuntimeAccount
73    // as its first field. We dereference this pointer to get the RuntimeAccount
74    // base address, then read the first 4 bytes as an unaligned u32.
75    //
76    // Preconditions (all guaranteed by the SVM for entrypoint accounts):
77    // 1. AccountView is #[repr(C)] and its first field is a data pointer.
78    // 2. The pointer is valid and points to a RuntimeAccount in the input buffer.
79    // 3. The RuntimeAccount starts with [borrow_state, is_signer, is_writable, executable].
80    let ptr = account as *const AccountView as *const u8;
81    // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
82    let raw_ptr = unsafe { *(ptr as *const *const u8) };
83    unsafe { core::ptr::read_unaligned(raw_ptr as *const u32) }
84}
85
86/// Fast single-compare account validation.
87///
88/// Reads the RuntimeAccount header as a u32 and compares against the expected
89/// pattern. If the comparison fails, decomposes to identify the specific error.
90///
91/// This collapses duplicate-check + signer-check + writable-check + executable-check
92/// into a **single u32 comparison**, saving ~4-8 CU per account.
93///
94/// Safe to call on any `AccountView` from the SVM entrypoint.
95/// On non-SVM targets, falls back to individual checks via AccountView methods.
96#[inline(always)]
97pub fn check_account_fast(account: &AccountView, expected_header: u32) -> ProgramResult {
98    // Fast path: one compare for all flags
99    #[cfg(all(target_os = "solana", feature = "hopper-native-backend"))]
100    {
101        // SAFETY: This block is part of Hopper's audited zero-copy/backend boundary; surrounding checks and caller contracts uphold the required raw-pointer, layout, and aliasing invariants.
102        let actual = unsafe { read_account_header(account) };
103        if (actual & expected_header) == expected_header {
104            return Ok(());
105        }
106        // Cold path: decompose error
107        decompose_header_error(actual, expected_header)
108    }
109    #[cfg(not(all(target_os = "solana", feature = "hopper-native-backend")))]
110    {
111        // Off-chain fallback: individual checks
112        check_account_flags_fallback(account, expected_header)
113    }
114}
115
116/// Decompose a header mismatch into a specific error.
117///
118/// This is `#[cold]` -- only invoked on the error path, keeping the hot path
119/// (the comparison above) as fast as possible.
120#[cold]
121#[inline(never)]
122#[allow(dead_code)]
123fn decompose_header_error(actual: u32, expected: u32) -> ProgramResult {
124    // Check duplicate (borrow_state != 0xFF)
125    if actual & 0xFF != 0xFF {
126        return Err(ProgramError::AccountBorrowFailed);
127    }
128    // Check signer
129    if (expected & (1 << 8)) != 0 && (actual & (1 << 8)) == 0 {
130        return Err(ProgramError::MissingRequiredSignature);
131    }
132    // Check writable
133    if (expected & (1 << 16)) != 0 && (actual & (1 << 16)) == 0 {
134        return Err(ProgramError::InvalidAccountData);
135    }
136    // Check executable
137    if (expected & (1 << 24)) != 0 && (actual & (1 << 24)) == 0 {
138        return Err(ProgramError::InvalidAccountData);
139    }
140    // Generic mismatch
141    Err(ProgramError::InvalidAccountData)
142}
143
144/// Off-chain fallback using individual AccountView methods.
145#[cfg(not(all(target_os = "solana", feature = "hopper-native-backend")))]
146fn check_account_flags_fallback(account: &AccountView, expected: u32) -> ProgramResult {
147    if (expected & (1 << 8)) != 0 && !account.is_signer() {
148        return Err(ProgramError::MissingRequiredSignature);
149    }
150    if (expected & (1 << 16)) != 0 && !account.is_writable() {
151        return Err(ProgramError::InvalidAccountData);
152    }
153    if (expected & (1 << 24)) != 0 && !account.executable() {
154        return Err(ProgramError::InvalidAccountData);
155    }
156    Ok(())
157}
158
159/// Validate a signer account with single u32 compare.
160#[inline(always)]
161pub fn check_signer_fast(account: &AccountView) -> ProgramResult {
162    check_account_fast(account, HEADER_SIGNER)
163}
164
165/// Validate a writable account with single u32 compare.
166#[inline(always)]
167pub fn check_writable_fast(account: &AccountView) -> ProgramResult {
168    check_account_fast(account, HEADER_WRITABLE)
169}
170
171/// Validate a signer + writable account (authority) with single u32 compare.
172#[inline(always)]
173pub fn check_authority_fast(account: &AccountView) -> ProgramResult {
174    check_account_fast(account, HEADER_AUTHORITY)
175}
176
177/// Validate an executable (program) account with single u32 compare.
178#[inline(always)]
179pub fn check_executable_fast(account: &AccountView) -> ProgramResult {
180    check_account_fast(account, HEADER_EXECUTABLE)
181}