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