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