Expand description
Self-CPI event emission: the wire format, the verification primitives, and the runtime half of the one-line macro surface.
Log output is lossy. Transaction metadata is not. A program that
needs events to arrive at indexers regardless of log truncation
invokes itself with a distinctive CPI whose bytes carry the event
payload. This serves the same persistence role as Anchor’s emit_cpi!.
§The one-liner
Programs normally use the macro-generated context surface:
#[hopper::context(event_cpi)] // ← one attribute option
pub struct Deposit {
#[account(mut)]
pub vault: Vault,
}
#[hopper::program]
mod vault_prog {
#[instruction(0)]
fn deposit(ctx: Context<Deposit>, amount: u64) -> ProgramResult {
// ... state changes ...
ctx.emit_event_cpi(&Deposited { amount: WireU64::new(amount) })?; // ← one call
Ok(())
}
}event_cpi appends two trailing accounts to the context (the
event-authority PDA and the program account itself, the same two
Anchor’s #[event_cpi] appends), validates them at bind, exposes
ctx.emit_event_cpi(&event) on the bound context, and the
#[hopper::program] dispatcher grows a self-CPI sink on the
reserved [0xE0, 0x1E] marker that authenticates each event before
accepting it. The macro emits the sink path only for contexts that opt in.
§Wire format
[0..2] CPI_EVENT_MARKER (0xE0, 0x1E)
[2] event tag (the byte from `#[hopper::event(tag = N)]`)
[3..] event payload (the event's Pod bytes)Three bytes of instruction-data overhead per event. Anchor’s
emit_cpi! spends sixteen: the 8-byte EVENT_IX_TAG_LE instruction
discriminator plus the event’s own 8-byte account-style
discriminator. Hopper’s 2-byte marker + 1-byte tag table carries the
same routing information for 13 fewer instruction-data bytes per
event (5 fewer counting only the event-identification layer: 3-byte
marker+tag vs one 8-byte hash discriminator).
§Why the sink verifies
The generated sink is not a bare no-op. Any program can CPI into
any other program, so an unauthenticated sink would let an attacker
program plant forged “events” in your program’s inner-instruction
list. The sink therefore requires the event-authority PDA, derived with
EVENT_AUTHORITY_SEED under this program’s id, to sign the
CPI. Only this program’s own invoke_signed can produce that
signature, which is exactly Anchor’s authenticity argument for its
event_authority account.
On-chain verification uses crate::pda::find_and_verify_pda. Anchor
v0.31+ pins the same PDA against a compile-time
constant; Hopper has no compile-time program id, so it derives at
runtime. Off-chain hosts have no sha256
syscall (see crate::pda), so host builds enforce the marker and
the signer flag and document the address pin as an on-chain check.
§Manual wiring (appendix)
The pre-event_cpi manual pattern remains supported for programs
that want custom control. Declare the sentinel yourself:
#[instruction(discriminator = [0xE0, 0x1E])]
fn __hopper_event_sink(ctx: &mut Context<'_>) -> ProgramResult {
// Recommended: authenticate instead of no-op'ing.
hopper_runtime::cpi_event::handle_event_sink(ctx, ctx.instruction_data())
}pass the event-authority PDA + program account in your context, and
emit through crate::hopper_emit_cpi! (or encode_event_cpi +
invoke_event_cpi for full control).
Constants§
- CPI_
EVENT_ MARKER - The reserved self-CPI event discriminator.
- EVENT_
AUTHORITY_ SEED - Canonical PDA seed for the Hopper event-authority. The
#[hopper::context(event_cpi)]machinery derives, verifies, and signs with this seed; manual programs must match it so the CPI signer resolves. - HOST_
EVENT_ AUTHORITY_ BUMP - The bump byte host builds report for the event authority.
- MAX_
EVENT_ PAYLOAD - Maximum event payload accepted by the emit helpers’ stack buffer.
Traits§
- CpiEvent
- A self-CPI-emittable event: a stable 1-byte tag plus a borrowed payload view.
Functions§
- decode_
event_ cpi - Decode the CPI wire format back into
(tag, payload). - encode_
event_ cpi - Fill an out buffer with the CPI wire format for an event.
- handle_
event_ sink - The event sink: validate an incoming self-CPI event instruction.
- invoke_
event_ cpi - Invoke a self-CPI carrying the encoded event payload.
- verify_
event_ authority - Verify an account is this program’s event-authority PDA and return its bump.