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. Pinocchio and
6//! solana-program remain compatibility backends isolated behind `compat/`.
7
8#![no_std]
9#![deny(unsafe_op_in_unsafe_fn)]
10
11#[cfg(test)]
12extern crate std;
13
14#[cfg(feature = "solana-program-backend")]
15extern crate alloc;
16
17#[cfg(any(
18    all(feature = "hopper-native-backend", feature = "legacy-pinocchio-compat"),
19    all(feature = "hopper-native-backend", feature = "solana-program-backend"),
20    all(
21        feature = "legacy-pinocchio-compat",
22        feature = "solana-program-backend"
23    ),
24))]
25compile_error!(
26    "Only one backend feature may be enabled at a time: hopper-native-backend, legacy-pinocchio-compat, or solana-program-backend"
27);
28
29#[cfg(not(any(
30    feature = "hopper-native-backend",
31    feature = "legacy-pinocchio-compat",
32    feature = "solana-program-backend",
33)))]
34compile_error!(
35    "At least one backend feature must be enabled: hopper-native-backend, legacy-pinocchio-compat, or solana-program-backend"
36);
37
38#[doc(hidden)]
39pub mod compat;
40
41pub mod account;
42pub mod account_wrappers;
43pub mod address;
44pub mod audit;
45pub mod borrow;
46pub(crate) mod borrow_registry;
47pub mod cpi;
48pub mod cpi_event;
49pub mod crank;
50pub mod dyn_cpi;
51pub mod error;
52pub mod field_map;
53pub mod foreign;
54pub mod interop;
55pub mod log;
56pub mod migrate;
57pub mod pod;
58pub mod policy;
59pub mod proof;
60pub mod ref_only;
61pub mod result;
62pub mod segment;
63pub mod tail;
64pub mod utils;
65pub mod zerocopy;
66// Re-export the sealed marker module at the crate root so macro
67// codegen can address it as `::hopper_runtime::__sealed::...`. It's
68// doc-hidden because it's the audit's Step 5 enforcement surface,
69// not a normal-user-facing API.
70#[doc(hidden)]
71pub use zerocopy::__sealed;
72pub mod context;
73pub mod instruction;
74pub mod layout;
75pub mod option_byte;
76pub mod pda;
77pub mod remaining;
78pub mod rent;
79pub mod segment_borrow;
80pub mod segment_lease;
81pub mod syscall;
82pub mod syscalls;
83pub mod system;
84pub mod token;
85pub mod token_2022_ext;
86
87pub use account::AccountView;
88pub use account_wrappers::{
89    Account, InitAccount, Interface, InterfaceAccount, InterfaceAccountLayout,
90    InterfaceAccountResolve, InterfaceSpec, Program, ProgramId, Signer as HopperSigner,
91    SystemAccount, SystemId, UncheckedAccount,
92};
93pub use address::Address;
94pub use audit::{AccountAudit, DuplicateAccount};
95pub use borrow::{Ref, RefMut};
96pub use context::Context;
97pub use cpi::{invoke, invoke_signed};
98pub use error::ProgramError;
99pub use field_map::{FieldInfo, FieldMap};
100pub use foreign::{ForeignLens, ForeignManifest};
101pub use interop::TransparentAddress;
102pub use migrate::{apply_pending_migrations, LayoutMigration, MigrationEdge};
103pub use policy::{HopperInstructionPolicy, HopperProgramPolicy, HopperProgramProfile};
104pub use proof::{
105    AccountProof, ExecutableChecked, HasOneChecked, LayoutChecked, OwnerChecked, SeedsChecked,
106    SignerChecked, TokenExtensionsChecked, Unchecked, WritableChecked,
107};
108pub use ref_only::HopperRefOnly;
109pub use remaining::{
110    RemainingAccountViews, RemainingAccounts, RemainingError, RemainingMode, RemainingSigners,
111    MAX_REMAINING_ACCOUNTS,
112};
113pub use tail::{
114    borrow_address_slice, borrow_bounded_str, read_tail, read_tail_len, tail_capacity,
115    tail_payload, write_tail, BoundedString, BoundedVec, HopperString, HopperVec, TailCodec,
116    TailElement,
117};
118
119/// Compose a layout's `LayoutMigration::MIGRATIONS` chain from a list
120/// of `#[hopper::migrate]`-emitted edge constants.
121///
122/// ```ignore
123/// #[hopper::migrate(from = 1, to = 2)]
124/// pub fn vault_v1_to_v2(body: &mut [u8]) -> ProgramResult { Ok(()) }
125///
126/// hopper::layout_migrations! {
127///     Vault = [VAULT_V1_TO_V2_EDGE, VAULT_V2_TO_V3_EDGE],
128/// }
129/// ```
130///
131/// Emits `impl LayoutMigration for Vault { const MIGRATIONS = .. }`.
132/// Each list entry must evaluate to a
133/// [`MigrationEdge`](crate::migrate::MigrationEdge). typically the
134/// `<UPPER_SNAKE_FN_NAME>_EDGE` constant that
135/// `#[hopper::migrate]` emits alongside each migration function.
136/// Chain continuity (every adjacent pair must satisfy
137/// `a.to_epoch == b.from_epoch`) is enforced at runtime by
138/// [`apply_pending_migrations`].
139#[macro_export]
140macro_rules! layout_migrations {
141    ( $layout:ty = [ $( $edge:expr ),+ $(,)? ] $(,)? ) => {
142        impl $crate::migrate::LayoutMigration for $layout {
143            const MIGRATIONS: &'static [$crate::migrate::MigrationEdge] = &[
144                $( $edge ),+
145            ];
146        }
147    };
148}
149#[cfg(feature = "hopper-native-backend")]
150pub use instruction::CpiAccount;
151pub use instruction::{InstructionAccount, InstructionView, Seed, Signer};
152pub use layout::{HopperHeader, LayoutContract, LayoutInfo};
153pub use pod::Pod;
154pub use result::ProgramResult;
155pub use segment::{
156    FieldCapability, Segment, TypedSegment, FIELD_POLICY_AUTHORITY_GATED,
157    FIELD_POLICY_CHECKED_MATH, FIELD_POLICY_IMMUTABLE_AFTER_INIT, FIELD_ROLE_AUTHORITY,
158    FIELD_ROLE_BALANCE, FIELD_ROLE_DATA, FIELD_ROLE_VERSION,
159};
160pub use segment_borrow::{AccessKind, SegmentBorrow, SegmentBorrowGuard, SegmentBorrowRegistry};
161pub use segment_lease::{SegRef, SegRefMut, SegmentLease};
162pub use zerocopy::{AccountLayout, WireLayout, ZeroCopy};
163
164pub const MAX_TX_ACCOUNTS: usize = compat::BACKEND_MAX_TX_ACCOUNTS;
165pub const SUCCESS: u64 = compat::BACKEND_SUCCESS;
166
167#[cfg(feature = "hopper-native-backend")]
168#[doc(hidden)]
169pub use hopper_native as __hopper_native;
170
171#[cfg(feature = "solana-program-backend")]
172#[doc(hidden)]
173pub use ::solana_program as __solana_program;
174
175#[doc(hidden)]
176pub use five8_const as __five8_const;
177
178/// Compile-time base58 address literal.
179#[macro_export]
180macro_rules! address {
181    ( $literal:expr ) => {
182        $crate::Address::new_from_array($crate::__five8_const::decode_32_const($literal))
183    };
184}
185
186/// Early-return with an error if the condition is false.
187#[macro_export]
188macro_rules! require {
189    ( $cond:expr, $err:expr ) => {
190        if !($cond) {
191            return Err($err);
192        }
193    };
194    ( $cond:expr ) => {
195        if !($cond) {
196            return Err($crate::ProgramError::InvalidArgument);
197        }
198    };
199}
200
201/// Assert two values are equal, returning an error on mismatch.
202#[macro_export]
203macro_rules! require_eq {
204    ( $left:expr, $right:expr, $err:expr ) => {
205        if ($left) != ($right) {
206            return Err($err);
207        }
208    };
209    ( $left:expr, $right:expr ) => {
210        if ($left) != ($right) {
211            return Err($crate::ProgramError::InvalidArgument);
212        }
213    };
214}
215
216/// Assert two values are not equal. Early-returns with the supplied
217/// error on match (or `ProgramError::InvalidArgument` in the short
218/// form). Symmetric with [`require_eq!`].
219#[macro_export]
220macro_rules! require_neq {
221    ( $left:expr, $right:expr, $err:expr ) => {
222        if ($left) == ($right) {
223            return Err($err);
224        }
225    };
226    ( $left:expr, $right:expr ) => {
227        if ($left) == ($right) {
228            return Err($crate::ProgramError::InvalidArgument);
229        }
230    };
231}
232
233/// Assert two public keys (or any byte slices convertible via
234/// [`AsRef<[u8; 32]>`]) are equal. Narrower than [`require_eq!`] but
235/// matches the ergonomic spelling ecosystem migrators coming from
236/// Anchor / Jiminy are familiar with.
237///
238/// ```ignore
239/// hopper::require_keys_eq!(
240///     vault.authority,
241///     ctx.signer.address(),
242///     ProgramError::InvalidAccountData,
243/// );
244/// ```
245#[macro_export]
246macro_rules! require_keys_eq {
247    ( $left:expr, $right:expr, $err:expr ) => {
248        if ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left)
249            != ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right)
250        {
251            return Err($err);
252        }
253    };
254    ( $left:expr, $right:expr ) => {
255        if ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left)
256            != ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right)
257        {
258            return Err($crate::ProgramError::InvalidAccountData);
259        }
260    };
261}
262
263/// Assert two public keys are *not* equal. Used for pinning distinct
264/// accounts (authority != user, source != destination). Same coercion
265/// and error semantics as [`require_keys_eq!`].
266#[macro_export]
267macro_rules! require_keys_neq {
268    ( $left:expr, $right:expr, $err:expr ) => {
269        if ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left)
270            == ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right)
271        {
272            return Err($err);
273        }
274    };
275    ( $left:expr, $right:expr ) => {
276        if ::core::convert::AsRef::<[u8; 32]>::as_ref(&$left)
277            == ::core::convert::AsRef::<[u8; 32]>::as_ref(&$right)
278        {
279            return Err($crate::ProgramError::InvalidAccountData);
280        }
281    };
282}
283
284/// Assert `left >= right`, returning the supplied error on underrun.
285/// Useful for lamport / balance checks.
286#[macro_export]
287macro_rules! require_gte {
288    ( $left:expr, $right:expr, $err:expr ) => {
289        if !($left >= $right) {
290            return Err($err);
291        }
292    };
293    ( $left:expr, $right:expr ) => {
294        if !($left >= $right) {
295            return Err($crate::ProgramError::InsufficientFunds);
296        }
297    };
298}
299
300/// Assert `left > right` strictly.
301#[macro_export]
302macro_rules! require_gt {
303    ( $left:expr, $right:expr, $err:expr ) => {
304        if !($left > $right) {
305            return Err($err);
306        }
307    };
308    ( $left:expr, $right:expr ) => {
309        if !($left > $right) {
310            return Err($crate::ProgramError::InvalidArgument);
311        }
312    };
313}
314
315/// Assert `left < right` strictly. Anchor-parity sibling of
316/// [`require_gt!`]. Default error is `ProgramError::InvalidArgument`
317/// because a failed ordering check most often flags a bad user input.
318#[macro_export]
319macro_rules! require_lt {
320    ( $left:expr, $right:expr, $err:expr ) => {
321        if !($left < $right) {
322            return Err($err);
323        }
324    };
325    ( $left:expr, $right:expr ) => {
326        if !($left < $right) {
327            return Err($crate::ProgramError::InvalidArgument);
328        }
329    };
330}
331
332/// Assert `left <= right`. Anchor-parity sibling of [`require_gte!`].
333#[macro_export]
334macro_rules! require_lte {
335    ( $left:expr, $right:expr, $err:expr ) => {
336        if !($left <= $right) {
337            return Err($err);
338        }
339    };
340    ( $left:expr, $right:expr ) => {
341        if !($left <= $right) {
342            return Err($crate::ProgramError::InvalidArgument);
343        }
344    };
345}
346
347/// Return an error immediately. Parallel to Anchor's `err!`.
348///
349/// The macro expands to a bare `return Err(...)`, so the call site
350/// reads like a control-flow keyword rather than an expression. The
351/// argument is evaluated as an expression so either a Hopper-generated
352/// error code or a raw `ProgramError` works.
353///
354/// ```ignore
355/// if amount == 0 {
356///     return err!(VaultError::ZeroDeposit);
357/// }
358/// ```
359#[macro_export]
360macro_rules! err {
361    ( $e:expr ) => {
362        return ::core::result::Result::Err($crate::ProgramError::from($e))
363    };
364}
365
366/// Alias for [`err!`]. Anchor compatibility shim so ported code needs
367/// no rename. Functionally identical.
368#[macro_export]
369macro_rules! error {
370    ( $e:expr ) => {
371        return ::core::result::Result::Err($crate::ProgramError::from($e))
372    };
373}
374
375/// Auditable raw-pointer boundary.
376///
377/// Wraps a block that needs `unsafe` in a named Hopper macro so an
378/// auditor can grep `hopper_unsafe_region!` and find every raw
379/// reinterpretation in the tree with one command. The macro expands
380/// to a plain `unsafe { ... }` block: zero runtime cost, identical
381/// codegen, but the invocation site is nameable and documented.
382///
383/// Usage:
384///
385/// ```ignore
386/// let cleared = hopper::hopper_unsafe_region!("clear rewards via raw ptr", {
387///     let ptr = ctx.as_mut_ptr(0)?;
388///     (ptr.add(24) as *mut u64).write_unaligned(0);
389///     0u64
390/// });
391/// ```
392///
393/// The label is a compile-time string literal. It is discarded by
394/// the expansion but serves as inline documentation an auditor
395/// reads alongside the `unsafe` body.
396#[macro_export]
397macro_rules! hopper_unsafe_region {
398    ( $label:literal, $body:block ) => {{
399        // The label is a compile-time string literal, captured so it
400        // surfaces in `cargo expand` output and can be grep'd out of
401        // the expanded tree the same way as the macro name.
402        const _HOPPER_UNSAFE_REGION_LABEL: &str = $label;
403        #[allow(unused_unsafe)]
404        // 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.
405        unsafe { $body }
406    }};
407}
408
409/// Backend-neutral logging macro.
410#[macro_export]
411macro_rules! msg {
412    ( $literal:expr ) => {{
413        $crate::log::log($literal);
414    }};
415    ( $fmt:expr, $($arg:tt)* ) => {{
416        #[cfg(target_os = "solana")]
417        {
418            use core::fmt::Write;
419            let mut buf = [0u8; 256];
420            let mut wrapper = $crate::log::StackWriter::new(&mut buf);
421            let _ = write!(wrapper, $fmt, $($arg)*);
422            let len = wrapper.pos();
423            $crate::log::log(
424                // 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.
425                unsafe { core::str::from_utf8_unchecked(&buf[..len]) }
426            );
427        }
428        #[cfg(not(target_os = "solana"))]
429        {
430            let _ = ($fmt, $($arg)*);
431        }
432    }};
433}
434
435/// Emit a Hopper event via self-CPI for reliable indexing.
436///
437/// Wraps [`cpi_event::encode_event_cpi`] and a call into the active
438/// backend's `invoke_signed` so indexers see the event as an inner
439/// instruction in the transaction metadata. Log output is size-capped; inner
440/// instructions are retained. Anchor's `emit_cpi!` solves the same problem
441/// with the same trick; Hopper's lives in pure Rust so it works under
442/// `no_std` and any of the three backends.
443///
444/// ## Required program plumbing
445///
446/// The caller must declare a sentinel handler so the runtime routes
447/// the self-CPI somewhere:
448///
449/// ```ignore
450/// #[instruction(discriminator = [0xE0, 0x1E])]
451/// fn __hopper_event_sink(_ctx: &mut Context<'_>) -> ProgramResult {
452///     Ok(())
453/// }
454/// ```
455///
456/// And a PDA named `event_authority` seeded with
457/// `[b"__hopper_event_authority"]` so the CPI has a signer.
458///
459/// ## Usage
460///
461/// ```ignore
462/// hopper_emit_cpi!(
463///     ctx.program_id(),
464///     event_authority: &AccountView,
465///     event_authority_bump: u8,
466///     Deposited { amount, depositor }
467/// );
468/// ```
469///
470/// Expands to: build instruction bytes, invoke_signed with the
471/// event_authority PDA as the signer. One CPI, bounded stack
472/// allocation, zero heap.
473#[macro_export]
474macro_rules! hopper_emit_cpi {
475    ( $program_id:expr, $event_authority:expr, $bump:expr, $event:expr ) => {{
476        // Build the wire format into a stack buffer. 512 payload bytes
477        // fits every sensibly-sized event; callers with larger events
478        // should grow the buffer at the call site or use `emit!` with
479        // the log-based path.
480        let __ev = $event;
481        let __tag: u8 = ::core::convert::Into::<u8>::into(__ev.tag());
482        let __payload: &[u8] = __ev.as_bytes();
483        let mut __buf = [0u8; 2 + 1 + 512];
484        let __n = $crate::cpi_event::encode_event_cpi(__tag, __payload, &mut __buf[..])
485            .ok_or($crate::ProgramError::InvalidInstructionData)?;
486        // Signer seeds for the event-authority PDA. The caller
487        // derived and cached `$bump` so this is a stored-bump CPI.
488        let __bump_byte: [u8; 1] = [$bump];
489        let __seed_slices: [&[u8]; 2] = [b"__hopper_event_authority", &__bump_byte[..]];
490        $crate::cpi_event::invoke_event_cpi(
491            $program_id,
492            $event_authority,
493            &__buf[..__n],
494            &__seed_slices[..],
495        )?;
496    }};
497}
498
499/// Cheap structured logging for hot handlers.
500///
501/// `hopper_log!` is the compute-unit-aware sibling of [`msg!`]. It
502/// dispatches to the backend's native log syscall with no format
503/// machinery, no stack buffer, and no UTF-8 formatting pass. The
504/// tradeoff: fewer ergonomics, predictable CU.
505///
506/// Forms:
507///
508/// - `hopper_log!("static message")` - one `sol_log_` syscall.
509/// - `hopper_log!(my_str_slice)` - same, but for runtime `&str` values.
510/// - `hopper_log!("label:", u64_value)` - one `sol_log_` plus one
511///   `sol_log_64_`. Five `u64` slots (the `sol_log_64_` ABI) are
512///   populated left-to-right and the rest zero.
513/// - `hopper_log!("label:", a, b)` through `hopper_log!("label:", a, b, c, d, e)` -
514///   same pattern; up to five integer values per call.
515///
516/// Reach for `msg!` when you need `{}`-style formatting. Reach for
517/// `hopper_log!` when you are paying for every CU and you already
518/// know the shape of the data.
519#[macro_export]
520macro_rules! hopper_log {
521    // One label + 1..=5 integer values. Each integer is cast to `u64`
522    // at the call site so callers do not need to sprinkle `as u64`.
523    ($label:expr, $a:expr) => {{
524        $crate::log::log($label);
525        $crate::log::log_64($a as u64, 0, 0, 0, 0);
526    }};
527    ($label:expr, $a:expr, $b:expr) => {{
528        $crate::log::log($label);
529        $crate::log::log_64($a as u64, $b as u64, 0, 0, 0);
530    }};
531    ($label:expr, $a:expr, $b:expr, $c:expr) => {{
532        $crate::log::log($label);
533        $crate::log::log_64($a as u64, $b as u64, $c as u64, 0, 0);
534    }};
535    ($label:expr, $a:expr, $b:expr, $c:expr, $d:expr) => {{
536        $crate::log::log($label);
537        $crate::log::log_64($a as u64, $b as u64, $c as u64, $d as u64, 0);
538    }};
539    ($label:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {{
540        $crate::log::log($label);
541        $crate::log::log_64($a as u64, $b as u64, $c as u64, $d as u64, $e as u64);
542    }};
543    // Bare message. Uses the one-argument `log::log` syscall.
544    ($msg:expr) => {{
545        $crate::log::log($msg);
546    }};
547}
548
549/// Declare the explicit Hopper runtime entrypoint bridge.
550///
551/// With `hopper-native-backend`, this is a thin alias to Hopper Native's raw
552/// entrypoint macro. With compatibility backends, it delegates to `compat/`.
553#[macro_export]
554macro_rules! hopper_entrypoint {
555    ( $process_instruction:expr ) => {
556        $crate::hopper_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
557    };
558    ( $process_instruction:expr, $maximum:expr ) => {
559        #[cfg(feature = "hopper-native-backend")]
560        /// # Safety
561        ///
562        /// Called by the Solana runtime; `input` is a valid BPF input buffer.
563        #[no_mangle]
564        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
565            const UNINIT: core::mem::MaybeUninit<$crate::__hopper_native::AccountView> =
566                core::mem::MaybeUninit::<$crate::__hopper_native::AccountView>::uninit();
567            let mut accounts = [UNINIT; $maximum];
568
569            // 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.
570            let (program_id, count, instruction_data) = unsafe {
571                $crate::__hopper_native::raw_input::deserialize_accounts::<$maximum>(
572                    input,
573                    &mut accounts,
574                )
575            };
576
577            // 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.
578            let hopper_program_id = unsafe {
579                &*(&program_id as *const $crate::__hopper_native::Address as *const $crate::Address)
580            };
581            // 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.
582            let hopper_accounts = unsafe {
583                core::slice::from_raw_parts(accounts.as_ptr() as *const $crate::AccountView, count)
584            };
585
586            match $process_instruction(hopper_program_id, hopper_accounts, instruction_data) {
587                Ok(()) => $crate::__hopper_native::SUCCESS,
588                Err(error) => error.into(),
589            }
590        }
591
592        #[cfg(any(
593            feature = "legacy-pinocchio-compat",
594            feature = "solana-program-backend"
595        ))]
596        $crate::__hopper_compat_entrypoint!($process_instruction, $maximum);
597    };
598}
599
600/// Declare the canonical Hopper program entrypoint.
601#[macro_export]
602macro_rules! program_entrypoint {
603    ( $process_instruction:expr ) => {
604        $crate::hopper_entrypoint!($process_instruction);
605    };
606    ( $process_instruction:expr, $maximum:expr ) => {
607        $crate::hopper_entrypoint!($process_instruction, $maximum);
608    };
609}
610
611/// Declare the fast two-argument Hopper entrypoint.
612///
613/// Uses the SVM's second register to receive instruction data directly,
614/// eliminating the full account-scanning pass. Saves ~30-40 CU per
615/// instruction. Requires SVM runtime ≥1.17.
616#[macro_export]
617macro_rules! hopper_fast_entrypoint {
618    ( $process_instruction:expr ) => {
619        $crate::hopper_fast_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
620    };
621    ( $process_instruction:expr, $maximum:expr ) => {
622        #[cfg(feature = "hopper-native-backend")]
623        /// # Safety
624        ///
625        /// Called by the Solana runtime; `input` is a valid BPF input buffer
626        /// and `ix_data` points to the instruction data with its u64 length
627        /// stored at offset -8.
628        #[no_mangle]
629        pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data: *const u8) -> u64 {
630            const UNINIT: core::mem::MaybeUninit<$crate::__hopper_native::AccountView> =
631                core::mem::MaybeUninit::<$crate::__hopper_native::AccountView>::uninit();
632            let mut accounts = [UNINIT; $maximum];
633
634            // 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.
635            let ix_len = unsafe { *(ix_data.sub(8) as *const u64) as usize };
636            let instruction_data: &'static [u8] =
637                unsafe { core::slice::from_raw_parts(ix_data, ix_len) };
638            // 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.
639            let program_id = unsafe {
640                core::ptr::read(ix_data.add(ix_len) as *const $crate::__hopper_native::Address)
641            };
642
643            // 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.
644            let (program_id, count, instruction_data) = unsafe {
645                $crate::__hopper_native::raw_input::deserialize_accounts_fast::<$maximum>(
646                    input,
647                    &mut accounts,
648                    instruction_data,
649                    program_id,
650                )
651            };
652
653            // 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.
654            let hopper_program_id = unsafe {
655                &*(&program_id as *const $crate::__hopper_native::Address as *const $crate::Address)
656            };
657            // 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.
658            let hopper_accounts = unsafe {
659                core::slice::from_raw_parts(accounts.as_ptr() as *const $crate::AccountView, count)
660            };
661
662            match $process_instruction(hopper_program_id, hopper_accounts, instruction_data) {
663                Ok(()) => $crate::__hopper_native::SUCCESS,
664                Err(error) => error.into(),
665            }
666        }
667
668        #[cfg(any(
669            feature = "legacy-pinocchio-compat",
670            feature = "solana-program-backend"
671        ))]
672        compile_error!("hopper_fast_entrypoint! requires hopper-native-backend");
673    };
674}
675
676/// Backward-compatible alias for the fast Hopper entrypoint macro.
677#[macro_export]
678macro_rules! fast_entrypoint {
679    ( $process_instruction:expr ) => {
680        $crate::hopper_fast_entrypoint!($process_instruction);
681    };
682    ( $process_instruction:expr, $maximum:expr ) => {
683        $crate::hopper_fast_entrypoint!($process_instruction, $maximum);
684    };
685}
686
687/// Declare the Hopper lazy entrypoint.
688#[macro_export]
689macro_rules! hopper_lazy_entrypoint {
690    ( $process:expr ) => {
691        #[cfg(feature = "hopper-native-backend")]
692        $crate::__hopper_native::hopper_lazy_entrypoint!($process);
693
694        #[cfg(any(
695            feature = "legacy-pinocchio-compat",
696            feature = "solana-program-backend"
697        ))]
698        compile_error!("hopper_lazy_entrypoint! requires hopper-native-backend");
699    };
700}
701
702/// Backward-compatible alias for the lazy Hopper entrypoint macro.
703#[macro_export]
704macro_rules! lazy_entrypoint {
705    ( $process:expr ) => {
706        $crate::hopper_lazy_entrypoint!($process);
707    };
708}
709
710#[macro_export]
711macro_rules! no_allocator {
712    () => {
713        #[cfg(target_os = "solana")]
714        mod __hopper_allocator {
715            struct NoAlloc;
716
717            unsafe impl core::alloc::GlobalAlloc for NoAlloc {
718                unsafe fn alloc(&self, _layout: core::alloc::Layout) -> *mut u8 {
719                    core::ptr::null_mut()
720                }
721
722                unsafe fn dealloc(&self, _ptr: *mut u8, _layout: core::alloc::Layout) {}
723            }
724
725            #[global_allocator]
726            static ALLOCATOR: NoAlloc = NoAlloc;
727        }
728    };
729}
730
731#[macro_export]
732macro_rules! nostd_panic_handler {
733    () => {
734        #[cfg(target_os = "solana")]
735        #[panic_handler]
736        fn panic(_info: &core::panic::PanicInfo) -> ! {
737            let _ = _info;
738            loop {
739                core::hint::spin_loop();
740            }
741        }
742    };
743}