Skip to content
hopper
Get started
Safety / proc-macros

Proc macro policy

The public macro contract for account, context, program, instruction, and policy generation.

Core Rule

No proc macros are required for correctness or core functionality.

Hopper must remain fully usable for layouts, overlays, pod access, validation, phases, receipts, compatibility checks, and migration tooling without relying on proc macros for soundness.

What proc macros may do

Proc macros are allowed for optional ergonomic and schema-generation tasks:

  • #[hopper::state] -- emit layout contracts and segment metadata
  • #[derive(Accounts)] -- emit first-touch typed account binding over Hopper runtime wrappers
  • #[hopper::context] -- emit lower-level typed account accessors for migrations and systems-mode code
  • #[hopper::program] -- emit dispatch glue over ordinary Hopper handlers
  • #[derive(HopperSchema)] -- emit LayoutManifest const from struct
  • #[derive(HopperInstruction)] -- emit instruction metadata
  • #[derive(HopperEvent)] -- emit event metadata
  • #[derive(HopperManifest)] -- assemble full program manifest

These generate code around existing Hopper runtime semantics, not new runtime behavior. The program works identically with or without them.

For first-touch typed account contexts, this means proc macros may emit the Ctx<T> binding and ctx.accounts.* wrapper construction used by Account<'info, T>, Signer<'info>, Program<'info, P>, and related account types. For systems-mode context macros, generated helpers such as segment accessors and raw-account escape hatches must remain direct, inspectable calls into AccountView and Context, not hidden logic.

What proc macros must not do

Proc macros must not hide:

  • Memory model guarantees (alignment, bounds, aliasing)
  • Unsafe access patterns
  • Validation correctness
  • Compatibility rules
  • Phase transition semantics
  • Trust-critical runtime behavior

If a proc macro would make a user unable to understand the real runtime behavior by reading the code, it should not be part of Hopper's critical path.

Why this policy exists

Trust preservation

Users can always drop to explicit code and inspect what Hopper does. Every hopper_layout! expansion is a #[repr(C)] struct with known offsets and compile-time assertions. No hidden transforms.

Escape hatch preservation

Serious builders can use Hopper without buying into framework magic. The macro-free path (manual #[repr(C)] struct + Pod impl + FixedLayout impl) is always available and produces identical runtime behavior.

Product quality

Hopper can use proc macros to improve:

  • DX (less boilerplate for common patterns)
  • Schema generation (manifest/IDL export)
  • Documentation generation
  • Client tooling

without compromising its trust model.

Current macro inventory

All current Hopper macros are macro_rules! (declarative, not proc):

Macro Purpose
hopper_layout! Define #[repr(C)] layout with fingerprint
hopper_dispatch! Instruction dispatch table
hopper_error! Sequential error code definitions
hopper_init! Account initialization
hopper_close! Account closure with sentinel
hopper_check! Inline validation assertion
hopper_require! Conditional error return
hopper_register_discs! Compile-time discriminator uniqueness
hopper_verify_pda! PDA seed verification
hopper_invariant! Invariant assertion
hopper_manifest! Manifest constant generation
hopper_segment! Segment descriptor
hopper_validate! Validation bundle
hopper_virtual! Virtual state slots
hopper_assert_compatible! Compile-time compat check
hopper_assert_fingerprint! Compile-time fingerprint check
hopper_interface! Cross-program read-only view

No proc macros required. This is a design choice, not a limitation.

Doctrine

Hopper does not require proc macros for correctness or core functionality. Proc macros are optional ergonomic and schema-generation tools only. The runtime model, memory model, validation model, and compatibility guarantees remain explicit and usable without them.