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