Skip to content

Panic asm exit, unsafe_no_disc accounts, PDA bump auto-detection#98

Merged
L0STE merged 4 commits intomasterfrom
feat/caravel-parity-optimizations
Mar 27, 2026
Merged

Panic asm exit, unsafe_no_disc accounts, PDA bump auto-detection#98
L0STE merged 4 commits intomasterfrom
feat/caravel-parity-optimizations

Conversation

@L0STE
Copy link
Copy Markdown
Contributor

@L0STE L0STE commented Mar 27, 2026

Three independent improvements to the derive macros and runtime, plus a GitHub issue for future work.

Changes

1. Panic infrastructure → inline asm exit

Before: Panic handler calls sol_log_("PANIC", 5) then enters loop {}, burning all remaining CU before the SVM kills the program. The no_alloc! allocator triggers the same path via panic!(""), pulling in format machinery.

After: New abort_program() function in quasar-lang emits two SBF instructions: lddw r0, 0x100000000; exit. Immediately terminates with ProgramError::Custom(0). No string in .rodata, no syscall, no loop. The off-chain path still panics with a descriptive message for test ergonomics.

Three call sites updated:

  • #[program]-emitted panic handler (derive/src/program.rs)
  • no_alloc! allocator (lang/src/entrypoint.rs)
  • standalone panic_handler! macro (lang/src/entrypoint.rs)

Required #![feature(asm_experimental_arch)] on the quasar-lang crate (SBF target only). The asm lives in quasar-lang so user crates don't need the feature gate.

Vault binary: 6,928 → 6,888 bytes (−40 bytes)

2. #[account(unsafe_no_disc)]

Opt-in attribute for accounts that don't need a discriminator — replicates the SPL Token AccountCheck pattern (size-only validation, no disc offset in Deref).

#[account(unsafe_no_disc)] generates:

  • Discriminator::DISCRIMINATOR = &[]
  • Space::SPACE = sizeof(ZcType) (no disc bytes added)
  • AccountCheck::check — size-only, no disc byte validation
  • Deref at data_ptr().add(0) — no offset

The unsafe_ prefix is the safety contract: the developer must ensure account type uniqueness via size. Rejected for dynamic fields (String/Vec/tail). Init and close paths work unchanged — copy_nonoverlapping with length 0 is a no-op, and close zeroes the first 8 bytes of actual data as defense-in-depth.

3. PDA bump auto-detection

When bump is bare (no = expr), we now auto-detect the bump from two sources before falling back to based_try_find_program_address (~544 CU):

Source 1 — Instruction args: If the Accounts struct has #[instruction(vault_bump: u8)] and a PDA field named vault with bare bump, the vault_bump arg auto-binds to verify_program_address (~200 CU). Also matches a bare bump: u8 arg when there's exactly one bare-bump PDA in the struct.

Source 2 — Stored bump in account data: If the inner type of Account<T> has a bump: u8 field, the #[account] macro sets Discriminator::BUMP_OFFSET = Some(disc_len + offset_of!(ZcType, bump)). At PDA check time, for non-init fields, the generated code reads the bump from the raw account data at that offset and uses verify_program_address. The if let Some(offset) = BUMP_OFFSET branch is constant-folded by LLVM via LTO — types without a stored bump compile down to the find path with zero overhead.

Priority: instruction arg > stored bump > find.

L0STE added 3 commits March 27, 2026 11:54
Eliminate log("PANIC") + loop {} in panic handler, no_alloc!, and
panic_handler! macro. The new abort_program() function emits two SBF
instructions (lddw r0, 0x100000000; exit) that immediately terminate
with ProgramError::Custom(0). Removes the "PANIC" string from .rodata
and the sol_log_ syscall path.

Vault binary: 6,928 → 6,888 bytes (-40 bytes)
Add opt-in `#[account(unsafe_no_disc)]` that generates size-only
AccountCheck (no discriminator validation), zero Deref offset, and
empty Discriminator::DISCRIMINATOR. Replicates the SPL Token pattern
for accounts that don't need disc bytes.

Mutually exclusive with discriminator=, rejected for dynamic fields.
The `unsafe_` prefix signals the developer's responsibility for
ensuring account type uniqueness via size.
For bare `bump` (no `= expr`), auto-detect the bump value from two
sources before falling back to based_try_find_program_address:

1. Instruction args: `{field}_bump: u8` or single-PDA `bump: u8`
   → uses verify_program_address (~200 CU vs ~544 CU)

2. Inner account's stored bump: if Account<T> where T has a `bump: u8`
   field, reads it via Discriminator::BUMP_OFFSET and verifies
   → compiler constant-folds the None branch away via LTO

Priority: instruction arg > stored bump > find (current fallback)

Saves ~344 CU per PDA when bump is available from either source.
@L0STE L0STE changed the title Close CU/binary gap with caravel while keeping Rust safety Panic asm exit, unsafe_no_disc accounts, PDA bump auto-detection Mar 27, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 27, 2026

⚡ CU Benchmark (Vault)

Instruction Base PR Delta
Deposit 1,577 1,577 0 ⚪
Withdraw 411 411 0 ⚪

Binary size: 6,888 bytes (-40 🟢 bytes)

@L0STE L0STE force-pushed the feat/caravel-parity-optimizations branch from 8cfc626 to 50586d8 Compare March 27, 2026 11:28
@L0STE L0STE merged commit a95e1be into master Mar 27, 2026
8 checks passed
@L0STE L0STE deleted the feat/caravel-parity-optimizations branch March 27, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant