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