Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Security Model

Pina’s safety posture is built around explicit validation and predictable state transitions.

Core invariants

  • Type correctness: account bytes must match expected discriminator and layout.
  • Authority correctness: signer/owner checks must precede mutation.
  • PDA correctness: seed and bump checks must gate PDA-bound operations.
  • Value correctness: arithmetic and balance mutations must be checked.

Version-safe binary layout and compatibility

The discriminator-first model makes byte layout part of protocol compatibility. Treat every #[account] struct as ABI:

  • Do not reorder fields.
  • Do not change existing discriminator values.
  • Do not alter field types in-place without migration.
  • If a struct grows, treat it as a new versioned shape and migrate state explicitly.

Discriminator and payload versioning

ChangeCompatibility impact
Add a new enum variantUsually backward-compatible if old clients ignore unknown variants
Change an existing variant valueBreaking for every historical byte slice
Reorder or remove struct fieldsBreaking (offsets change)
Append fields to a structMostly non-breaking, but consumers must accept the larger size
Switch primitive width (u8u16, etc.)Breaking for serialized payloads at that boundary

For on-chain accounts, treat layout as part of protocol ABI:

  • Keep field order stable.
  • Introduce optional version fields at the tail for in-place migration strategies.
  • Never change existing discriminator values in place.
  • When incompatible layout changes are required, perform explicit migration with a new account version and an operator upgrade flow.

For instruction payloads:

  • Prefer additive migration: add a new variant and keep legacy handlers for a release cycle.
  • Reject stale payload shapes with explicit errors rather than silently reinterpreting bytes.

High-priority guardrails

  • Prefer checked arithmetic (checked_add, checked_sub) for all user-facing or balance-affecting values.
  • Ensure all token account types used by helper traits implement AccountValidation.
  • Keep close/transfer helpers conservation-safe (no temporary double-crediting).

Best practices

  • Always call assert_signer() before trusting authority accounts
  • Always call assert_owner() / assert_owners() before as_token_*() methods
  • Always call assert_empty() before account initialization to prevent reinitialization attacks
  • Always verify program accounts with assert_address() / assert_program() before CPI invocations
  • Use assert_type::<T>() to prevent type cosplay — it checks discriminator, owner, and data size
  • Use close_with_recipient() with zeroed() to safely close accounts and prevent revival attacks
  • Prefer assert_seeds() / assert_canonical_bump() over assert_seeds_with_bump() to enforce canonical PDA bumps
  • Namespace PDA seeds with type-specific prefixes to prevent PDA sharing across account types

Testing strategy

  • Unit tests for negative validation cases.
  • Regression tests for every previously fixed bug class.
  • Integration tests for cross-account invariants where mutation order matters.