hopper-derive 0.2.0

Proc macro layer for Hopper. Generates account layouts, typed context accessors, instruction dispatchers, schema metadata, and CPI builders.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
//! Optional proc macro DX layer for Hopper.
//!
//! Provides both the canonical `#[hopper::state]`, `#[hopper::context]`,
//! `#[hopper::program]` surface and the legacy `#[hopper_state]`,
//! `#[hopper_context]`, `#[hopper_program]` aliases. All entry points generate
//! zero-cost code targeting Hopper's runtime primitives.
//!
//! **Not required.** Every feature these macros provide is achievable through
//! Hopper's declarative `macro_rules!` macros or hand-written code. These
//! exist purely for developer velocity. The generated code compiles to the
//! exact same pointer arithmetic as raw Pinocchio.
//!
//! # Design Philosophy
//!
//! - **Macros generate code, not behavior.** No hidden runtime logic.
//! - **Everything inlines.** No function calls that wouldn't exist in hand-written code.
//! - **No heap.** Generated code is `no_std`, `no_alloc`.
//! - **Optional.** Core Hopper never depends on this crate.

extern crate proc_macro;

mod args;
mod constant;
mod context;
mod crank;
mod declare_program;
mod dynamic;
mod dynamic_account;
mod error;
mod event;
mod init_space;
mod migrate;
mod pod;
mod program;
mod state;

use proc_macro::TokenStream;

/// Generate a `SegmentMap` implementation for a zero-copy layout struct.
///
/// Computes field offsets at compile time and emits a const segment table.
/// The generated code is zero-cost. Segment lookups resolve to const loads.
///
/// # Example
///
/// ```ignore
/// #[hopper_state]
/// #[repr(C)]
/// pub struct Vault {
///     pub authority: [u8; 32],  // TypedAddress<Authority>
///     pub balance: [u8; 8],     // WireU64
///     pub bump: u8,
/// }
///
/// // Generated:
/// // impl SegmentMap for Vault { ... }
/// // const VAULT_SEGMENTS: ... (for direct access)
/// ```
#[proc_macro_attribute]
pub fn hopper_state(attr: TokenStream, item: TokenStream) -> TokenStream {
    state::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

#[proc_macro_attribute]
pub fn state(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_state(attr, item)
}

/// Derive `const INIT_SPACE: usize = size_of::<Self>()` on a struct.
///
/// Anchor-parity derive: programs that have an Anchor-shaped
/// `#[account(init, payer = X, space = 8 + Foo::INIT_SPACE)]`
/// expression can port to Hopper without reshaping the size
/// computation. For types already declared with `#[hopper::state]`
/// the same constant is emitted automatically; this derive exists
/// for hand-authored `#[repr(C)]` Pod structs that want to
/// participate in the pattern without adopting the full state
/// attribute.
///
/// # Example
///
/// ```ignore
/// #[derive(HopperInitSpace)]
/// #[repr(C)]
/// pub struct Profile {
///     pub bump: u8,
///     pub authority: [u8; 32],
/// }
///
/// // Generated:
/// // impl Profile {
/// //     pub const INIT_SPACE: usize = core::mem::size_of::<Self>();
/// // }
/// ```
#[proc_macro_derive(HopperInitSpace)]
pub fn derive_hopper_init_space(input: TokenStream) -> TokenStream {
    init_space::expand(input.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Anchor / Quasar naming alias for [`state`].
///
/// Declare a zero-copy account layout with the familiar `#[account]`
/// spelling. Functionally identical to `#[hopper::state]`. The same
/// `#[account(mut(...))]` field-attribute syntax inside a
/// `#[hopper::context]` keeps working because field-level attrs are
/// consumed by the outer macro, not by this proc macro.
#[proc_macro_attribute]
pub fn account(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_state(attr, item)
}

/// Generate typed context accessors with segment-level borrow tracking.
///
/// Each field annotated with `#[account(mut(field1, field2))]` gets accessor
/// methods that:
/// 1. Look up the segment by const offset (no string matching)
/// 2. Register a segment borrow in the registry
/// 3. Return a typed reference via pointer cast
///
/// # Example
///
/// ```ignore
/// #[hopper_context]
/// pub struct Deposit {
///     #[account(signer, mut)]
///     pub depositor: AccountView,
///
///     #[account(mut(balance))]
///     pub vault: Vault,
/// }
///
/// // Generated:
/// // impl<'a> Deposit<'a> {
/// //     pub fn vault_balance_mut(&mut self) -> Result<RefMut<WireU64>, ProgramError> { ... }
/// // }
/// ```
#[proc_macro_attribute]
pub fn hopper_context(attr: TokenStream, item: TokenStream) -> TokenStream {
    context::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

#[proc_macro_attribute]
pub fn context(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_context(attr, item)
}

/// Anchor-style plural alias for [`context`].
///
/// Anchor writes `#[derive(Accounts)]` on the accounts struct. Hopper
/// uses attribute macros instead of derive macros, so this is the
/// closest naturally-spelled alias: `#[accounts]`. Functionally
/// identical to `#[hopper::context]`. Pick whichever spelling reads
/// best to the rest of your codebase.
#[proc_macro_attribute]
pub fn accounts(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_context(attr, item)
}

/// `#[derive(Accounts)]` - Anchor-spelled drop-in for `#[hopper::context]`.
///
/// Functionally identical to the attribute form: every constraint Hopper
/// recognises (`init`, `init_if_needed`, `mut`, `signer`, `seeds`, `bump`,
/// `payer`, `space`, `has_one`, `owner`, `address`, `constraint`,
/// `token::*`, `mint::*`, `associated_token::*`, the Token-2022 extension
/// gates, `dup`, `sweep`, `executable`, `rent_exempt`, `realloc`, `zero`,
/// `close`) all work in the derive form. Hopper-specific authoring sugar
/// - segment-tagged `mut(field, …)`, `read(field, …)`, the inline
/// `#[hopper::pipeline]` / `#[hopper::receipt]` / `#[hopper::invariant]`
/// stack - also works untouched.
///
/// The derive registers `account`, `signer`, `instruction`, and `validate`
/// as helper attributes so the existing `#[account(...)]`, `#[signer]`,
/// `#[instruction(...)]`, and `#[validate]` field/struct annotations
/// compile without an extra `use` line. Helper attributes are silently
/// consumed by `rustc` once the derive runs, just like Anchor's setup.
///
/// # When to use which spelling
///
/// - **`#[hopper::context]`** when you want Hopper-native vocabulary and
///   are starting from scratch.
/// - **`#[derive(Accounts)]`** when you're porting from Anchor or want a
///   spelling that matches the broader Solana-Rust convention. The
///   `Accounts` symbol comes from `hopper::prelude::*`.
///
/// # Example
///
/// ```ignore
/// use hopper::prelude::*;
///
/// #[derive(Accounts)]
/// #[instruction(amount: u64)]
/// pub struct Deposit {
///     #[account(mut)]
///     pub vault: Vault,
///
///     #[signer]
///     pub authority: AccountView,
///
///     pub system_program: AccountView,
/// }
/// ```
///
/// The generated code is identical to `#[hopper::context]` on the same
/// struct - same binder type, same accessors, same constraint validation
/// pipeline. No runtime difference between the two spellings.
#[proc_macro_derive(Accounts, attributes(account, signer, instruction, validate))]
pub fn derive_accounts(input: TokenStream) -> TokenStream {
    context::expand_for_derive(input.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Generate a dispatch table for a Hopper program module.
///
/// Maps instruction discriminator bytes to handler functions, generating
/// a clean entrypoint with minimal branching.
///
/// # Example
///
/// ```ignore
/// #[hopper_program]
/// mod vault {
///     pub fn deposit(ctx: &mut Context, amount: u64) -> ProgramResult { ... }
///     pub fn withdraw(ctx: &mut Context, amount: u64) -> ProgramResult { ... }
/// }
///
/// // Generated:
/// // pub fn __hopper_dispatch(program_id, accounts, data) -> ProgramResult { ... }
/// ```
#[proc_macro_attribute]
pub fn hopper_program(attr: TokenStream, item: TokenStream) -> TokenStream {
    program::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

#[proc_macro_attribute]
pub fn program(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_program(attr, item)
}

/// Derive the Hopper zero-copy marker contract for a user-defined struct.
///
/// Unlike `#[hopper::state]` (which emits the full Hopper layout: 16-byte
/// header, layout_id, schema export, typed load helpers), `#[hopper::pod]`
/// is the minimal opt-in: it asserts that the struct satisfies the
/// Pod + FixedLayout + alignment-1 + non-padded + non-zero-sized contract
/// at compile time, and emits the matching `unsafe impl Pod` and
/// `impl FixedLayout` so it can participate in every Hopper segment /
/// raw access API.
///
/// This is the Hopper Safety Audit's "derive macros for Pod and layout"
/// recommendation delivered standalone: use it on sub-structs, wire
/// helpers, or any `#[repr(C)]` overlay that isn't a full top-level
/// account layout.
///
/// # Example
///
/// ```ignore
/// #[hopper::pod]
/// #[repr(C)]
/// pub struct Cursor {
///     pub head: WireU64,
///     pub tail: WireU64,
///     pub capacity: WireU64,
/// }
///
/// // Now usable as:
/// let c: Ref<'_, Cursor> = account.segment_ref::<Cursor>(&mut borrows, 0, 24)?;
/// ```
#[proc_macro_attribute]
pub fn hopper_pod(attr: TokenStream, item: TokenStream) -> TokenStream {
    pod::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::pod]`. Functionally identical to `#[hopper_pod]`.
#[proc_macro_attribute]
pub fn pod(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_pod(attr, item)
}

/// Declare a schema-epoch migration edge.
///
/// Decorates a function of signature
/// `fn(&mut [u8]) -> Result<(), ProgramError>` that mutates an
/// account body in-place from schema epoch `from` to epoch `to`.
/// The macro emits the fn unchanged plus a paired
/// `<FN_NAME>_EDGE: hopper_runtime::MigrationEdge` constant so the
/// layout author can compose edges via `hopper::layout_migrations!`.
///
/// Closes Hopper Safety Audit innovation I4 ("Schema epoch with
/// in-place migration helpers"). Runtime chain application and
/// atomic-per-edge `schema_epoch` bump live in
/// `hopper_runtime::migrate`.
///
/// # Example
///
/// ```ignore
/// #[hopper::migrate(from = 1, to = 2)]
/// pub fn vault_v1_to_v2(body: &mut [u8]) -> ProgramResult {
///     // Reinterpret bytes to match the epoch-2 shape.
///     Ok(())
/// }
/// ```
#[proc_macro_attribute]
pub fn hopper_migrate(attr: TokenStream, item: TokenStream) -> TokenStream {
    migrate::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::migrate]`. Functionally identical to
/// `#[hopper_migrate]`.
#[proc_macro_attribute]
pub fn migrate(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_migrate(attr, item)
}

// -----------------------------------------------------------------------------
// Newly added derives (added alongside the existing surface, not replacing it):
//   - `#[hopper::event]`. segment-tagged events with a stable tag byte.
//   - `#[hopper::error]`. error codes linked to invariant IDs.
//   - `#[hopper::args]`. borrowing zero-copy instruction argument parser.
//   - `#[hopper::dynamic]`- field-level dynamic tail opt-in.
// -----------------------------------------------------------------------------

/// Derive a Hopper event: emits a stable 1-byte tag, optional segment source,
/// a `NAME` string, a `FIELD_COUNT` const, and an `as_bytes(&self)` view for
/// the framework's log-emission pathway.
///
/// # Example
/// ```ignore
/// #[hopper::event(tag = 7, segment = 1)]
/// #[repr(C)]
/// pub struct Deposited {
///     pub amount: [u8; 8],
///     pub depositor: [u8; 32],
/// }
/// ```
#[proc_macro_attribute]
pub fn hopper_event(attr: TokenStream, item: TokenStream) -> TokenStream {
    event::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::event]`.
#[proc_macro_attribute]
pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_event(attr, item)
}

/// Mark an instruction handler as an autonomous crank.
///
/// Attaches a `"Crank"` capability tag to the instruction descriptor
/// in the program manifest and optionally captures
/// `seeds(account_name = [...])` hints so a keeper-bot CLI can
/// resolve every PDA without per-program config.
///
/// Cranks must be zero-arg handlers. Any value argument is a
/// compile-time error, because the crank runner cannot invent
/// instruction data on behalf of the caller.
#[proc_macro_attribute]
pub fn hopper_crank(attr: TokenStream, item: TokenStream) -> TokenStream {
    crank::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::crank]`.
#[proc_macro_attribute]
pub fn crank(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_crank(attr, item)
}

/// Generate a typed CPI surface from an on-disk Hopper manifest.
///
/// ```ignore
/// hopper::declare_program!(amm, "idl/amm.json");
/// ```
///
/// Emits a module with `PROGRAM_NAME`, `PROGRAM_ID_STR`, a
/// `FINGERPRINT: [u8; 32]` compile-time manifest-hash const, and one
/// builder per instruction. See the declare_program module for the
/// full contract.
#[proc_macro]
pub fn declare_program(input: TokenStream) -> TokenStream {
    declare_program::expand(input.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Derive a Hopper error-code enum. Emits `code()`, `variant_name()`,
/// `From<T> for u32`, and two const tables (`CODE_TABLE`, `INVARIANT_TABLE`)
/// that the schema crate surfaces in the manifest.
///
/// Per-variant `#[invariant = "name"]` attributes are the innovation: when
/// a runtime invariant check fails, the corresponding error carries the
/// invariant name, and the off-chain SDK can render "Invariant `x` failed"
/// instead of an opaque hex code.
///
/// # Example
/// ```ignore
/// #[hopper::error]
/// #[repr(u32)]
/// pub enum VaultError {
///     #[invariant = "balance_nonzero"]
///     InsufficientBalance = 0x1001,
///     MigrationRequired,   // auto-assigned stable code
/// }
/// ```
#[proc_macro_attribute]
pub fn hopper_error(attr: TokenStream, item: TokenStream) -> TokenStream {
    error::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::error]`.
#[proc_macro_attribute]
pub fn error(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_error(attr, item)
}

/// Derive a zero-copy borrowing parser for an instruction argument struct.
///
/// Emits `parse(&[u8]) -> Result<&Self, ArgParseError>`, `PACKED_SIZE`,
/// `ARG_DESCRIPTORS`, and `CU_HINT`. The `cu` attribute lets a program
/// declare a compute-unit budget clients can inspect via the manifest before
/// submission.
///
/// # Example
/// ```ignore
/// #[hopper::args(cu = 1200)]
/// #[repr(C)]
/// pub struct DepositArgs {
///     pub amount: [u8; 8],
///     pub memo:   [u8; 16],
/// }
/// ```
#[proc_macro_attribute]
pub fn hopper_args(attr: TokenStream, item: TokenStream) -> TokenStream {
    args::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::args]`.
#[proc_macro_attribute]
pub fn args(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_args(attr, item)
}

/// Declare which field of a `#[repr(C)]` struct is the dynamic-tail region.
///
/// Attaches to the **struct**, not the field, because stable Rust does not
/// permit `#[proc_macro_attribute]` macros on struct fields. The field name
/// is passed as a string via `field = "<name>"`.
///
/// # Example
///
/// ```ignore
/// #[hopper::dynamic(field = "entries")]
/// #[derive(Clone, Copy)]
/// #[hopper::state]
/// #[repr(C)]
/// pub struct Ledger {
///     pub head: WireU64,
///     pub tail: WireU64,
///     pub entries: DynamicRegion<LedgerEntry>,
/// }
/// ```
#[proc_macro_attribute]
pub fn hopper_dynamic(attr: TokenStream, item: TokenStream) -> TokenStream {
    dynamic::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::dynamic(field = "…")]`.
#[proc_macro_attribute]
pub fn dynamic(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_dynamic(attr, item)
}

/// Quasar-style bounded dynamic fields lowered into Hopper's fixed-body +
/// compact-tail account layout.
///
/// Fields annotated with `#[tail(string<N>)]` or `#[tail(vec<T, N>)]`
/// are removed from the fixed body, encoded into a generated `NameTail`, and
/// exposed through generated view / owned editor helpers. `Address` / `Pubkey`
/// vectors keep borrowed-slice views; other `T: TailElement` vectors return
/// `HopperVec<T, N>` values. The account still uses Hopper's canonical
/// `[body][u32 tail_len][tail_payload]` wire format.
///
/// # Example
///
/// ```ignore
/// #[hopper::dynamic_account(disc = 7, version = 1)]
/// pub struct Multisig {
///     pub creator: Address,
///     pub threshold: u8,
///
///     #[tail(string<32>)]
///     pub label: String,
///
///     #[tail(vec<Address, 10>)]
///     pub signers: Vec<Address>,
///
///     #[tail(vec<u16, 10>)]
///     pub weights: Vec<u16>,
/// }
/// ```
#[proc_macro_attribute]
pub fn hopper_dynamic_account(attr: TokenStream, item: TokenStream) -> TokenStream {
    dynamic_account::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::dynamic_account]`.
#[proc_macro_attribute]
pub fn dynamic_account(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_dynamic_account(attr, item)
}

/// Mark a `pub const` so it is surfaced in the Anchor IDL `"constants"`
/// array.
///
/// Anchor-compatible surface for `#[constant]`. The original constant is
/// preserved unchanged; alongside it a hidden
/// `__HOPPER_CONST_<NAME>: hopper_schema::ConstantDescriptor` sibling
/// is emitted, capturing the stringified type and initializer
/// expression. Collect the descriptors into a slice and hand it to
/// `hopper_schema::AnchorIdlWithConstants` (or
/// `AnchorIdlFromManifestWithConstants`) when emitting the IDL.
///
/// # Example
///
/// ```ignore
/// #[hopper::constant]
/// pub const MAX_DEPOSIT: u64 = 1_000_000;
///
/// // A hidden sibling const is emitted:
/// // pub const __HOPPER_CONST_MAX_DEPOSIT: ConstantDescriptor = ...;
///
/// pub const PROGRAM_CONSTANTS: &[ConstantDescriptor] = &[__HOPPER_CONST_MAX_DEPOSIT];
/// ```
#[proc_macro_attribute]
pub fn hopper_constant(attr: TokenStream, item: TokenStream) -> TokenStream {
    constant::expand(attr.into(), item.into())
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

/// Short alias: `#[hopper::constant]`.
#[proc_macro_attribute]
pub fn constant(attr: TokenStream, item: TokenStream) -> TokenStream {
    hopper_constant(attr, item)
}