Skip to main content

hopper_runtime/
lib.rs

1//! Hopper Runtime -- canonical semantic runtime surface.
2//!
3//! Hopper Runtime owns the public rules, validation, typed loading, CPI
4//! semantics, and execution context that authored Hopper code targets.
5//! Hopper Native owns the raw execution boundary.
6
7#![no_std]
8#![deny(unsafe_op_in_unsafe_fn)]
9// The backend `AccountView` is `Copy` only under the `copy` feature. The
10// `.clone()` in our manual `Clone` impl is required in the default lane, so we
11// silence `clone_on_copy` only in the lane where the type is actually `Copy`.
12#![cfg_attr(feature = "copy", allow(clippy::clone_on_copy))]
13
14#[cfg(any(test, feature = "thread-local-registry"))]
15extern crate std;
16
17#[doc(hidden)]
18pub mod native_boundary;
19
20pub mod account;
21pub mod account_wrappers;
22pub mod address;
23pub mod audit;
24pub mod behavior;
25pub mod borrow;
26pub(crate) mod borrow_registry;
27pub mod compact;
28#[cfg(any(not(target_os = "solana"), feature = "remaining-compute-units-syscall"))]
29pub mod compute;
30pub mod cpi;
31pub mod cpi_event;
32pub mod crank;
33pub mod crypto;
34pub mod dyn_cpi;
35pub mod error;
36pub mod field_map;
37pub mod foreign;
38pub mod interop;
39pub mod lamports;
40pub mod lazy;
41pub mod log;
42pub mod memory;
43pub mod migrate;
44pub mod pod;
45pub mod policy;
46pub mod proof;
47pub mod ref_only;
48pub mod result;
49pub mod segment;
50pub mod tail;
51pub mod utils;
52pub mod zerocopy;
53// Re-export the sealed marker module at the crate root so macro
54// codegen can address it as `::hopper_runtime::__sealed::...`. It's
55// doc-hidden because it is the sealed enforcement surface,
56// not a normal-user-facing API.
57#[doc(hidden)]
58pub use zerocopy::__sealed;
59pub mod context;
60pub mod enum_byte;
61pub mod instruction;
62pub mod layout;
63pub mod option_byte;
64pub mod pda;
65#[cfg(test)]
66mod pda_host_tests;
67pub mod remaining;
68pub mod rent;
69pub mod return_data;
70pub mod segment_borrow;
71pub mod segment_lease;
72pub mod syscall;
73pub mod syscalls;
74pub mod system;
75pub mod token;
76pub mod token_2022_ext;
77pub mod token_2022_ix;
78pub mod token_admin;
79pub mod token_batch;
80pub mod token_confidential_ix;
81#[cfg(test)]
82mod token_differential_tests;
83pub mod token_metadata_ix;
84pub mod token_mint;
85#[cfg(test)]
86mod unsafe_site_tests;
87pub mod write_policy;
88
89pub use account::AccountView;
90pub use account_wrappers::{
91    Account, InitAccount, Interface, InterfaceAccount, InterfaceAccountLayout,
92    InterfaceAccountResolve, InterfaceSpec, Program, ProgramId, Signer as HopperSigner,
93    SystemAccount, SystemId, UncheckedAccount,
94};
95pub use address::Address;
96pub use audit::{AccountAudit, DuplicateAccount};
97pub use behavior::{BehaviorChecked, BehaviorWrite, HopperBehavior};
98pub use borrow::{Ref, RefMut};
99pub use compact::{CompactDynamicLayout, CompactLayout, COMPACT_BODY_OFFSET};
100#[cfg(any(not(target_os = "solana"), feature = "remaining-compute-units-syscall"))]
101pub use compute::{check_compute_units, remaining_compute_units, require_compute_units};
102pub use context::{Context, ScopedContext};
103pub use cpi::{invoke, invoke_checked, invoke_signed, invoke_signed_checked};
104#[cfg(feature = "crypto-big-mod-exp")]
105pub use crypto::big_mod_exp;
106#[cfg(feature = "crypto-bn254")]
107pub use crypto::{
108    alt_bn128_add, alt_bn128_g1_addition_be, alt_bn128_g1_compress_be, alt_bn128_g1_decompress_be,
109    alt_bn128_g1_multiplication_be, alt_bn128_g2_compress_be, alt_bn128_g2_decompress_be,
110    alt_bn128_mul, alt_bn128_pairing, alt_bn128_pairing_be,
111};
112pub use crypto::{
113    blake3, blake3_single, keccak256, keccak256_single, recover_ethereum_address,
114    secp256k1_recover, sha256, sha256_single,
115};
116#[cfg(feature = "crypto-curve")]
117pub use crypto::{curve_group_add, curve_group_mul, curve_group_sub, curve_multiscalar_mul};
118#[cfg(feature = "crypto-poseidon")]
119pub use crypto::{poseidon_bn254_x5, poseidon_hash, poseidon_hashv};
120pub use error::ProgramError;
121pub use field_map::{FieldInfo, FieldMap};
122pub use foreign::{
123    ExplainExternal, ExternalAccount, ExternalBytes, ExternalChecked, ExternalExplainSink,
124    ExternalLens, ExternalLensValue, ExternalProof, ExternalResolve, ExternalZeroCopy, ForeignLens,
125    ForeignManifest,
126};
127pub use interop::TransparentAddress;
128pub use lamports::transfer_lamports;
129pub use lazy::LazyContext;
130pub use migrate::{
131    apply_pending_migrations, ensure_fits_with_rent, migrate_layout, migrate_layout_resizing,
132    validate_header_for_epoch_migration, LayoutMigration, MigrationEdge,
133};
134pub use policy::{HopperInstructionPolicy, HopperProgramPolicy, HopperProgramProfile};
135pub use proof::{
136    AccountProof, ExecutableChecked, HasOneChecked, LayoutChecked, OwnerChecked, SeedsChecked,
137    SignerChecked, TokenExtensionsChecked, Unchecked, WritableChecked,
138};
139pub use ref_only::HopperRefOnly;
140pub use remaining::{
141    RemainingAccountViews, RemainingAccounts, RemainingError, RemainingExternalAccounts,
142    RemainingGroup, RemainingLazy, RemainingLazySlot, RemainingMode, RemainingSigners,
143    RemainingTyped, MAX_REMAINING_ACCOUNTS,
144};
145pub use return_data::{get_return_data, set_return_data, try_set_return_data, ReturnData};
146pub use tail::{
147    borrow_address_slice, borrow_bounded_str, read_tail, read_tail_len, replace_tail_field,
148    seq_capacity_for, seq_region_bytes_for, tail_capacity, tail_payload, vec_field_extent,
149    write_tail, write_tail_payload, BoundedString, BoundedVec, HopperString, HopperVec, SeqElement,
150    SeqTailRead, SeqTailWrite, TailBytes, TailCodec, TailElement, TailSeq, TailSeqIter, TailSeqMut,
151    TailStr, SEQ_LEN_PREFIX,
152};
153
154/// Compose a layout's `LayoutMigration::MIGRATIONS` chain from a list
155/// of `#[hopper::migrate]`-emitted edge constants.
156///
157/// ```ignore
158/// #[hopper::migrate(from = 1, to = 2)]
159/// pub fn vault_v1_to_v2(body: &mut [u8]) -> ProgramResult { Ok(()) }
160///
161/// hopper::layout_migrations! {
162///     Vault = [VAULT_V1_TO_V2_EDGE, VAULT_V2_TO_V3_EDGE],
163/// }
164/// ```
165///
166/// Emits `impl LayoutMigration for Vault { const MIGRATIONS = .. }`.
167/// Each list entry must evaluate to a
168/// [`MigrationEdge`](crate::migrate::MigrationEdge). typically the
169/// `<UPPER_SNAKE_FN_NAME>_EDGE` constant that
170/// `#[hopper::migrate]` emits alongside each migration function.
171/// Chain continuity (every adjacent pair must satisfy
172/// `a.to_epoch == b.from_epoch`) is enforced at runtime by
173/// [`apply_pending_migrations`].
174#[macro_export]
175macro_rules! layout_migrations {
176    ( $layout:ty = [ $( $edge:expr ),+ $(,)? ] $(,)? ) => {
177        impl $crate::migrate::LayoutMigration for $layout {
178            const MIGRATIONS: &'static [$crate::migrate::MigrationEdge] = &[
179                $( $edge ),+
180            ];
181        }
182    };
183}
184pub use enum_byte::{EnumByte, UnitEnum};
185pub use instruction::CpiAccount;
186pub use instruction::{
187    InstructionAccount, InstructionView, Seed, Signer, StoredAccountMeta, StoredInstruction,
188};
189pub use layout::{HopperHeader, LayoutContract, LayoutInfo};
190pub use option_byte::OptionByte;
191pub use pod::{read_unaligned_value, Pod, ValuePod, Zeroable};
192pub use result::ProgramResult;
193pub use segment::{
194    FieldCapability, Segment, TypedSegment, FIELD_POLICY_AUTHORITY_GATED,
195    FIELD_POLICY_CHECKED_MATH, FIELD_POLICY_IMMUTABLE_AFTER_INIT, FIELD_ROLE_AUTHORITY,
196    FIELD_ROLE_BALANCE, FIELD_ROLE_DATA, FIELD_ROLE_VERSION,
197};
198pub use segment_borrow::{AccessKind, SegmentBorrow, SegmentBorrowGuard, SegmentBorrowRegistry};
199pub use segment_lease::{SegRef, SegRefMut, SegmentLease, SegmentsMut};
200pub use write_policy::{
201    ParametricWriteRange, WritePolicy, WriteRange, WRITE_POLICY_VIOLATION_PAGE,
202};
203pub use zerocopy::{AccountLayout, WireLayout, ZeroCopy};
204
205pub const MAX_TX_ACCOUNTS: usize = native_boundary::BACKEND_MAX_TX_ACCOUNTS;
206pub const SUCCESS: u64 = native_boundary::BACKEND_SUCCESS;
207
208#[doc(hidden)]
209pub use hopper_native as __hopper_native;
210pub use hopper_native::sha256;
211
212#[doc(hidden)]
213pub use hopper_native::address::decode_base58_32 as __decode_base58_32;
214
215/// Compile-time base58 address literal.
216#[macro_export]
217macro_rules! address {
218    ( $literal:expr ) => {
219        $crate::Address::new_from_array($crate::__decode_base58_32($literal))
220    };
221}
222
223/// Declare a program's on-chain id, mirroring the `declare_id!` convention
224/// every other Solana framework ships (Anchor, Pinocchio, Quasar).
225///
226/// Expands to a `pub const ID: Address` decoded at compile time, a
227/// `pub const fn id() -> Address` accessor, and a `pub fn check_id(&Address)
228/// -> bool` guard. Programs that pin their own id for self-PDA derivation,
229/// CPI-guard checks, or `require_keys_eq!(program.key(), &crate::ID)` use this
230/// instead of hand-rolling an [`address!`] constant.
231///
232/// ```ignore
233/// hopper::declare_id!("D8UGWDX5QRwEkKs2J9Sweabf4zd6hzdLqv7CB11SF91F");
234/// assert!(crate::check_id(&crate::ID));
235/// ```
236#[macro_export]
237macro_rules! declare_id {
238    ( $literal:expr ) => {
239        /// The program's on-chain address, decoded at compile time.
240        pub const ID: $crate::Address = $crate::address!($literal);
241
242        /// Return the program's declared id.
243        #[inline]
244        pub const fn id() -> $crate::Address {
245            ID
246        }
247
248        /// Return true if `other` equals the declared program id.
249        #[inline]
250        pub fn check_id(other: &$crate::Address) -> bool {
251            *other == ID
252        }
253    };
254}
255
256/// A program-derived address evaluated at compile time:
257/// `const_pda!(PROGRAM_ID, [seed, ...], bump)` is
258/// [`pda::const_program_address`] with the seed list spelled inline (each
259/// seed anything that casts to `&[u8]`: a byte-string literal, an
260/// `Address::as_array()`, a `&[u8; N]`). See that function for the bump
261/// contract and the soundness note.
262///
263/// ```ignore
264/// hopper::declare_id!("F4Um7PWsnZfN7y8WFzu1aPYJwqGduJTa4zuCGY9EUqMy");
265/// pub const VAULT: hopper::Address = hopper::const_pda!(ID, [b"vault", ID.as_array()], 255);
266/// ```
267#[macro_export]
268macro_rules! const_pda {
269    ( $program_id:expr, [ $( $seed:expr ),* $(,)? ], $bump:expr ) => {
270        $crate::pda::const_program_address(&$program_id, &[ $( $seed as &[u8] ),* ], $bump)
271    };
272}
273
274/// Early-return with an error if the condition is false.
275#[macro_export]
276macro_rules! require {
277    ( $cond:expr, $err:expr ) => {
278        if !($cond) {
279            return Err($err);
280        }
281    };
282    ( $cond:expr ) => {
283        if !($cond) {
284            return Err($crate::ProgramError::InvalidArgument);
285        }
286    };
287}
288
289/// Assert two values are equal, returning an error on mismatch.
290#[macro_export]
291macro_rules! require_eq {
292    ( $left:expr, $right:expr, $err:expr ) => {
293        if ($left) != ($right) {
294            return Err($err);
295        }
296    };
297    ( $left:expr, $right:expr ) => {
298        if ($left) != ($right) {
299            return Err($crate::ProgramError::InvalidArgument);
300        }
301    };
302}
303
304/// Assert two values are not equal. Early-returns with the supplied
305/// error on match (or `ProgramError::InvalidArgument` in the short
306/// form). Symmetric with [`require_eq!`].
307#[macro_export]
308macro_rules! require_neq {
309    ( $left:expr, $right:expr, $err:expr ) => {
310        if ($left) == ($right) {
311            return Err($err);
312        }
313    };
314    ( $left:expr, $right:expr ) => {
315        if ($left) == ($right) {
316            return Err($crate::ProgramError::InvalidArgument);
317        }
318    };
319}
320
321/// Assert two public keys (or any byte slices convertible via
322/// [`AsRef<[u8; 32]>`]) are equal. Narrower than [`require_eq!`] but
323/// matches the ergonomic spelling ecosystem migrators coming from
324/// Anchor / Jiminy are familiar with.
325///
326/// ```ignore
327/// hopper::require_keys_eq!(
328///     vault.authority,
329///     ctx.signer.address(),
330///     ProgramError::InvalidAccountData,
331/// );
332/// ```
333#[macro_export]
334macro_rules! require_keys_eq {
335    ( $left:expr, $right:expr, $err:expr ) => {
336        if !$crate::address::keys_eq(
337            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left),
338            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right),
339        ) {
340            return Err($err);
341        }
342    };
343    ( $left:expr, $right:expr ) => {
344        if !$crate::address::keys_eq(
345            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left),
346            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right),
347        ) {
348            return Err($crate::ProgramError::InvalidAccountData);
349        }
350    };
351}
352
353/// Assert two public keys are *not* equal. Used for pinning distinct
354/// accounts (authority != user, source != destination). Same coercion
355/// and error semantics as [`require_keys_eq!`].
356#[macro_export]
357macro_rules! require_keys_neq {
358    ( $left:expr, $right:expr, $err:expr ) => {
359        if $crate::address::keys_eq(
360            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left),
361            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right),
362        ) {
363            return Err($err);
364        }
365    };
366    ( $left:expr, $right:expr ) => {
367        if $crate::address::keys_eq(
368            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left),
369            ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right),
370        ) {
371            return Err($crate::ProgramError::InvalidAccountData);
372        }
373    };
374}
375
376/// Assert `left >= right`, returning the supplied error on underrun.
377/// Useful for lamport / balance checks.
378#[macro_export]
379macro_rules! require_gte {
380    ( $left:expr, $right:expr, $err:expr ) => {
381        if !($left >= $right) {
382            return Err($err);
383        }
384    };
385    ( $left:expr, $right:expr ) => {
386        if !($left >= $right) {
387            return Err($crate::ProgramError::InsufficientFunds);
388        }
389    };
390}
391
392/// Assert `left > right` strictly.
393#[macro_export]
394macro_rules! require_gt {
395    ( $left:expr, $right:expr, $err:expr ) => {
396        if !($left > $right) {
397            return Err($err);
398        }
399    };
400    ( $left:expr, $right:expr ) => {
401        if !($left > $right) {
402            return Err($crate::ProgramError::InvalidArgument);
403        }
404    };
405}
406
407/// Assert `left < right` strictly. Anchor-parity sibling of
408/// [`require_gt!`]. Default error is `ProgramError::InvalidArgument`
409/// because a failed ordering check most often flags a bad user input.
410#[macro_export]
411macro_rules! require_lt {
412    ( $left:expr, $right:expr, $err:expr ) => {
413        if !($left < $right) {
414            return Err($err);
415        }
416    };
417    ( $left:expr, $right:expr ) => {
418        if !($left < $right) {
419            return Err($crate::ProgramError::InvalidArgument);
420        }
421    };
422}
423
424/// Assert `left <= right`. Anchor-parity sibling of [`require_gte!`].
425#[macro_export]
426macro_rules! require_lte {
427    ( $left:expr, $right:expr, $err:expr ) => {
428        if !($left <= $right) {
429            return Err($err);
430        }
431    };
432    ( $left:expr, $right:expr ) => {
433        if !($left <= $right) {
434            return Err($crate::ProgramError::InvalidArgument);
435        }
436    };
437}
438
439/// Return an error immediately. Parallel to Anchor's `err!`.
440///
441/// The macro expands to a bare `return Err(...)`, so the call site
442/// reads like a control-flow keyword rather than an expression. The
443/// argument is evaluated as an expression so either a Hopper-generated
444/// error code or a raw `ProgramError` works.
445///
446/// ```ignore
447/// if amount == 0 {
448///     return err!(VaultError::ZeroDeposit);
449/// }
450/// ```
451#[macro_export]
452macro_rules! err {
453    ( $e:expr ) => {
454        return ::core::result::Result::Err($crate::ProgramError::from($e))
455    };
456}
457
458/// Alias for [`err!`]. Anchor-style spelling for ported code. Functionally
459/// identical.
460#[macro_export]
461macro_rules! error {
462    ( $e:expr ) => {
463        return ::core::result::Result::Err($crate::ProgramError::from($e))
464    };
465}
466
467/// Auditable raw-pointer boundary.
468///
469/// Wraps a block that needs `unsafe` in a named Hopper macro so an
470/// auditor can grep `hopper_unsafe_region!` and find every raw
471/// reinterpretation in the tree with one command. The macro expands
472/// to a plain `unsafe { ... }` block: zero runtime cost, identical
473/// codegen, but the invocation site is nameable and documented.
474///
475/// Usage:
476///
477/// ```ignore
478/// let cleared = hopper::hopper_unsafe_region!("clear rewards via raw ptr", {
479///     let ptr = ctx.as_mut_ptr(0)?;
480///     (ptr.add(24) as *mut u64).write_unaligned(0);
481///     0u64
482/// });
483/// ```
484///
485/// The label is a compile-time string literal. It is discarded by
486/// the expansion but serves as inline documentation an auditor
487/// reads alongside the `unsafe` body.
488#[macro_export]
489macro_rules! hopper_unsafe_region {
490    ( $label:literal, $body:block ) => {{
491        // The label is a compile-time string literal, captured so it
492        // surfaces in `cargo expand` output and can be grep'd out of
493        // the expanded tree the same way as the macro name.
494        const _HOPPER_UNSAFE_REGION_LABEL: &str = $label;
495        #[allow(unused_unsafe)]
496        // SAFETY: This macro marks a region the caller has labelled and
497        // reviewed; the justification for `$body` is written at the call
498        // site.
499        unsafe {
500            $body
501        }
502    }};
503}
504
505/// Backend-neutral logging macro. A formatted message is built in a
506/// 256-byte stack buffer; a longer one is cut at the last whole
507/// character that fits.
508#[macro_export]
509macro_rules! msg {
510    ( $literal:expr ) => {{
511        $crate::log::log($literal);
512    }};
513    ( $fmt:expr, $($arg:tt)* ) => {{
514        #[cfg(target_os = "solana")]
515        {
516            use core::fmt::Write;
517            let mut buf = [0u8; 256];
518            let mut wrapper = $crate::log::StackWriter::new(&mut buf);
519            let _ = write!(wrapper, $fmt, $($arg)*);
520            $crate::log::log(wrapper.as_str());
521        }
522        #[cfg(not(target_os = "solana"))]
523        {
524            let _ = ($fmt, $($arg)*);
525        }
526    }};
527}
528
529/// Emit a Hopper event via self-CPI for reliable indexing, the
530/// manual-wiring form.
531///
532/// Most programs should not call this directly: `#[hopper::context(event_cpi)]`
533/// plus `ctx.emit_event_cpi(&event)` wires the same emission (accounts,
534/// bump, sink) with zero hand-written plumbing. Reach for this macro
535/// only when the context macro is out of the picture (raw handlers,
536/// hand-rolled account handling, a custom sink).
537///
538/// Wraps [`cpi_event::encode_event_cpi`] and a call into the active
539/// backend's `invoke_signed` so indexers see the event as an inner
540/// instruction in the transaction metadata. Log output is size-capped; inner
541/// instructions are retained. Anchor's `emit_cpi!` solves the same problem
542/// with the same trick; Hopper's lives in pure Rust so it works under
543/// `no_std` and any of the three backends, and its wire format is
544/// 3 bytes of overhead (2-byte marker + 1-byte tag) against Anchor's 16
545/// (8-byte instruction tag + 8-byte event discriminator).
546///
547/// ## Required program plumbing (manual form only)
548///
549/// The caller must declare a sentinel handler so the dispatcher routes
550/// the self-CPI somewhere, and should authenticate it rather than
551/// no-op, or forged events become possible:
552///
553/// ```ignore
554/// #[instruction(discriminator = [0xE0, 0x1E])]
555/// fn __hopper_event_sink(ctx: &mut Context<'_>) -> ProgramResult {
556///     hopper_runtime::cpi_event::handle_event_sink(ctx, ctx.instruction_data())
557/// }
558/// ```
559///
560/// And a PDA account seeded with [`cpi_event::EVENT_AUTHORITY_SEED`]
561/// (`b"__hopper_event_authority"`) so the CPI has a signer, plus the
562/// program's own account in the instruction so the runtime can resolve
563/// the self-CPI target.
564///
565/// ## Usage
566///
567/// ```ignore
568/// hopper_emit_cpi!(
569///     ctx.program_id(),
570///     event_authority: &AccountView,
571///     event_authority_bump: u8,
572///     Deposited { amount, depositor }
573/// );
574/// ```
575///
576/// `$event` must be a `#[hopper::event]` type (anything implementing
577/// [`cpi_event::CpiEvent`]). Expands to: build instruction bytes,
578/// invoke_signed with the event_authority PDA as the signer. One CPI,
579/// bounded stack allocation, zero heap.
580#[macro_export]
581macro_rules! hopper_emit_cpi {
582    ( $program_id:expr, $event_authority:expr, $bump:expr, $event:expr ) => {{
583        // Build the wire format into a stack buffer. MAX_EVENT_PAYLOAD
584        // (512) bytes fits every sensibly-sized event; callers with
585        // larger events should grow the buffer at the call site or use
586        // `emit!` with the log-based path.
587        let __ev = $event;
588        // The stack buffer below holds MAX_EVENT_PAYLOAD bytes; an event
589        // that cannot fit fails here at compile time instead of at every
590        // emit.
591        $crate::cpi_event::assert_event_fits(&__ev);
592        let __tag: u8 = $crate::cpi_event::CpiEvent::tag(&__ev);
593        let __payload: &[u8] = $crate::cpi_event::CpiEvent::payload_bytes(&__ev);
594        let mut __buf = [0u8; 2 + 1 + $crate::cpi_event::MAX_EVENT_PAYLOAD];
595        let __n = $crate::cpi_event::encode_event_cpi(__tag, __payload, &mut __buf[..])
596            .ok_or($crate::ProgramError::InvalidInstructionData)?;
597        // Signer seeds for the event-authority PDA. The caller
598        // derived and cached `$bump` so this is a stored-bump CPI.
599        let __bump_byte: [u8; 1] = [$bump];
600        let __seed_slices: [&[u8]; 2] = [$crate::cpi_event::EVENT_AUTHORITY_SEED, &__bump_byte[..]];
601        $crate::cpi_event::invoke_event_cpi(
602            $program_id,
603            $event_authority,
604            &__buf[..__n],
605            &__seed_slices[..],
606        )?;
607    }};
608}
609
610/// Cheap structured logging for hot handlers.
611///
612/// `hopper_log!` is the compute-unit-aware sibling of [`msg!`]. It
613/// dispatches to the backend's native log syscall with no format
614/// machinery, no stack buffer, and no UTF-8 formatting pass. The
615/// tradeoff: fewer ergonomics, predictable CU.
616///
617/// Forms:
618///
619/// - `hopper_log!("static message")` - one `sol_log_` syscall.
620/// - `hopper_log!(my_str_slice)` - same, but for runtime `&str` values.
621/// - `hopper_log!("label:", u64_value)` - one `sol_log_` plus one
622///   `sol_log_64_`. Five `u64` slots (the `sol_log_64_` ABI) are
623///   populated left-to-right and the rest zero.
624/// - `hopper_log!("label:", a, b)` through `hopper_log!("label:", a, b, c, d, e)` -
625///   same pattern; up to five integer values per call.
626///
627/// Reach for `msg!` when you need `{}`-style formatting. Reach for
628/// `hopper_log!` when you are paying for every CU and you already
629/// know the shape of the data.
630#[macro_export]
631macro_rules! hopper_log {
632    // One label + 1..=5 integer values. Each integer is cast to `u64`
633    // at the call site so callers do not need to sprinkle `as u64`.
634    ($label:expr, $a:expr) => {{
635        $crate::log::log($label);
636        $crate::log::log_64($a as u64, 0, 0, 0, 0);
637    }};
638    ($label:expr, $a:expr, $b:expr) => {{
639        $crate::log::log($label);
640        $crate::log::log_64($a as u64, $b as u64, 0, 0, 0);
641    }};
642    ($label:expr, $a:expr, $b:expr, $c:expr) => {{
643        $crate::log::log($label);
644        $crate::log::log_64($a as u64, $b as u64, $c as u64, 0, 0);
645    }};
646    ($label:expr, $a:expr, $b:expr, $c:expr, $d:expr) => {{
647        $crate::log::log($label);
648        $crate::log::log_64($a as u64, $b as u64, $c as u64, $d as u64, 0);
649    }};
650    ($label:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {{
651        $crate::log::log($label);
652        $crate::log::log_64($a as u64, $b as u64, $c as u64, $d as u64, $e as u64);
653    }};
654    // Bare message. Uses the one-argument `log::log` syscall.
655    ($msg:expr) => {{
656        $crate::log::log($msg);
657    }};
658}
659
660/// Declare the explicit Hopper runtime entrypoint bridge.
661///
662/// This is Hopper's direct runtime entrypoint over Solana account memory.
663#[macro_export]
664macro_rules! hopper_entrypoint {
665    ( $process_instruction:expr ) => {
666        $crate::hopper_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
667    };
668    ( $process_instruction:expr, $maximum:expr ) => {
669        /// # Safety
670        ///
671        /// Called by the Solana runtime; `input` is a valid BPF input buffer.
672        #[no_mangle]
673        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
674            const UNINIT: core::mem::MaybeUninit<$crate::__hopper_native::AccountView<'static>> =
675                core::mem::MaybeUninit::<$crate::__hopper_native::AccountView<'static>>::uninit();
676            let mut accounts = [UNINIT; $maximum];
677
678            // SAFETY: `input` is the loader's input buffer (the entrypoint's
679            // contract) and `accounts` has `$maximum` slots, the bound the
680            // parser is given.
681            let (program_id, count, instruction_data) = unsafe {
682                $crate::__hopper_native::raw_input::deserialize_accounts::<$maximum>(
683                    input,
684                    &mut accounts,
685                )
686            };
687
688            // SAFETY: The native and runtime `Address` are both
689            // `#[repr(transparent)]` over `[u8; 32]`.
690            let hopper_program_id = unsafe {
691                &*(program_id as *const $crate::__hopper_native::Address as *const $crate::Address)
692            };
693            // SAFETY: The parser initialized the first `count` slots, and the
694            // runtime `AccountView` is `#[repr(transparent)]` over the native
695            // one (its size and alignment are asserted where it is defined).
696            let hopper_accounts = unsafe {
697                core::slice::from_raw_parts(
698                    accounts.as_ptr() as *const $crate::AccountView<'_>,
699                    count,
700                )
701            };
702
703            match $process_instruction(hopper_program_id, hopper_accounts, instruction_data) {
704                Ok(()) => $crate::__hopper_native::SUCCESS,
705                Err(error) => error.into(),
706            }
707        }
708    };
709}
710
711/// Declare the canonical Hopper program entrypoint.
712#[macro_export]
713macro_rules! program_entrypoint {
714    ( $process_instruction:expr ) => {
715        $crate::hopper_entrypoint!($process_instruction);
716    };
717    ( $process_instruction:expr, $maximum:expr ) => {
718        $crate::hopper_entrypoint!($process_instruction, $maximum);
719    };
720}
721
722/// Declare the fast two-argument Hopper entrypoint.
723///
724/// Uses the SVM's second register (`r2`) to receive instruction data
725/// directly under [SIMD-0321], whose gate is active on all public
726/// clusters (mainnet-beta since 2026-04-01). Post the 2026-07-07 fused
727/// single-pass walk this is CU-neutral (+/- 2, measured 2026-07-21) on
728/// programs whose accounts fit the declared maximum, the fused scanner
729/// already banks the old "~30-40 CU" saving; so the feature stays
730/// opt-in; it is the foundation of the SIMD-0449 table path.
731///
732/// Without the `simd-0321` cargo feature this macro expands to the
733/// standard scanning entrypoint, identical semantics, sound everywhere
734/// today. With the feature it expands to the two-argument form, which
735/// null-checks `r2` and falls back to the scanning parse as defense in
736/// depth.
737///
738/// [SIMD-0321]: https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0321-vm-r2-instruction-data-pointer.md
739#[cfg(feature = "simd-0321")]
740#[macro_export]
741macro_rules! hopper_fast_entrypoint {
742    ( $process_instruction:expr ) => {
743        $crate::hopper_fast_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
744    };
745    ( $process_instruction:expr, $maximum:expr ) => {
746        /// # Safety
747        ///
748        /// Called by the Solana runtime; `input` is a valid BPF input buffer.
749        /// When SIMD-0321 is active, `ix_data` points to the instruction data
750        /// with its u64 length stored at offset -8; when it is not active the
751        /// register is zero and the scanning fallback is taken.
752        #[no_mangle]
753        pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data: *const u8) -> u64 {
754            const UNINIT: core::mem::MaybeUninit<$crate::__hopper_native::AccountView> =
755                core::mem::MaybeUninit::<$crate::__hopper_native::AccountView>::uninit();
756            let mut accounts = [UNINIT; $maximum];
757
758            let (program_id, count, instruction_data) = if ix_data.is_null() {
759                // SIMD-0321 not active on this cluster: r2 is zero. Fall back
760                // to the full scanning parse so the program stays correct.
761                // SAFETY: `input` is the loader-provided input buffer; the
762                // scanning parser owns all bounds/duplicate-marker checks.
763                unsafe {
764                    $crate::__hopper_native::raw_input::deserialize_accounts::<$maximum>(
765                        input,
766                        &mut accounts,
767                    )
768                }
769            } else {
770                // SAFETY: SIMD-0321 guarantees `ix_data` points at the
771                // instruction-data bytes, with the u64 length prefix at
772                // `ix_data - 8` and the 32-byte program id after the data.
773                let ix_len =
774                    unsafe { core::ptr::read_unaligned(ix_data.sub(8) as *const u64) as usize };
775                let instruction_data: &'static [u8] =
776                    unsafe { core::slice::from_raw_parts(ix_data, ix_len) };
777                // SAFETY: program id trails the instruction data per the
778                // loader serialization layout; `Address` is a transparent
779                // `[u8; 32]`, so a reference into the buffer is valid at any
780                // offset and lives as long as the invocation.
781                let program_id: &'static $crate::__hopper_native::Address =
782                    unsafe { &*(ix_data.add(ix_len) as *const $crate::__hopper_native::Address) };
783
784                if $crate::__hopper_native::raw_input::SIMD_0449_TABLE_ENABLED {
785                    // SIMD-0449 build: consume the runtime's appended
786                    // pre-deduplicated account-pointer table, O(1)
787                    // resolution plus one pointer copy per account. The gate
788                    // is a `const`, so the untaken branch folds away entirely.
789                    // Macro programs reach this arm the same way the native
790                    // entrypoint does, so `hopper/simd-0449` is not a no-op
791                    // one tier up.
792                    // SAFETY: the `simd-0449` feature asserts the SIMD is
793                    // active on the target cluster (table present);
794                    // `instruction_data`/`program_id` were derived from the
795                    // SIMD-0321 r2 register above.
796                    unsafe {
797                        $crate::__hopper_native::raw_input::deserialize_accounts_0449_into::<$maximum>(
798                            input,
799                            &mut accounts,
800                            instruction_data,
801                            program_id,
802                        )
803                    }
804                } else {
805                    // SAFETY: `input` is the loader input buffer; account-slot
806                    // framing is validated by `deserialize_accounts_fast`.
807                    unsafe {
808                        $crate::__hopper_native::raw_input::deserialize_accounts_fast::<$maximum>(
809                            input,
810                            &mut accounts,
811                            instruction_data,
812                            program_id,
813                        )
814                    }
815                }
816            };
817
818            // SAFETY: `Address` is a transparent 32-byte wrapper shared by the
819            // native and runtime layers; the reinterpret is layout-identical.
820            let hopper_program_id = unsafe {
821                &*(program_id as *const $crate::__hopper_native::Address as *const $crate::Address)
822            };
823            // SAFETY: the first `count` slots were initialized by the parser;
824            // runtime `AccountView` is repr(transparent) over the native view.
825            let hopper_accounts = unsafe {
826                core::slice::from_raw_parts(accounts.as_ptr() as *const $crate::AccountView, count)
827            };
828
829            match $process_instruction(hopper_program_id, hopper_accounts, instruction_data) {
830                Ok(()) => $crate::__hopper_native::SUCCESS,
831                Err(error) => error.into(),
832            }
833        }
834    };
835}
836
837/// Without the `simd-0321` feature the "fast" entrypoint is an alias for
838/// the standard scanning entrypoint. The SIMD-0321 gate is live on every
839/// public cluster (mainnet-beta 2026-04-01), so the two-argument form is
840/// sound to build; it stays opt-in because the r2 path measured CU-neutral
841/// against the fused scanning walk for ~368 bytes of extra `.text` (see the
842/// `simd-0321` feature note in the workspace `Cargo.toml`). Build with
843/// `--features simd-0321` to select the r2 entrypoint.
844#[cfg(not(feature = "simd-0321"))]
845#[macro_export]
846macro_rules! hopper_fast_entrypoint {
847    ( $process_instruction:expr ) => {
848        $crate::hopper_entrypoint!($process_instruction);
849    };
850    ( $process_instruction:expr, $maximum:expr ) => {
851        $crate::hopper_entrypoint!($process_instruction, $maximum);
852    };
853}
854
855/// Declare the count-exact program entrypoint.
856///
857/// Reads the discriminator from the SIMD-0321 `r2` instruction-data pointer
858/// first, then materializes exactly the matched instruction's declared
859/// account bound before dispatching to the helper `#[program]` generated
860/// for it. `arms` pairs each one-byte discriminator (optionally with an
861/// `if <const>` guard, which folds the arm away when false) with that bound
862/// and that helper. Each helper returns the entry's own return code,
863/// `SUCCESS` or the mapped error, so the dispatch is the entrypoint's last
864/// call. Accounts past the bound are neither materialized nor walked, and
865/// there is no transaction-sized pointer table: the entry cost is the
866/// declared accounts only. The `Context` (segment borrow registry, write
867/// gate, parametric args) is built exactly as on the scanning path.
868///
869/// The `r2` gate (`5xXZc66h4UdB6Yq7FzdBxBiRAFMMScMLwHxk2QZDaNZL`) is active
870/// on mainnet-beta, devnet, and testnet. A runtime that leaves `r2` zero
871/// gets `ProgramError::InvalidArgument` back instead of a scanning
872/// fallback, which keeps the dual-path code out of the binary; `hopper
873/// feature-gate` reports the gate for a target cluster.
874///
875/// A transaction that passes more accounts than the matched arm's bound is
876/// refused with [`ERR_TOO_MANY_ACCOUNTS`] before any account is walked:
877/// the extra accounts would never be materialized, and a handler with
878/// `#[remaining_accounts(max = N)]` must not run on a truncated list.
879#[macro_export]
880macro_rules! hopper_exact_entrypoint {
881    ( $( ( $disc:literal $( if $guard:expr )?, $bound:expr, $helper:path ) ),* $(,)? ) => {
882        /// # Safety
883        ///
884        /// Called by the Solana runtime with the loader input in `input` and,
885        /// under SIMD-0321, the instruction-data pointer in `ix_data` (length
886        /// at `ix_data - 8`, program id after the data).
887        #[no_mangle]
888        pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data: *const u8) -> u64 {
889            if ix_data.is_null() {
890                return $crate::entry_refusal($crate::ProgramError::InvalidArgument);
891            }
892            // SAFETY: SIMD-0321 serialization contract, see above.
893            let ix_len =
894                unsafe { core::ptr::read_unaligned(ix_data.sub(8) as *const u64) as usize };
895            // SAFETY: `ix_len` bytes of instruction data start at `ix_data`
896            // and live for the whole invocation.
897            let instruction_data: &'static [u8] =
898                unsafe { core::slice::from_raw_parts(ix_data, ix_len) };
899            // SAFETY: the 32-byte program id trails the data; `Address` is a
900            // transparent `[u8; 32]` with alignment 1.
901            let program_id: &'static $crate::Address =
902                unsafe { &*(ix_data.add(ix_len) as *const $crate::Address) };
903            // The discriminator is read once, into a register. The account
904            // walk stores into the input region, which the compiler cannot
905            // prove misses the instruction data, so a second read of the
906            // byte after the walk would be a second load.
907            let disc: u8 = match instruction_data.first() {
908                ::core::option::Option::Some(&disc) => disc,
909                ::core::option::Option::None => {
910                    return $crate::entry_refusal($crate::ProgramError::InvalidInstructionData)
911                }
912            };
913            // The matched arm's bound first, so one walk and one `Context`
914            // serve every instruction (no per-arm copy of either). A guarded
915            // arm whose guard is a false constant folds away.
916            let bound: usize = match disc {
917                $( $disc $( if $guard )? => $bound, )*
918                _ => return $crate::entry_refusal($crate::ProgramError::InvalidInstructionData),
919            };
920            // Count-exact means exact. Accounts past the arm's bound are
921            // never materialized, so a remaining-accounts handler handed
922            // more than its declared maximum would otherwise process a
923            // silently truncated batch. The loader's count word is compared
924            // with the bound before the walk (two instructions).
925            // SAFETY: `input` is the loader input, which starts with the
926            // account count.
927            let total = unsafe { $crate::__hopper_native::raw_input::loader_account_count(input) };
928            if total > bound {
929                return $crate::entry_refusal($crate::ERR_TOO_MANY_ACCOUNTS);
930            }
931            const WIDEST: usize = $crate::max_account_bound(&[ $( $bound ),* ]);
932            const UNINIT: core::mem::MaybeUninit<
933                $crate::__hopper_native::AccountView<'static>,
934            > = core::mem::MaybeUninit::uninit();
935            let mut views = [UNINIT; WIDEST];
936            // SAFETY: `input` is the loader input buffer; the prefix walk
937            // validates its own framing and never exceeds `WIDEST` slots.
938            let count = unsafe {
939                $crate::__hopper_native::raw_input::deserialize_leading_accounts::<WIDEST>(
940                    input,
941                    &mut views,
942                    bound,
943                )
944            };
945            // SAFETY: the first `count` slots were initialized by the walk;
946            // runtime `AccountView` is repr(transparent) over the native view.
947            let accounts = unsafe {
948                core::slice::from_raw_parts(views.as_ptr() as *const $crate::AccountView<'_>, count)
949            };
950            let mut ctx = $crate::Context::new(program_id, accounts, instruction_data);
951            // Each helper returns the entry code itself (`SUCCESS` or the
952            // mapped error), so this is the last call.
953            match disc {
954                $( $disc $( if $guard )? => $helper(&mut ctx, instruction_data), )*
955                _ => $crate::entry_refusal($crate::ProgramError::InvalidInstructionData),
956            }
957        }
958    };
959}
960
961/// The entrypoint's return code for a refused instruction. Cold and out of
962/// line, so the accepted path neither materializes an error code nor
963/// carries the conversion.
964#[doc(hidden)]
965#[cold]
966#[inline(never)]
967pub fn entry_refusal(error: crate::ProgramError) -> u64 {
968    error.into()
969}
970
971/// Custom-error page for refusals at the program entry, below the
972/// `0xC000` context-acquisition page.
973pub const ENTRY_REFUSAL_PAGE: u32 = 0xB000;
974
975/// The count-exact entrypoint (`profile = "tiny"`) received more accounts
976/// than the matched instruction's bound: its declared context plus any
977/// `#[remaining_accounts(max = N)]`. The scanning profiles keep Anchor's
978/// lenient rule and ignore extra accounts; the count-exact profile never
979/// materializes them, so it refuses them instead of letting a
980/// remaining-accounts handler run on a truncated list.
981pub const ERR_TOO_MANY_ACCOUNTS: ProgramError = ProgramError::Custom(ENTRY_REFUSAL_PAGE | 0x01);
982
983/// One account was passed in two mutable roles of a context that did not
984/// declare the alias with `dup = other`. `#[derive(Accounts)]` emits
985/// `Context::require_distinct_slots` for every pair of mutable slots, so a
986/// handler's sequential writes through two roles can never land on one
987/// account by accident (the segment borrow registry only sees borrows that
988/// are live at the same time).
989pub const ERR_ALIASED_MUTABLE_ACCOUNTS: ProgramError =
990    ProgramError::Custom(ENTRY_REFUSAL_PAGE | 0x02);
991
992/// The widest of a program's per-instruction account bounds; sizes the
993/// scratch the count-exact entrypoint materializes into.
994#[doc(hidden)]
995pub const fn max_account_bound(bounds: &[usize]) -> usize {
996    let mut widest = 0usize;
997    let mut i = 0usize;
998    while i < bounds.len() {
999        if bounds[i] > widest {
1000            widest = bounds[i];
1001        }
1002        i += 1;
1003    }
1004    widest
1005}
1006
1007/// Backward-compatible alias for the fast Hopper entrypoint macro.
1008#[macro_export]
1009macro_rules! fast_entrypoint {
1010    ( $process_instruction:expr ) => {
1011        $crate::hopper_fast_entrypoint!($process_instruction);
1012    };
1013    ( $process_instruction:expr, $maximum:expr ) => {
1014        $crate::hopper_fast_entrypoint!($process_instruction, $maximum);
1015    };
1016}
1017
1018/// Declare the Hopper lazy entrypoint, RUNTIME-typed, matching the
1019/// eager `hopper_fast_entrypoint!`'s layering.
1020///
1021/// The handler receives `&mut hopper_runtime::lazy::LazyContext` (also
1022/// in the facade prelude) and returns the runtime `ProgramResult`; the
1023/// expansion bridges to the substrate lazy parser and maps errors
1024/// through the layout-twin glue at the boundary. Substrate authors who
1025/// want the native-typed context use the substrate layer's own
1026/// `hopper_lazy_entrypoint!` directly, exactly as with the eager pair.
1027#[macro_export]
1028macro_rules! hopper_lazy_entrypoint {
1029    ( $process:expr ) => {
1030        $crate::__hopper_native::hopper_lazy_entrypoint!(
1031            |__hopper_native_ctx: &mut $crate::__hopper_native::LazyContext|
1032                -> ::core::result::Result<(), $crate::__hopper_native::error::ProgramError> {
1033                let mut __hopper_ctx =
1034                    $crate::lazy::LazyContext::from_native(__hopper_native_ctx);
1035                match $process(&mut __hopper_ctx) {
1036                    ::core::result::Result::Ok(()) => ::core::result::Result::Ok(()),
1037                    ::core::result::Result::Err(e) => {
1038                        ::core::result::Result::Err(::core::convert::From::from(e))
1039                    }
1040                }
1041            }
1042        );
1043    };
1044}
1045
1046/// Backward-compatible alias for the lazy Hopper entrypoint macro.
1047#[macro_export]
1048macro_rules! lazy_entrypoint {
1049    ( $process:expr ) => {
1050        $crate::hopper_lazy_entrypoint!($process);
1051    };
1052}
1053
1054/// Refuse allocations immediately using the native SVM abort boundary.
1055#[macro_export]
1056macro_rules! no_allocator {
1057    () => {
1058        $crate::__hopper_native::no_allocator!();
1059    };
1060}
1061
1062/// Install the default bump allocator over the SVM heap region. Opt-in
1063/// counterpart to [`no_allocator!`] for programs that need `alloc` on a
1064/// cold path. See [`hopper_native::BumpAllocator`].
1065///
1066/// `default_allocator!(heap = 128 * 1024)` declares a larger heap, up to
1067/// 256 KiB, for transactions that carry `RequestHeapFrame`.
1068#[macro_export]
1069macro_rules! default_allocator {
1070    () => {
1071        $crate::default_allocator!(heap = $crate::__hopper_native::HEAP_LENGTH);
1072    };
1073    (heap = $len:expr) => {
1074        #[cfg(target_os = "solana")]
1075        #[global_allocator]
1076        static ALLOCATOR: $crate::__hopper_native::BumpAllocator =
1077            $crate::__hopper_native::BumpAllocator::new($len);
1078    };
1079}
1080
1081/// Abort a panicking no_std program immediately without burning its remaining
1082/// CU. With the `panic-location` feature the file, line, and column are
1083/// reported first; with `panic-message`, the message.
1084#[macro_export]
1085macro_rules! nostd_panic_handler {
1086    () => {
1087        $crate::__hopper_native::nostd_panic_handler!();
1088    };
1089}