# safe-solana-builder

Use this skill whenever the user wants to write, scaffold, or build a Solana smart contract or program from scratch. Triggers on: "write a Solana program", "create a smart contract", "build an anchor program", "write a native Rust Solana program", "scaffold a Solana program", "help me write a program that does X on Solana", or any request to produce production-grade on-chain Solana code. This skill enforces Frank Castle's security best practices and pitfall avoidance guidelines automatically — giving every program a first layer of protection before it ever reaches an auditor. Always use this skill — even for simple programs — whenever Solana program code is the primary deliverable.

- **Kind:** skill
- **Source:** https://github.com/Frankcastleauditor/safe-solana-builder
- **Page:** https://forefy.com/skills/e55792b4-4efe-48dd-a88d-cb06c8944ead
- **API (JSON + files):** https://forefy.com/api/skills/e55792b4-4efe-48dd-a88d-cb06c8944ead

---

## README.md

# 🛡️ Safe Solana Builder

**The first Claude skill for writing production-grade, security-first Solana programs.**

Built by a Solana security researcher, for Solana developers — so your code arrives at audit already hardened.

---

## What Is This?

**Safe Solana Builder** is a security skill system you can load into AI coding workflows (Claude / Cursor) so Frank Castle's Solana security knowledge is applied before writing a single line of code.

This is not a prompt. It is a layered reference architecture that forces Claude to:

- Select the right framework (Anchor, Native Rust, or Pinocchio) and load the matching security ruleset
- Choose a testing approach (LiteSVM or framework default) and load the matching test patterns
- Assess the program's risk level (🟢 Low / 🟡 Medium / 🔴 Critical) before touching the keyboard
- Apply a curated set of security rules drawn from real audit findings — CPIs, PDAs, account validation, arithmetic, Token-2022, and more
- Deliver a full project scaffold — not just `lib.rs`
- Generate a test file skeleton with security edge cases pre-identified
- Output a security checklist documenting every rule applied and every known limitation

Every program this skill produces has a first layer of protection baked in before it reaches an auditor.

---

## Why It Exists

Most AI-generated Solana code is a liability.

Missing ownership checks. Non-canonical bumps. Stale data used after CPIs. No duplicate account guards. No checked arithmetic. It compiles, it looks right, and it fails on mainnet.

The Cyfrin team built a skill like this for Solidity. Nobody built one for Solana — until now.

---

## What It Produces

For every program request, the skill outputs:

| Output | Description |
|---|---|
| **Full project scaffold** | `Anchor.toml`, `Cargo.toml`, proper folder structure — ready to `anchor build` or `cargo build-sbf` |
| **`lib.rs`** | Complete, compilable program with inline security comments |
| **Test file** | LiteSVM or framework-default — happy path tests implemented + security edge case tests scaffolded with `TODO` bodies |
| **`security-checklist.md`** | Every rule applied, every assumption made, every known limitation flagged |

---

## Skill Structure

```
safe-solana-builder/
├── SKILL.md                        ← Orchestrator: workflow, risk assessment, output format
├── references/
│   ├── shared-base.md              ← Framework-agnostic rules (PDAs, CPIs, arithmetic, Token-2022...)
│   ├── anchor.md                   ← Anchor-specific: constraints, account types, reload(), close...
│   ├── native-rust.md              ← Native Rust: manual validation sequence, invoke, deserialization...
│   ├── pinocchio.md                ← Pinocchio: zero-copy patterns, bytemuck, wincode, CPI, Shank IDL...
│   └── litesvm.md                  ← LiteSVM: test setup, sysvar control, CU profiling, account injection...
└── examples/
    └── nft-whitelist-mint/
        ├── lib.rs                  ← Full Anchor NFT whitelist mint program
        └── security-checklist.md  ← 31-rule checklist for the example
```

### Reference Coverage

The reference files cover:

**Shared Base (framework-agnostic)**
- Account & identity validation (signer, owner, discriminator, reinitialization)
- PDA security (canonical bumps, sharing prevention, seed collision)
- Arithmetic safety (checked math, multiply-before-divide, slippage)
- Duplicate mutable account attacks
- Full CPI safety surface (arbitrary CPI, stale reload, signer pass-through, SOL drain, post-CPI ownership)
- Account lifecycle (rent, closing, anti-revival, sysvar verification)
- Token-2022 compatibility
- Transaction model safety
- Safe Rust patterns

**Anchor-specific**
- Account type selection (`Account<T>` vs `UncheckedAccount` vs `Interface`)
- Constraint patterns (`has_one`, `seeds+bump`, `init` vs `init_if_needed`, `close`, `realloc`)
- `reload()` after CPI — non-negotiable
- `token_interface::transfer_checked` for Token-2022 compatibility
- CPI construction, signer seeds, program ID validation
- `#[error_code]` custom errors

**Native Rust-specific**
- The 6-step mandatory validation sequence (key → owner → signer → writable → discriminator → data)
- Borsh deserialization patterns and length pre-checks
- PDA derivation: `find_program_address` at init, `create_program_address` on reuse
- `invoke` vs `invoke_signed` patterns
- Manual post-CPI data refresh
- Account creation via System Program CPI
- Manual 3-step safe account close
- Custom error enum with `ProgramError` conversion

**Pinocchio-specific** *(new)*
- Zero-copy account definitions with `bytemuck` (`Pod` + `Zeroable`, explicit `_padding`, 8-byte alignment)
- Account validation via `TryFrom` pattern and validation macros
- `wincode` for instruction data serialization — `SchemaWrite`/`SchemaRead` derives, zero-copy deserialization for `#[repr(C)]` structs, `Pod<T>` foreign type adapter, compact-u16 / ShortVec length encoding
- `bytemuck` vs `wincode` decision rule: bytemuck for on-chain account state, wincode for instruction data
- CPI via `pinocchio-system` and `pinocchio-token` typed helpers
- IDL generation with Shank + Codama
- Entrypoint selection by CU cost (`no_allocator!`, `lazy_entrypoint!`, `entrypoint!`)
- Pinocchio-specific build errors and toolchain notes

**LiteSVM testing** *(new)*
- In-process VM — no validator, no async runtime, fastest test loop available
- `setup()` pattern, `send_tx()` helper with `expire_blockhash()`, `TransactionMetadata` fields
- Devnet account injection via `svm.set_account()` + RPC client
- Token setup with `litesvm-token` (`CreateMint`, `CreateAssociatedTokenAccount`, `MintTo`)
- Full sysvar control: time travel (`Clock`), slot warping, rent reads
- CU profiling with `CU_RESULTS` static and `zz_cu_summary` test
- Simulation (dry-run without state commit)
- Framework-specific patterns: Anchor (`InstructionData`/`ToAccountMetas`) and Native/Pinocchio (manual discriminator encoding)
- 12-item LiteSVM security test checklist
- Common errors table (GLIBC, `BlockhashNotFound`, missing SO, etc.)

---

## How to Install

### Claude Setup

1. Download `safe-solana-builder.skill` from the [Releases](../../releases) page
2. In Claude.ai, go to **Settings → Skills**
3. Upload the `.skill` file
4. The skill activates automatically whenever you ask Claude to write a Solana program

### Cursor IDE Support

Cursor does not natively support `.skill` files, but the same system works by loading this repository as a **context rule** source.

1. Clone the repository into Cursor's skills directory:

```bash
mkdir -p ~/.cursor/skills
git clone https://github.com/Frankcastleauditor/safe-solana-builder.git ~/.cursor/skills/safe_solana_builder
```

2. Restart Cursor
3. Reference the skill in your prompt:

```txt
Use the safe_solana_builder skill.

Build a secure Solana program using Anchor.
```

### Trigger Phrases

The skill fires on any of the following:
- *"Write a Solana program that..."*
- *"Build an Anchor program for..."*
- *"Create a native Rust Solana contract..."*
- *"Scaffold a Solana program..."*
- *"Help me write a program that does X on Solana"*

---

## Roadmap

This skill is under active development. Planned expansions:

- [ ] Native Rust example program (staking vault)
- [ ] Additional reference sources: SPL Token-2022 extension security, Metaplex deep-dive, oracle manipulation patterns
- [ ] Anchor v0.31+ specific patterns
- [ ] Invariant testing guidance (Trident, Fuzz)
- [ ] Common DeFi pattern references: AMM, lending, bonding curves

The reference files are the living core of this skill. Every new vulnerability source, audit finding, or best practice I encounter gets distilled and added. The skill grows with the threat landscape.

---

## About the Author

## Hi there 👋 I'm Frank Castle

🛡️ **Smart Contract Security Researcher** specializing in **Solana (Anchor)** and **Rust-based ecosystems**.

I help protocols ship safer smart contracts by identifying **critical vulnerabilities**, validating everything related to DeFi and blockchain, and for Solana reviewing **CPI / PDA / token-account security boundaries / and any custom logic**.

---

### 🔍 Focus Areas

- **Solana Program Security**: Anchor, PDAs, CPI, account validation, rent/DoS patterns
- **SPL / Token-2022 Security**: extensions, mint assumptions, transfer hooks, authority models
- **DeFi Security**: AMMs, vaults, staking, bonding curves, fee mechanisms
- **Rust Security**: state machines, invariants, edge cases, unsafe patterns

---

### 🏆 Highlights

- 70+ Rust audits, 50+ Solana audits
- 250+ Critical/High severity vulnerabilities identified
- Top placements in competitive audits:
  - 🥈 **2nd place** — HydraDX Omnipool (Code4rena)
  - 🏅 **4th place** — Centrifuge (Cantina)

---

### 📌 Featured Repositories

- 🔒 **Public Audits**: [public-audits](https://github.com/Frankcastleauditor/public-audits)
- 🧪 **Solana CTF / Practice**: [Solana_CTF](https://github.com/Frankcastleauditor/Solana_CTF)

---

### 🧾 Writeups & Content

- X (Twitter): [@0xcastle_chain](https://x.com/0xcastle_chain)
- Medium: [FrankCastleAudits](https://medium.com/@FrankCastleAudits)

---

### 📫 Contact

- Twitter: [@castle_chain](https://x.com/0xcastle_chain)
- Discord: [@castle_chain](https://discordapp.com/users/1119172287330004992)
- Telegram: [castle_chain](https://t.me/castle_chain)
- Email: castlechain99@gmail.com

---

⭐ If you're building on Solana and want a security review, feel free to reach out.

---

## License

MIT — use it, fork it, build on it. If you add something valuable, consider contributing it back.

---

*Safe Solana Builder — first layer of protection, before the auditor ever sees your code.*

## SKILL.md

---
name: safe-solana-builder
description: >
  Use this skill whenever the user wants to write, scaffold, or build a Solana smart contract
  or program from scratch. Triggers on: "write a Solana program", "create a smart contract",
  "build an anchor program", "write a native Rust Solana program", "scaffold a Solana program",
  "help me write a program that does X on Solana", or any request to produce production-grade
  on-chain Solana code. This skill enforces Frank Castle's security best practices and pitfall
  avoidance guidelines automatically — giving every program a first layer of protection before
  it ever reaches an auditor. Always use this skill — even for simple programs — whenever
  Solana program code is the primary deliverable.
---

# Safe Solana Builder — by Frank Castle

You are writing production-grade Solana programs. Security is not an afterthought — it is baked into every line. Every program produced by this skill ships with a full project scaffold, a test file skeleton, and a security checklist.

### What This Skill Enforces

This skill systematically addresses the following vulnerability classes derived from real Solana protocol audits:

- **Protocol-specific vulnerabilities** — oracle manipulation, fee bypass, slippage attacks, LP preprocessing gaps
- **Logic flaws & edge cases** — dust DoS, time-unit mismatches, pre/post-fee inconsistencies, type narrowing
- **Access control & authorization bugs** — missing signer checks, frontrunnable initialization, inbound transfer auth, post-expiry flows
- **State management errors** — coupled-field resets, counter drift, vested/unvested balance separation, rollback safety
- **PDA-related issues** — zombie accounts, seed collisions, canonical bump enforcement, lifecycle closure
- **Reward accounting exploits** — rounding gaps in partial unstake, dual-path reward debt bypass, retroactive rate application, dead share price, inflation/first-depositor attack, fee-on-transfer delta errors, rewards sourced from principal
- **Vault & pool architecture** — missing withdrawal paths on PDA-controlled vaults
- **Token-2022 extension validation** — PermanentDelegate seizure, uncontrolled FreezeAuthority, TransferHook CPI forwarding, ConfidentialTransfer compatibility
- **Admin key security** — two-step rotation pattern, timelock recommendations for Critical programs
- **BPF runtime limits** — 4096-byte stack frame DoS, Box<> mitigation for large account types

---

## Step 1 — Ask the Framework Question

If the user has not already specified, ask exactly this (and nothing else):

> "Should I write this in **Native Rust**, **Anchor**, or **Pinocchio**?"

**Pinocchio** is Anza's zero-dependency, zero-copy framework — 88–95% CU reduction vs. Anchor. Best for high-throughput programs (DEXs, orderbooks, vaults). It is unaudited — flag this in the checklist for Critical programs.

Wait for the answer before proceeding.

---

## Step 1b — Ask the Testing Question

Immediately after the framework is chosen, ask:

> "Should I use **LiteSVM** for testing (fast, in-process, no validator required), or the default testing approach for your framework?"

Present the options clearly:

| Option | Best For |
|---|---|
| **LiteSVM** | Fast unit/integration tests, CI pipelines, time-lock testing, CU profiling, account injection from devnet |
| **Framework default** | Anchor: TypeScript with `@coral-xyz/anchor`; Native: `solana-program-test` async harness |

Wait for the answer before proceeding to Step 2.

---

## Step 2 — Load Your Reference Files

Once **both** the framework and testing approach are chosen, read the following files
**before writing a single line of code**:

1. **Always read first (both files):**
   - `references/shared-base.md` — Core security rules, pitfall patterns, and best practices for ALL Solana programs. Sections 1–20 cover foundational security; sections 21–25 cover vulnerability-derived rules from real protocol audits (reward accounting, vault architecture, Token-2022 extension validation, admin key rotation, BPF stack frame limits).

2. **Then read the framework-specific file:**
   - Native Rust → `references/native-rust.md`
   - Anchor → `references/anchor.md`
   - Pinocchio → `references/pinocchio.md`
   → Framework-specific patterns, constraints, additional pitfalls, and common build/tooling errors.

3. **If LiteSVM was chosen for testing, also read:**
   - `references/litesvm.md`
   → Test structure patterns, sysvar control, token setup, account inspection, CU profiling, and the LiteSVM security test checklist.

4. **Check for a relevant example:**
   See the Examples table at the bottom of this file. If a similar program exists in `examples/`, read it before writing — use it as a quality and structure benchmark.

Do not skip or skim these files. They are the source of truth for this skill.

---

## Step 3 — Assess Risk Level

Before gathering requirements, classify the program's sensitivity. This determines how thorough your security comments and "Known Limitations" section must be.

| Level | Criteria | Examples |
|---|---|---|
| 🟢 Low | No SOL/token custody, no CPI, single user, read-heavy | Counter, registry, simple config |
| 🟡 Medium | Token transfers, basic CPI, multi-user state, PDAs | Staking, voting, simple escrow |
| 🔴 Critical | Vaults, multi-CPI chains, admin keys, large TVL potential | AMM, lending, NFT launchpad, bridges |

State the risk level explicitly at the top of your security checklist. For 🔴 Critical programs: add a "High-Risk Decisions" section to the checklist and flag every admin key, upgrade authority, and irreversible state transition.

---

## Step 4 — Gather Program Requirements

Collect the following in one message (if not already provided):

- **Program name** — what is it called?
- **What it does** — brief description of functionality
- **Accounts** — what accounts does it need?
- **Instructions** — what instructions/functions?
- **Access control** — who can call what? Any admin roles?
- **Token standard** — SPL Token, Token-2022, or none?
- **Any external programs called** — Metaplex, another protocol, etc.?

If the user's description already covers most of these, proceed and note your assumptions clearly.

---

## Step 5 — Write the Program

### 5a. Security Pre-Check (internal, not shown to user)
Before writing, run through shared-base.md and the framework file. Flag which rules apply to this program's design. Note any inherent risks in the design itself.

### 5b. Project Scaffold

Deliver a complete, ready-to-build project structure. Not just `lib.rs` — the full scaffold:

**For Anchor:**
```
<program-name>/
├── Anchor.toml
├── Cargo.toml
├── programs/
│   └── <program-name>/
│       ├── Cargo.toml
│       └── src/
│           └── lib.rs
└── tests/
    └── <program-name>.ts           # if framework-default testing
    └── <program-name>_tests.rs     # if LiteSVM testing
```

**For Native Rust / Pinocchio:**
```
<program-name>/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── instruction.rs
    ├── processor.rs
    ├── state.rs
    └── error.rs
tests/
    └── <program-name>_tests.rs     # if LiteSVM testing
```

### 5c. The Program Code

Requirements:
- Compilable without warnings
- Every account validated — ownership, type, signer, writable as applicable
- No unchecked math on any financial value
- PDAs derived with canonical bumps stored and reused
- No logic after CPI calls that relies on stale state
- Descriptive program-specific error types
- Inline security comments on every non-obvious decision

Header comment block at the top of `lib.rs`:
```rust
// ============================================================
// Program: <ProgramName>
// Framework: <Native Rust | Anchor | Pinocchio>
// Testing:   <LiteSVM | solana-program-test | TypeScript/Anchor>
// Risk Level: 🟢 Low | 🟡 Medium | 🔴 Critical
// Author: Frank Castle Security Template
// Security: See accompanying security-checklist.md
// ============================================================
```

### 5d. Test File

Always produce a test file. The approach depends on what was chosen in Step 1b:

#### If LiteSVM was chosen:

Produce Rust tests following `references/litesvm.md`. The test file must include:

**Required structure:**
- A `setup()` function that loads the `.so`, airdrops SOL, and returns `(LiteSVM, Keypair)`
- A `send_tx()` helper that wraps message/transaction building and calls `expire_blockhash()` after each send
- PDA derivation helpers matching the on-chain seeds exactly

**Happy path tests (implement fully):**
- End-to-end success flow with full state assertion (lamports, token balances, account data fields)
- Account closure verification (lamports=0, data.len()=0, owner=system_program)
- CU consumption logged and recorded to a `CU_RESULTS` static for the `zz_cu_summary` test

**Security/edge case tests (implement or scaffold with `TODO` + explanation comment):**
- Wrong signer → `assert!(result.is_err())`
- Re-initialization attempt → `assert!(result.is_err())`
- Before-deadline action → `assert!(result.is_err())` (if time-locked)
- After-deadline action → succeeds (time travel via `svm.set_sysvar(&clock)`)
- Over-limit / zero-amount arithmetic → `assert!(result.is_err())`
- Any program-specific edge cases flagged in the checklist

**Mandatory closing test:**
```rust
#[test]
fn zz_cu_summary() { /* print CU table */ }
```

#### If framework default was chosen:

- Anchor: TypeScript using `@coral-xyz/anchor`
- Native Rust: Rust integration tests using `solana-program-test`

In both cases, cover the same happy path + security/edge case matrix as above.
Mark unimplemented security tests with `TODO` and an explanation comment.

## Examples

The `examples/` directory contains complete reference programs written to this skill's standard. Before writing, check if a similar example exists — use it to calibrate output quality, structure, and checklist depth. Do not copy-paste; treat it as a quality benchmark.

| Example | Framework | Testing | Risk Level | What it demonstrates |
|---|---|---|---|---|
| `examples/nft-whitelist-mint/` | Anchor | TypeScript/Anchor | 🔴 Critical | MintConfig PDA, per-user WhitelistEntry PDA, double-mint guard, Metaplex CPI with program ID verification, SOL balance check around CPI, Token-2022 compatible mint, safe account close |

Each example folder contains:
- `lib.rs` — the full program
- `security-checklist.md` — the applied rules checklist

---

## Notes for Edge Cases

- **Simple programs (counter, hello world):** Still apply all checks. Simplicity is not an excuse for insecure patterns.
- **Inherent design risks (admin key with no timelock, no upgrade authority check):** Flag explicitly in the checklist under "High-Risk Decisions" or "Known Limitations."
- **Token-2022 features (transfer hooks, confidential transfers):** Flag in the checklist as requiring extra manual review — expanded attack surface. Always validate extensions at `initialize` per shared-base §23.
- **Programs with `remaining_accounts`:** Apply the same ownership, signer, and type checks as named accounts. Flag in checklist.
- **Upgrade authority:** Always note whether the program is upgradeable and who holds the authority. Recommend a timelock or multisig for 🔴 Critical programs.
- **Staking / yield programs:** Pay special attention to shared-base §21 (reward accounting). Every reward payout path must update `reward_debt`. Retroactive rate application and partial-unstake rounding are the two most common Critical findings in this category.
- **Share-based pools (stX/totalStaked):** Apply §21.4 (dead share price) and §21.5 (inflation attack) checks at design time — these are architectural, not line-level, and cannot be patched easily after deployment.
- **Large account contexts:** After `anchor build`, check for stack frame warnings (§25). Apply `Box<>` to large account fields if the warning appears.

- **LiteSVM for RPC-dependent tests:** LiteSVM does not support all RPC methods. If the program requires wallet integration tests or real validator behaviour, note in the checklist that those tests must use `solana-test-validator` separately.

## examples

```

```

## examples/nft-whitelist-mint

```

```

## examples/nft-whitelist-mint/lib.rs

```rust

```

## examples/nft-whitelist-mint/security-checklist.md

# Security Checklist — NFT Whitelist Mint

## Framework
Anchor

---

## Rules Applied

| # | Category | Rule | Status | Notes |
|---|----------|------|--------|-------|
| 1 | Account Validation | Signer check on all authority operations | ✅ Applied | `Signer<'info>` used for `authority` in admin instructions, `payer` in mint |
| 2 | Account Validation | Ownership check on all typed accounts | ✅ Applied | `Account<'info, T>` on `MintConfig` and `WhitelistEntry` — Anchor auto-verifies owner + discriminator |
| 3 | Account Validation | Cross-account relationship enforcement | ✅ Applied | `has_one = authority` on `AddToWhitelist`, `RemoveFromWhitelist`; `constraint = whitelist_entry.user == payer.key()` on `MintNft` |
| 4 | Account Validation | Type cosplay prevention | ✅ Applied | Anchor discriminators on all state accounts via `#[account]` derive |
| 5 | Account Validation | Reinitialization prevention | ✅ Applied | `init` constraint on `MintConfig` and `WhitelistEntry` — fails if already exists |
| 6 | Account Validation | Writable flag enforcement | ✅ Applied | `mut` only on accounts that are modified; read-only accounts have no `mut` |
| 7 | PDA Security | Canonical bump only | ✅ Applied | Bumps stored in `MintConfig.bump` and `WhitelistEntry.bump` at init time; reused in all subsequent `seeds + bump` constraints |
| 8 | PDA Security | PDA sharing prevention | ✅ Applied | `WhitelistEntry` seeds include `user.key()` — each user has their own isolated PDA |
| 9 | PDA Security | Seed collision prevention | ✅ Applied | `b"mint_config"` and `b"whitelist"` are distinct prefixes for distinct PDA types |
| 10 | PDA Security | PDA purpose isolation | ✅ Applied | MintConfig and WhitelistEntry are separate PDAs with separate seeds and purposes |
| 11 | Arithmetic | Checked arithmetic on all financial values | ✅ Applied | `checked_add` on `current_supply`; `checked_sub` and `checked_add` on balance verification |
| 12 | Arithmetic | SOL balance check around CPI | ✅ Applied | `payer_balance_before` recorded pre-CPI; verified post-CPI that drain ≤ price + small buffer |
| 13 | Duplicate Accounts | Distinct mutable accounts enforced | ✅ Applied | `constraint = payer.key() != nft_mint.key()` on MintNft |
| 14 | CPI Safety | Program ID validation | ✅ Applied | `Program<'info, System>`, `Interface<'info, TokenInterface>`, `Program<'info, AssociatedToken>` auto-validate; `require_keys_eq!` on `token_metadata_program` against hardcoded `TOKEN_METADATA_PROGRAM_ID` constant |
| 15 | CPI Safety | No arbitrary CPI | ✅ Applied | All CPI targets are either `Program<T>` typed accounts or verified via `require_keys_eq!` before use |
| 16 | CPI Safety | Post-CPI state reload | ✅ Applied | `ctx.accounts.mint_config.reload()?` called after Metaplex CPIs before mutating supply counter |
| 17 | CPI Safety | Error propagation | ✅ Applied | All CPI calls use `?` operator — any inner failure reverts the full transaction |
| 18 | CPI Safety | invoke vs invoke_signed | ✅ Applied | `invoke_signed` only where MintConfig PDA must authorize (mint_to, create_metadata, create_edition); system transfer uses `invoke` via Anchor CpiContext |
| 19 | CPI Safety | Signer seeds use stored canonical bump | ✅ Applied | `&[config.bump]` used in signer seeds — never re-derived with `find_program_address` |
| 20 | Account Lifecycle | Rent exemption | ✅ Applied | All `init` accounts are payer-funded to rent-exempt threshold automatically by Anchor |
| 21 | Account Lifecycle | Safe account closing | ✅ Applied | `close = authority` constraint on `whitelist_entry` in `RemoveFromWhitelist` — zeroes data, transfers lamports, reassigns to system program |
| 22 | Account Lifecycle | Anti-revival close | ✅ Applied | Anchor's `close` constraint performs the full 3-step safe close; lamports return to trusted `authority`, not user-supplied address |
| 23 | Token Operations | Token-2022 compatibility | ✅ Applied | `Interface<'info, TokenInterface>`, `InterfaceAccount<'info, Mint>`, `InterfaceAccount<'info, TokenAccount>`, `token_interface::mint_to` used throughout |
| 24 | Business Logic | Double-mint prevention | ✅ Applied | `has_minted: bool` flag in `WhitelistEntry`; checked at instruction entry with `require!(!entry.has_minted, ...)`; set to `true` after successful mint |
| 25 | Business Logic | Supply cap enforcement | ✅ Applied | `require!(config.current_supply < config.max_supply, MintError::MaxSupplyReached)` checked before any state change |
| 26 | Business Logic | Input validation | ✅ Applied | `price > 0`, `max_supply > 0`, non-empty + length-bounded metadata fields all validated |
| 27 | Business Logic | State mutation after all CPIs | ✅ Applied | `current_supply` increment and `has_minted = true` set only after all CPIs succeed |
| 28 | Error Handling | Descriptive custom error codes | ✅ Applied | 13 distinct `#[error_code]` variants with clear messages |
| 29 | Error Handling | `require!` macros | ✅ Applied | All validation checks use `require!`, `require_keys_eq!`, `require_eq!` — no raw `if/return Err` |
| 30 | Sysvar | Sysvar account verification | ✅ Applied | `Sysvar<'info, Rent>` type used — Anchor validates the pubkey matches the canonical sysvar address |
| 31 | UncheckedAccount | All UncheckedAccount fields documented | ✅ Applied | Every `UncheckedAccount` has a `/// CHECK:` comment explaining why it's safe |

---

## Assumptions Made

- The program authority is set at `initialize` time and is a trusted admin keypair. Key rotation is not implemented — extend with a `transfer_authority` instruction if needed.
- `mpl_token_metadata` program is deployed at the canonical `TOKEN_METADATA_PROGRAM_ID` on the target cluster. Verified via `require_keys_eq!` before every CPI.
- NFT mints are true 1-of-1 (master edition `max_supply = Some(0)`). If prints are desired, `max_supply` logic must be revised.
- Metadata is set as `is_mutable = true` to allow post-reveal updates. If immutability is required after reveal, a `freeze_metadata` instruction should be added that calls Metaplex's update authority to revoke mutability.
- The whitelist is append-only from the user's perspective — users cannot add themselves. All `add_to_whitelist` calls are admin-only.
- Payment goes directly to the authority wallet. For revenue splits or escrow, replace the system transfer CPI with a more complex distribution pattern.

---

## Known Limitations / Follow-up for Auditor

1. **`init_if_needed` on `user_token_account`** — Used intentionally for the ATA, which is an idempotent operation by design. Security invariant is maintained by the `has_minted` flag, not by the ATA constraint. Auditor should verify the ATA is the correct mint/owner combination.

2. **Metadata `is_mutable = true`** — Authority can update name/URI after mint. If this is a reveal collection, this is intentional. If permanence is required, add a post-reveal `freeze_metadata` instruction.

3. **No royalty enforcement** — `seller_fee_basis_points = 0` and no creators array. If royalties are required, extend with creator/royalty fields in `MintConfig` and populate the `DataV2` struct accordingly.

4. **Metaplex `UncheckedAccount` for `metadata` and `master_edition`** — These PDAs are derived and validated inside the Metaplex program, not here. Auditor should verify the correct PDA addresses are passed from the client, and that the Metaplex program version matches expected behavior.

5. **No pause mechanism** — If the mint needs to be pauseable (e.g., during an incident), add a `paused: bool` flag to `MintConfig` and a `require!(!config.paused, ...)` at the top of `mint_nft`.

6. **No update_authority transfer** — Currently MintConfig PDA is the permanent update authority. For post-reveal immutability or for handing off to a DAO, a `transfer_update_authority` instruction is recommended.

7. **SOL balance buffer** — The post-CPI balance check allows `price + 10_000 lamports` tolerance for tx fees. This should be reviewed for the specific deployment environment — adjust if needed.

---

*Generated using Frank Castle's Safe Solana Builder*

## references

```

```

## references/anchor.md

# Anchor Framework — Specific Patterns, Constraints & Pitfalls
# Frank Castle — Safe Solana Builder
# Read this AFTER shared-base.md when the user selects Anchor.
# Claude: every rule here is in addition to — not instead of — shared-base.md.

---

## 1. ACCOUNT TYPES — USE THE RIGHT WRAPPER

Anchor provides typed account wrappers. Using the wrong one skips critical checks.

### 1.1 Account Type Selection Rules
- **`Account<'info, T>`** — Use whenever you expect typed, owned data. Anchor automatically verifies:
  - The account's owner matches the program that defined `T`
  - The discriminator (first 8 bytes) matches `T`
  - Never use `AccountInfo` or `UncheckedAccount` where `Account<T>` is possible.

- **`Signer<'info>`** — Use for accounts that must sign. Anchor verifies `is_signer` automatically.

- **`SystemAccount<'info>`** — Use for accounts that must be owned by the System Program.

- **`Program<'info, T>`** — Use for program accounts (e.g., `Program<'info, Token>`). Verifies the account is executable and matches the program ID.

- **`UncheckedAccount<'info>`** — Use only when you have a deliberate, documented reason. **Always add a `/// CHECK: <reason>` safety comment** explaining exactly why it's safe. Anchor requires this comment — treat it as a serious obligation, not boilerplate.

- **`Interface<'info, T>`** / **`InterfaceAccount<'info, T>`** — Use for Token-2022 compatible programs. Supports both legacy Token and Token-2022.

### 1.2 Never Use AccountInfo for Typed Data
- `AccountInfo` gives you raw bytes — no discriminator check, no owner check, no type safety.
- If you find yourself using `AccountInfo` and manually deserializing, switch to `Account<T>` unless you have a specific reason not to.

---

## 2. CONSTRAINTS — DECLARE, DON'T IMPEACH

Anchor's `#[account(...)]` constraints are your first line of defense. Push as much validation as possible into constraints, not function bodies.

### 2.1 Core Constraint Patterns

```rust
#[account(
    mut,                                          // must be writable
    has_one = authority,                          // vault.authority == ctx.accounts.authority.key()
    has_one = token_account,                      // vault.token_account == ctx.accounts.token_account.key()
    constraint = vault.amount >= amount @ ErrorCode::InsufficientFunds,
    constraint = vault.key() != destination.key() @ ErrorCode::SameAccount,
)]
pub vault: Account<'info, Vault>,
```

### 2.2 `has_one` — Enforce Cross-Account Relationships
- Use `has_one = field` to verify that a field in the deserialized account matches another account in the context.
- This replaces manual pubkey comparison inside your instruction logic.
- **Every account that "belongs to" another must have this enforced.**

### 2.3 `seeds` and `bump` — PDA Derivation in Constraints
- Use Anchor's built-in PDA verification:
  ```rust
  #[account(
      seeds = [b"vault", user.key().as_ref()],
      bump = vault.bump,
  )]
  pub vault: Account<'info, Vault>,
  ```
- **Always store the canonical bump in your account struct** and pass it back in the constraint.
- Do not use `bump` without `seeds` — it does nothing alone.
- Do not let users provide the bump — store it at init time and reuse it.

### 2.4 `init` vs `init_if_needed` — Critical Distinction
- **`init`**: Creates the account. Fails if the account already exists. Use this for one-time initialization. It sets discriminator + owner, preventing reinitialization.
- **`init_if_needed`**: Creates if not exists, skips if already exists. **This is a footgun.**
  - If you use `init_if_needed`, you MUST manually verify that the existing account's state is valid for your instruction — an attacker can pre-create the account with malicious state.
  - Add explicit checks: `require!(!account.initialized || account.authority == ctx.accounts.user.key(), ...)`.
  - Prefer `init` unless you have a documented reason to use `init_if_needed`.

### 2.5 `close = recipient` — Secure Account Closing
- Use Anchor's `close` constraint to properly close accounts:
  ```rust
  #[account(mut, close = user)]
  pub escrow: Account<'info, Escrow>,
  ```
- This zeroes the data, transfers lamports, and reassigns ownership to the System Program in one safe operation.
- **Always close to a trusted recipient** — never to a user-provided arbitrary account for admin-only closures.
- Never manually drain lamports without using `close` or following the manual sequence in shared-base.md §6.3.

### 2.6 `realloc` — Safe Account Resizing
- When resizing an account:
  ```rust
  #[account(
      mut,
      realloc = new_size,
      realloc::payer = user,
      realloc::zero_init = true,
  )]
  ```
- **Set `zero_init: true`** when increasing account size after a prior decrease in the same transaction. Prevents reading stale "dirty" memory that was previously used.
- Without `zero_init`, leftover bytes from a previously-shrunk account could be misread as valid data.

---

## 3. STATE MANAGEMENT

### 3.1 `reload()` After CPI — Non-Negotiable
- Anchor caches deserialized account data in memory. After any CPI that modifies an account, the in-memory struct is stale.
- **Always call `ctx.accounts.account_name.reload()?`** after a CPI that modifies that account before using its data.
- This applies even if the CPI is to your own program. Treat every CPI as a black box that may modify state.

```rust
// Transfer tokens via CPI
token::transfer(cpi_ctx, amount)?;

// Reload before using updated balance
ctx.accounts.token_account.reload()?;
let new_balance = ctx.accounts.token_account.amount;
```

### 3.2 Secure Initialization
- Your `initialize` instruction must be callable **exactly once**.
- Anchor's `init` constraint enforces this automatically (fails if account already exists).
- If you implement a custom initialization pattern, add an `initialized: bool` flag and `require!(!state.initialized, ...)` as the first check.

### 3.3 `#[access_control(...)]` — Pre-Condition Checks
- Use `#[access_control(check_fn(&ctx))]` for pre-condition checks that apply to an entire instruction.
- Keeps business logic clean by separating access checks from core logic.
- Ideal for: admin-only gates, protocol pause checks, time-lock validation.

---

## 4. TOKEN OPERATIONS — ANCHOR SPL

### 4.1 Token-2022 Compatible Transfers
- **Never use `anchor_spl::token::transfer`** for generic programs that may encounter Token-2022 mints.
- **Always use `anchor_spl::token_interface::transfer_checked`**:
  ```rust
  use anchor_spl::token_interface::{self, TransferChecked};
  
  token_interface::transfer_checked(
      CpiContext::new(
          ctx.accounts.token_program.to_account_info(),
          TransferChecked {
              from: ctx.accounts.from_ata.to_account_info(),
              mint: ctx.accounts.mint.to_account_info(),  // required
              to: ctx.accounts.to_ata.to_account_info(),
              authority: ctx.accounts.authority.to_account_info(),
          },
      ),
      amount,
      ctx.accounts.mint.decimals,  // required
  )?;
  ```
- Use `InterfaceAccount<'info, Mint>` and `InterfaceAccount<'info, TokenAccount>` for Token-2022 compatibility.
- Use `Interface<'info, TokenInterface>` instead of `Program<'info, Token>` for the token program account.

### 4.2 Mint and Decimal Validation
- Always validate the mint's `decimals` field matches your expected value before using it in calculations.
- Verify `mint.is_initialized` before operating on any mint account.

---

## 5. ANCHOR CPI PATTERNS

### 5.1 Program ID Validation in CPI
- For static, well-known programs, use `Program<'info, T>` — Anchor validates the ID automatically.
- For dynamic programs (e.g., user-provided callback programs):
  ```rust
  require_keys_eq!(
      ctx.accounts.external_program.key(),
      expected_program_id,
      ErrorCode::InvalidProgram
  );
  ```

### 5.2 CpiContext Construction
- Always construct `CpiContext` with only the accounts needed for that specific CPI call.
- Never pass your entire context to a CPI wrapper — it may expose accounts with unintended signer privileges.

### 5.3 Signer Seeds for PDA CPIs
- When a PDA must sign in a CPI:
  ```rust
  let seeds = &[b"vault", user.key().as_ref(), &[vault.bump]];
  let signer_seeds = &[&seeds[..]];
  CpiContext::new_with_signer(program, accounts, signer_seeds)
  ```
- Never hardcode bump values — always use the stored canonical bump.

---

## 6. ERROR HANDLING

### 6.1 Custom Error Codes
- **Always define program-specific error codes.** Never return generic errors or panic.
- Use Anchor's `#[error_code]` derive:
  ```rust
  #[error_code]
  pub enum ErrorCode {
      #[msg("Insufficient funds in vault")]
      InsufficientFunds,
      #[msg("Authority mismatch — provided authority does not own this account")]
      AuthorityMismatch,
      #[msg("Source and destination accounts must differ")]
      SameAccount,
      // ... etc
  }
  ```
- Descriptive error messages are crucial: they're the first thing an auditor and a debugging developer will see.

### 6.2 `require!` Over Manual if/return
- Use `require!(condition, ErrorCode::Variant)` for all validation checks.
- It's cleaner, shorter, and produces better error messages than manual `if !condition { return Err(...) }`.
- Use `require_keys_eq!`, `require_eq!`, `require_gt!`, etc. for typed comparisons.

---

## 7. ANCHOR-SPECIFIC FOOTGUNS SUMMARY

| Pattern | Safe Version | Unsafe Version |
|---|---|---|
| Account wrapping | `Account<'info, T>` | `AccountInfo` without `/// CHECK:` |
| Initialization | `init` constraint | `init_if_needed` without reinitialization guard |
| Cross-account link | `has_one = field` | Manual pubkey compare inside function body |
| Closing accounts | `close = recipient` constraint | Manually zeroing + draining without ownership transfer |
| After-CPI data use | `.reload()?` | Using cached struct values |
| Token transfers | `transfer_checked` via `token_interface` | `token::transfer` (legacy only) |
| PDA derivation | `seeds + bump` constraint with stored canonical bump | User-supplied bump |
| Memory resize | `realloc` with `zero_init = true` | Raw realloc without zeroing |
| Error reporting | `#[error_code]` with descriptive messages | `ProgramError::Custom(0)` or panics |

---

## 8. COMMON ANCHOR BUILD & TOOLING ERRORS

### GLIBC Version Too Old (`GLIBC_2.38` / `GLIBC_2.39` not found)
Anchor 0.31+ requires GLIBC ≥2.38; Anchor 0.32+ requires ≥2.39. Ubuntu 24.04+ ships 2.39.
**Fix:** Upgrade OS, or build Anchor CLI from source: `cargo install --git https://github.com/solana-foundation/anchor --tag v0.31.1 anchor-cli`

### `proc_macro_span_shrink` / Rust 1.80 Incompatibility
Anchor 0.30.x uses a `time` crate incompatible with Rust ≥1.80.
**Fix:** Use AVM (auto-pins rustc 1.79 for Anchor <0.31), or upgrade to Anchor 0.31+.

### `unexpected_cfg` Warnings
Newer Rust versions are stricter about `cfg` conditions. Add to `Cargo.toml`:
```toml
[lints.rust]
unexpected_cfgs = { level = "allow" }
```
Or upgrade to Anchor 0.31+.

### IDL Build Fails (`anchor build` or `anchor idl build`)
Ensure `idl-build` feature is enabled (required since 0.30.0):
```toml
[features]
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
```
Debug with: `ANCHOR_LOG=1 anchor build`. Skip IDL with: `anchor build --no-idl`.

### `module inner is private`
Version mismatch between `anchor-lang` crate and Anchor CLI. Match versions in `Cargo.toml` and `Anchor.toml`.

### `overflow-checks` Not Specified (Anchor 0.30+)
```toml
[profile.release]
overflow-checks = true
```

### Anchor Version Migration Quick Reference

**0.29 → 0.30:** Change `.accounts({...})` to `.accountsPartial({...})`. Add `idl-build` feature.

**0.30 → 0.31:** Remove direct `solana-program`/`solana-sdk` deps; use `anchor_lang::prelude::*` instead.

**0.31 → 0.32:** `solana-program` fully removed. Use `solana_pubkey::Pubkey` or `anchor_lang::prelude::*`. Duplicate mutable accounts now error — use `dup` constraint.

### `Connection refused` / IPv6 in Tests
Node.js 17+ resolves `localhost` to `::1` but `solana-test-validator` binds to `127.0.0.1`.
**Fix:** Set `cluster = "http://127.0.0.1:8899"` in `Anchor.toml`, or use `NODE_OPTIONS="--dns-result-order=ipv4first"`.

### `declare_program!` IDL Not Found
Place IDL JSON in `idls/<program_name>.json` at workspace root (snake_case filename matching program name).

### CLI / Crate Version Mismatch Warnings
Warnings like `anchor-lang version(0.32.1) and CLI(0.30.1) don't match` are cosmetic — builds succeed. Match versions in `Anchor.toml [toolchain]` and install with `avm install <version>` to eliminate them.

## references/litesvm.md

# LiteSVM — Testing Reference
# Frank Castle — Safe Solana Builder
# Use this file when the developer chooses LiteSVM for testing.
# Applies to ALL frameworks: Native Rust, Anchor, and Pinocchio.

---

## Overview

LiteSVM is a fast, in-process Solana VM for testing programs without starting
a validator. It embeds the full Solana runtime inside your Rust test process,
giving you deterministic, fast, zero-external-process tests.

### LiteSVM vs. the Alternatives

| Concern | `solana-test-validator` | `solana-program-test` | `litesvm` |
|---|---|---|---|
| **Speed** | Slowest (full validator) | Moderate | Fastest (in-process) |
| **Setup** | External process required | Async runtime required | None — `LiteSVM::new()` |
| **Sysvar control** | Limited | Limited | Full — set Clock, Rent, Slot freely |
| **Account injection** | Via RPC | Manual | `svm.set_account()` one-liner |
| **Devnet account replay** | Native | Manual | `svm.set_account()` with RPC data |
| **CI-friendliness** | Poor | Good | Excellent |
| **RPC method coverage** | Full | Partial | Partial — use validator for RPC tests |
| **Real validator behaviour** | ✅ | Approximation | Approximation |

**Use `litesvm` for:** unit and integration tests of program logic, security edge cases,
CU profiling, and CI pipelines.

**Use `solana-test-validator` for:** RPC method testing, wallet integration tests,
or any case where real-life validator behaviour matters.

---

## 1. PROJECT SETUP

### Cargo.toml (dev dependencies)

```toml
[dev-dependencies]
litesvm = "0.9.1"
litesvm-token = "0.9.1"

solana-instruction = "3.1.0"
solana-keypair = "3.1.0"
solana-native-token = "3.0.0"
solana-pubkey = "4.1.0"
solana-signer = "3.0.0"
solana-transaction = "3.0.2"
solana-message = "3.0.1"
solana-sdk-ids = "3.1.0"
spl-token-2022 = { version = "10.0.0", features = ["no-entrypoint"]}
spl-associated-token-account = "8.0.0"
solana-rpc-client = "3.1.9"
solana-address = "2.2.0"
solana-account = "4.1.0"
```

### Running tests

```bash
# Run all tests with output
cargo test -- --nocapture

# Run a specific test
cargo test test_make -- --nocapture

# Run serially (required if using a global CU summary Mutex)
cargo test -- --nocapture --test-threads=1
```

---

## 2. CORE PATTERNS

### 2a. Setup — Initialize and Fund

```rust
use litesvm::LiteSVM;
use litesvm_token::{CreateMint, CreateAssociatedTokenAccount, MintTo};
use solana_keypair::Keypair;
use solana_native_token::LAMPORTS_PER_SOL;
use solana_signer::Signer;
use std::path::PathBuf;

fn setup() -> (LiteSVM, Keypair) {
    let mut svm = LiteSVM::new();
    let payer = Keypair::new();

    // Airdrop SOL to payer
    svm.airdrop(&payer.pubkey(), 50 * LAMPORTS_PER_SOL)
        .expect("Airdrop failed");

    // Load the compiled program .so
    let so_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("../../target/deploy/my_program.so");
    let program_data = std::fs::read(&so_path)
        .unwrap_or_else(|_| panic!("Cannot read SO at {:?} — run cargo build-sbf first", so_path));

    svm.add_program(MY_PROGRAM_ID, &program_data);

    (svm, payer)
}
```

**Security notes:**
- Build the `.so` with `cargo build-sbf` before running tests — missing SO gives a
  confusing panic, not a compile error.
- Always use `LAMPORTS_PER_SOL` — never hardcode lamport values in tests.
- Use a dedicated `payer` per test via `Keypair::new()` — never share mutable state
  between tests.

---

### 2b. Loading Accounts from Devnet (Account Replay)

LiteSVM has no network access. To test against real devnet/mainnet accounts,
fetch the account data with an RPC client and inject it:

```rust
use litesvm::LiteSVM;
use solana_account::Account;
use solana_pubkey::Pubkey;
use solana_rpc_client::rpc_client::RpcClient;
use std::str::FromStr;

fn inject_devnet_account(svm: &mut LiteSVM, address: &str) {
    let rpc = RpcClient::new("https://api.devnet.solana.com");
    let pubkey = Pubkey::from_str(address).unwrap();
    let fetched = rpc.get_account(&pubkey).expect("Failed to fetch account");

    svm.set_account(
        pubkey,
        Account {
            lamports:   fetched.lamports,
            data:       fetched.data,
            owner:      Pubkey::from(fetched.owner.to_bytes()),
            executable: fetched.executable,
            rent_epoch: fetched.rent_epoch,
        },
    ).unwrap();
}
```

**Security notes:**
- Devnet account data can be stale — note the fetch timestamp in a comment.
- Never rely on devnet accounts for security-critical assertions; use injected
  accounts only for environment setup (e.g. price oracle fixtures, SPL mints).
- Do not hardcode private keys or funded keypairs from devnet in test files —
  generate fresh `Keypair::new()` per test.

---

### 2c. Token Setup with litesvm-token

```rust
use litesvm_token::{
    spl_token::ID as TOKEN_PROGRAM_ID,
    CreateMint, CreateAssociatedTokenAccount, MintTo,
};

// Create a mint (6 decimals, authority = maker)
let mint_a = CreateMint::new(&mut svm, &payer)
    .decimals(6)
    .authority(&maker.pubkey())
    .send()
    .unwrap();

// Create an associated token account for owner
let maker_ata = CreateAssociatedTokenAccount::new(&mut svm, &payer, &mint_a)
    .owner(&maker.pubkey())
    .send()
    .unwrap();

// Mint tokens (raw units — 1_000_000 = 1 token at 6 decimals)
MintTo::new(&mut svm, &payer, &mint_a, &maker_ata, 1_000_000_000)
    .send()
    .unwrap();
```

---

### 2d. Building and Sending Transactions

```rust
use solana_instruction::Instruction;
use solana_message::Message;
use solana_transaction::Transaction;

// Build instruction
let ix = Instruction {
    program_id: MY_PROGRAM_ID,
    accounts:   /* account metas */,
    data:       /* serialized instruction data */,
};

// Build, sign, send — always use latest_blockhash()
let message = Message::new(&[ix], Some(&payer.pubkey()));
let blockhash = svm.latest_blockhash();
let tx = Transaction::new(&[&payer], message, blockhash);

let result = svm.send_transaction(tx).unwrap();
println!("CUs consumed: {}", result.compute_units_consumed);
println!("Logs: {:?}", result.logs);
```

#### TransactionMetadata fields

```rust
pub struct TransactionMetadata {
    pub signature:             Signature,
    pub logs:                  Vec<String>,
    pub inner_instructions:    InnerInstructionsList,
    pub compute_units_consumed: u64,
    pub return_data:           TransactionReturnData,
}
```

#### Handling failures

```rust
use solana_transaction_error::TransactionError;
use solana_instruction::error::InstructionError;

match svm.send_transaction(tx) {
    Ok(meta) => { /* success */ }
    Err(failed) => {
        match failed.err {
            TransactionError::InsufficientFundsForFee => { /* ... */ }
            TransactionError::InstructionError(idx, err) => {
                // idx = which instruction in the tx failed
                // err = the InstructionError variant
                eprintln!("Instruction {} failed: {:?}", idx, err);
            }
            other => eprintln!("Transaction error: {:?}", other),
        }
    }
}
```

---

### 2e. Sending a Helper Function (Recommended Pattern)

Wrap send logic to reduce boilerplate across tests. Call `expire_blockhash()`
after every transaction to prevent "blockhash not found" errors on the next call:

```rust
fn send_tx(
    svm: &mut LiteSVM,
    instructions: &[Instruction],
    payer: &Pubkey,
    signers: &[&Keypair],
) -> u64 {
    let message = Message::new(instructions, Some(payer));
    let blockhash = svm.latest_blockhash();
    let tx = Transaction::new(signers, message, blockhash);
    let result = svm.send_transaction(tx).expect("Transaction failed");
    svm.expire_blockhash(); // advance blockhash after every tx
    result.compute_units_consumed
}
```

> **Why `expire_blockhash()`?** LiteSVM keeps a single current blockhash. If you
> send multiple transactions without advancing it, subsequent transactions will fail
> with `BlockhashNotFound`. Always expire after each send in multi-transaction tests.

---

## 3. SYSVAR & TIME CONTROL 

Full sysvar manipulation is one of LiteSVM's most powerful capabilities —
essential for testing time locks, deadlines, and slot-dependent logic.

### 3a. Time Travel (Clock) if the instructions require time manipulation

```rust
use anchor_lang::prelude::Clock; // or solana_clock::Clock for native

// Read current clock
let mut clock: Clock = svm.get_sysvar();

// Advance time (e.g. 5 days forward)
let five_days_secs = 5 * 24 * 60 * 60 + 1;
clock.unix_timestamp += five_days_secs;
svm.set_sysvar(&clock);

// Jump to a specific slot
svm.warp_to_slot(500);
```

**Security notes:**
- Always test both before and after a deadline — `test_X_before_deadline_fails`
  and `test_X_after_deadline_succeeds`. Both tests are required to validate a
  time lock is actually enforced.
- When warping time, also `expire_blockhash()` if subsequent transactions
  need a fresh blockhash.

### 3b. Rent sysvar

```rust
use solana_rent::Rent;

let rent: Rent = svm.get_sysvar();
let min_balance = rent.minimum_balance(data_size);
```

---

## 4. ACCOUNT INSPECTION

```rust
// Get raw account (returns Option<Account>)
let account = svm.get_account(&pubkey).expect("Account not found");
println!("lamports: {}", account.lamports);
println!("data len: {}", account.data.len());
println!("owner:    {}", account.owner);

// Get lamport balance directly
let balance = svm.get_balance(&pubkey).unwrap();

// Read SPL token balance (amount at offset 64 in token account layout)
fn get_token_balance(svm: &LiteSVM, token_account: &Pubkey) -> u64 {
    let acc = svm.get_account(token_account).expect("Token account not found");
    u64::from_le_bytes(acc.data[64..72].try_into().unwrap())
}

// Inject or override an account
use solana_account::Account;
svm.set_account(
    pubkey,
    Account {
        lamports:   1_000_000,
        data:       vec![/* serialized state */],
        owner:      MY_PROGRAM_ID,
        executable: false,
        rent_epoch: 0,
    },
).unwrap();
```

---

## 5. COMPUTE UNIT PROFILING

Track CU consumption across instructions — a natural fit alongside Pinocchio's
CU-reduction goals.

```rust
use std::sync::Mutex;

// Global CU collector (run tests with --test-threads=1 for accurate aggregation)
static CU_RESULTS: Mutex<Vec<(&'static str, u64)>> = Mutex::new(Vec::new());

fn record_cu(label: &'static str, cu: u64) {
    CU_RESULTS.lock().unwrap().push((label, cu));
}

// In your test:
let cu = send_tx(&mut svm, &[ix], &payer.pubkey(), &[&payer]);
record_cu("initialize/base", cu);

// Summary test (name with zz_ prefix so it runs last)
#[test]
fn zz_cu_summary() {
    let results = CU_RESULTS.lock().unwrap();
    if results.is_empty() {
        println!("No CU results (run tests with --test-threads=1)");
        return;
    }
    println!("\n=== Compute Unit Summary ===");
    for (label, cu) in results.iter() {
        println!("  {:<40} {:>8} CUs", label, cu);
    }
}
```

### Custom compute budget

```rust
let mut budget = litesvm::ComputeBudget::default();
budget.compute_unit_limit = 2_000_000; // raise ceiling for profiling
svm.with_compute_budget(budget);
```

---

## 6. SIMULATION (DRY-RUN)

Simulate a transaction without committing state changes — useful for pre-flight
checks and failure path verification:

```rust
match svm.simulate_transaction(tx) {
    Ok(sim_result) => {
        println!("Would succeed");
        println!("CUs: {}", sim_result.meta.compute_units_consumed);
        println!("Logs: {:?}", sim_result.meta.logs);
    }
    Err(err) => {
        println!("Would fail: {:?}", err.err);
        println!("Logs: {:?}", err.meta.logs);
    }
}
```

---

## 7. FRAMEWORK-SPECIFIC PATTERNS

### 7a. Anchor — Building Instructions

With raw LiteSVM + Anchor, use the generated `accounts::` and `instruction::`
types to get proper account metas and discriminated instruction data:

```rust
use anchor_lang::{InstructionData, ToAccountMetas};

let ix = Instruction {
    program_id: PROGRAM_ID,
    accounts: crate::accounts::Make {
        maker,
        mint_a,
        vault,
        escrow,
        system_program: SYSTEM_PROGRAM_ID,
        token_program:  TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
    }
    .to_account_metas(None),
    data: crate::instruction::Make {
        seed:    123u64,
        deposit: 1_000_000,
        receive: 500_000,
    }
    .data(), // automatically adds Anchor's 8-byte discriminator
};
```

**Deserializing Anchor account state:**

```rust
use anchor_lang::AccountDeserialize;

let escrow_account = svm.get_account(&escrow_pda).unwrap();
let escrow_state = crate::state::Escrow::try_deserialize(
    &mut escrow_account.data.as_ref()
).unwrap();
assert_eq!(escrow_state.maker, maker_pubkey);
```

### 7b. Native / Pinocchio — Manual Instruction Data

Without a framework generating discriminators, encode instruction data manually.
Use a consistent first-byte discriminator scheme and document it:

```rust
// Instruction discriminators — must match processor.rs match arms
// 0 = Initialize, 1 = Contribute, 2 = CheckContributions, 3 = Refund

let init_data: Vec<u8> = [
    vec![0u8],                               // discriminator
    vec![bump],                              // PDA bump
    amount_to_raise.to_le_bytes().to_vec(),  // u64 LE
    vec![duration],                          // u8
]
.concat();

// Verify: data length must match exactly what the handler expects
// Document this comment next to every manual instruction build:
// discriminator(1) + bump(1) + amount(8) + duration(1) = 11 bytes
```

**Reading native account state (fixed-size layout):**

```rust
// Document field offsets matching the on-chain struct layout:
// [0..32]  = maker: Pubkey
// [32..64] = mint:  Pubkey
// [64..72] = amount_to_raise: u64
// [88]     = duration: u8
// [89]     = bump: u8
let fund_data = svm.get_account(&fundraiser_pda).unwrap().data;
let stored_amount = u64::from_le_bytes(fund_data[64..72].try_into().unwrap());
assert_eq!(stored_amount, expected_amount);
```

---

## 8. TEST STRUCTURE PATTERNS

### 8a. Shared Setup Helper (Recommended)

Extract common environment setup into a `setup()` function. Return everything the
test needs — never put shared mutable state in statics for LiteSVM:

```rust
fn setup() -> (LiteSVM, Keypair) { /* load program, airdrop, return */ }

fn setup_with_initialized_program(
    svm: &mut LiteSVM,
    maker: &Keypair,
) -> (Pubkey, Pubkey, Pubkey) {
    // create mints, ATAs, derive PDAs, send Initialize tx
    // return (mint, escrow_pda, vault_pda)
}
```

### 8b. Required Test Coverage Matrix

For every instruction, cover all four quadrants:

| | Success | Failure |
|---|---|---|
| **Happy path** | ✅ Full state verification | — |
| **Authorization** | — | ✅ Wrong signer → must fail |
| **Time / sequence** | ✅ After deadline | ✅ Before deadline |
| **Arithmetic edge** | — | ✅ Over-limit / zero-amount |
| **Reinit guard** | — | ✅ Double-initialize → must fail |
| **Account closure** | ✅ Verify lamports=0, data=0, owner=system | — |

### 8c. Account Closure Assertions

When a program closes an account, verify all three fields — not just lamports:

```rust
let closed = svm.get_account(&vault_pda).unwrap();
assert_eq!(closed.data.len(), 0,       "Closed account must have empty data");
assert_eq!(closed.lamports,    0,       "Closed account must have zero lamports");
assert_eq!(closed.owner,       SYSTEM_PROGRAM_ID, "Closed account must be owned by system");
```

---

## 9. LITESVM SECURITY TEST CHECKLIST

The following security tests must exist for every program using LiteSVM:

- [ ] Happy path succeeds end-to-end with full state verification
- [ ] Wrong signer → transaction fails
- [ ] Re-initialization of an existing account → fails
- [ ] Before-deadline action → fails (if time-locked)
- [ ] After-deadline action → succeeds (if time-locked)
- [ ] Over-contribution / over-limit arithmetic → fails
- [ ] Account closure verified: lamports=0, data.len()=0, owner=system_program
- [ ] Token balances verified after every transfer with explicit `assert_eq!`
- [ ] PDA derivation uses same seeds as on-chain — document them in test comments
- [ ] `expire_blockhash()` called after every transaction in multi-tx tests
- [ ] No `unwrap()` on `send_transaction` in tests that expect failure — use `assert!(result.is_err())`
- [ ] CU consumption logged at minimum for Initialize, primary action, and close

---

## 10. COMMON LITESVM ERRORS

| Error | Cause | Fix |
|---|---|---|
| `Failed to read SO file` | `cargo build-sbf` not run | Run `cargo build-sbf` first; check path in `PathBuf::from(env!("CARGO_MANIFEST_DIR"))` |
| `BlockhashNotFound` | Second tx uses same blockhash | Call `svm.expire_blockhash()` after each `send_transaction` |
| `InsufficientFundsForFee` | Payer has no SOL | `svm.airdrop(&payer.pubkey(), N * LAMPORTS_PER_SOL)` before test |
| `InvalidProgramForExecution` | Program not loaded or wrong ID | Verify `PROGRAM_ID` matches the `declare_id!` in `lib.rs` |
| `AccountNotFound` in assertion | Account never created or wrong pubkey | Check PDA derivation seeds match exactly; print pubkeys before asserting |
| GLIBC version error | Linux system < 2.38 | Use `solana-bankrun` instead |
| Token account not found | ATA not created before use | Create ATA with `CreateAssociatedTokenAccount` before minting or transferring |
| Wrong instruction data length | Manual data encoding off-by-one | Print `data.len()` and cross-check against handler's expected parse |
| Tests pass in isolation, fail together | Shared global state | Use `--test-threads=1`; never share `LiteSVM` across tests via globals |

## references/native-rust.md

# Native Rust — Solana-Specific Patterns & Pitfalls
# Frank Castle — Safe Solana Builder
# Read this AFTER shared-base.md when the user selects Native Rust.
# Claude: every rule here is in addition to — not instead of — shared-base.md.

---

## 1. ACCOUNT DESERIALIZATION & VALIDATION

In native Rust, there is no framework to catch you. Every check that Anchor does automatically, you do by hand. Miss one and you ship a vulnerability.

### 1.1 The Mandatory Validation Sequence
For every account passed into your instruction, perform all applicable checks in this order before touching any data:

```
1. Key check       — is this the account I expect (if fixed)?
2. Owner check     — does the right program own this account?
3. Signer check    — does this account need to have signed?
4. Writable check  — does this account need to be writable?
5. Discriminator   — does the data belong to the expected type?
6. Data validation — are fields within expected ranges?
```

Never skip steps. Never reorder them. Perform all applicable checks before proceeding.

### 1.2 Owner Check — The Most Commonly Missed
```rust
if account.owner != program_id {
    return Err(ProgramError::IncorrectProgramId);
}
```
- Check this before deserializing. Deserializing data from the wrong program is a type cosplay attack.
- For accounts owned by the System Program (e.g., user wallets): `account.owner == &system_program::ID`.
- For token accounts: `account.owner == &spl_token::ID` (or `spl_token_2022::ID`).

### 1.3 Signer Check
```rust
if !authority.is_signer {
    return Err(ProgramError::MissingRequiredSignature);
}
```
- Perform this for every account that represents an authority, admin, or user authorizing an action.
- Never assume a keypair account signed just because it's listed.

### 1.4 Discriminator / Type Check
- Native Rust accounts don't get automatic discriminators. You must manage them.
- Design pattern: reserve the first byte(s) of every account's data as a type tag.
```rust
const VAULT_DISCRIMINATOR: u8 = 1;
const USER_DISCRIMINATOR: u8 = 2;

let data = account.try_borrow_data()?;
if data[0] != VAULT_DISCRIMINATOR {
    return Err(MyError::InvalidAccountType.into());
}
```
- Without discriminators, an attacker can pass a `UserAccount` where a `VaultAccount` is expected if they have the same shape.

### 1.5 Key Check (for Fixed Accounts)
```rust
if clock_sysvar.key != &sysvar::clock::ID {
    return Err(ProgramError::InvalidArgument);
}
```
- Hardcode expected pubkeys for all fixed accounts: sysvars, known program IDs, config accounts.
- Never trust position alone to identify a fixed account.

---

## 2. DESERIALIZATION

### 2.1 Use `try_from_slice` — Never Assume Layout
```rust
let vault = Vault::try_from_slice(&account.data.borrow())?;
```
- Always use Borsh deserialization (`try_from_slice`) rather than manual byte casting.
- Manual byte casting with transmutes or raw pointer reads is undefined behavior territory.

### 2.2 Verify Data Length Before Deserializing
```rust
if account.data_len() < Vault::LEN {
    return Err(ProgramError::InvalidAccountData);
}
```
- An undersized account will panic or misread data during deserialization.
- Define a `LEN` constant for every struct.

### 2.3 Borrow Data Carefully
```rust
let data = account.try_borrow_data()?;   // immutable borrow for reading
let mut data = account.try_borrow_mut_data()?;  // mutable borrow for writing
```
- Never hold both a mutable and immutable borrow of the same account simultaneously — this will panic at runtime.
- Drop borrows before taking new ones on the same account.

---

## 3. PDA DERIVATION — NATIVE RUST

### 3.1 Find and Store Canonical Bump at Init Time
```rust
// At initialization
let (vault_pda, bump) = Pubkey::find_program_address(
    &[b"vault", user.key.as_ref()],
    program_id,
);

// Verify the passed-in vault account matches the derived PDA
if vault_pda != *vault.key {
    return Err(ProgramError::InvalidArgument);
}

// Store bump in account data
vault_state.bump = bump;
```

### 3.2 Re-derivation on Subsequent Calls (Use Stored Bump)
```rust
// On subsequent instructions, use the stored bump — don't re-find it
let expected_vault = Pubkey::create_program_address(
    &[b"vault", user.key.as_ref(), &[vault_state.bump]],
    program_id,
)?;

if expected_vault != *vault.key {
    return Err(ProgramError::InvalidArgument);
}
```
- `create_program_address` is cheaper than `find_program_address` (no iteration).
- Never allow the user to supply a bump — they must use the one you stored.

### 3.3 Verify PDA is not a Signer Unless Expected
- PDAs cannot sign transactions initiated externally. If you receive a PDA that claims `is_signer = true` in a user-submitted transaction, something is wrong — reject it.

---

## 4. CPI IN NATIVE RUST

### 4.1 invoke — For Non-PDA-Signed CPIs
```rust
invoke(
    &system_instruction::transfer(from.key, to.key, lamports),
    &[from.clone(), to.clone(), system_program.clone()],
)?;
```
- Pass only the accounts the callee needs — nothing more.
- Always verify the callee's program ID before calling: `if callee_program.key != &expected_program::ID { ... }`.

### 4.2 invoke_signed — For PDA-Signed CPIs
```rust
let seeds = &[b"vault", user.key.as_ref(), &[vault_state.bump]];
invoke_signed(
    &instruction,
    &[account_a.clone(), account_b.clone()],
    &[seeds],
)?;
```
- Only use `invoke_signed` when your PDA must authorize the CPI.
- The seeds you provide must exactly match the PDA's derivation — any mismatch causes a runtime error.
- Never elevate non-signer accounts to signer status through `invoke_signed`.

### 4.3 Post-CPI Account Data Refresh
```rust
// CPI may have modified vault — refresh data manually
let vault_data = vault.try_borrow_data()?;
let updated_vault = Vault::try_from_slice(&vault_data)?;
```
- Unlike Anchor's `.reload()`, in native Rust you must manually re-borrow and re-deserialize the account data after a CPI.
- Never use a local variable that was deserialized before the CPI to make decisions after the CPI.

---

## 5. ACCOUNT CREATION & RENT

### 5.1 Creating Accounts via System Program CPI
```rust
let rent = Rent::get()?;
let lamports = rent.minimum_balance(Vault::LEN);

invoke(
    &system_instruction::create_account(
        payer.key,
        new_account.key,
        lamports,
        Vault::LEN as u64,
        program_id,  // owner = your program
    ),
    &[payer.clone(), new_account.clone(), system_program.clone()],
)?;
```
- Always fund with `rent.minimum_balance(size)` — never a hardcoded lamport value.
- Set `owner = program_id` immediately — the System Program creates the account, your program owns the data.

### 5.2 Pre-allocated Accounts (User-Created Externally)
- If a user pre-creates the account before calling your instruction, verify:
  1. `account.owner == program_id` (after `create_account`, it should be)
  2. `account.data_len() >= expected_len`
  3. `account.lamports() >= rent.minimum_balance(account.data_len())`
  4. Data is all zeros (not previously initialized)

---

## 6. ACCOUNT CLOSING — NATIVE RUST

Proper close sequence — never skip any step:

```rust
// Step 1: Transfer lamports to recipient
let dest_lamports = recipient.lamports();
**recipient.lamports.borrow_mut() = dest_lamports
    .checked_add(account_to_close.lamports())
    .ok_or(MyError::Overflow)?;
**account_to_close.lamports.borrow_mut() = 0;

// Step 3: Assign ownership back to System Program
account_to_close.assign(&system_program::ID);

// Step 4: Realloc to 0 data len
info.realloc(0, false)?;
```

- Skipping step 1 (zero lamports) recover all the lamports that were deposited for rent or other purposes
- Skipping step 2 (reassigning owner) means your program still "owns" a zero-balance account, wasting space.
- Skipping step 3 (realloc) we must realloc to zero bytes otherwise rent must be deposited
- The recipient must be a trusted address — never arbitrary user-supplied.

---

## 7. ERROR HANDLING

### 7.1 Define a Custom Error Enum
```rust
use thiserror::Error;
use solana_program::program_error::ProgramError;

#[derive(Debug, Error)]
pub enum MyError {
    #[error("Authority mismatch")]
    AuthorityMismatch,
    #[error("Insufficient vault balance")]
    InsufficientBalance,
    #[error("Account already initialized")]
    AlreadyInitialized,
    #[error("Invalid account discriminator")]
    InvalidAccountType,
    #[error("Arithmetic overflow")]
    Overflow,
}

impl From<MyError> for ProgramError {
    fn from(e: MyError) -> Self {
        ProgramError::Custom(e as u32)
    }
}
```

- Every error must have a descriptive message — `Custom(0)` is meaningless to an auditor or debugger.
- Use `?` with `map_err` to convert errors cleanly: `.ok_or(MyError::Overflow)?`.

### 7.2 Never Panic in Production Code
- `unwrap()` and `expect()` are banned in instruction handlers — they crash the entire program.
- Use `?`, `ok_or()`, `unwrap_or_else()`, or explicit match patterns everywhere.

---

## 8. INSTRUCTION PARSING

### 8.1 Deserialize Instruction Data Safely
```rust
#[derive(BorshDeserialize)]
pub struct TransferArgs {
    pub amount: u64,
    pub min_expected: u64,
}

let args = TransferArgs::try_from_slice(instruction_data)
    .map_err(|_| ProgramError::InvalidInstructionData)?;
```

### 8.2 Validate All Instruction Arguments
- After parsing, validate all fields: ranges, non-zero requirements, flag combinations.
- `amount == 0` should be explicitly rejected if not meaningful.
- Never trust instruction data — it's user-supplied and can be anything.

---

## 9. NATIVE RUST SECURITY CHECKLIST (Quick Reference)

Before submitting any instruction handler for review, verify:

- [ ] Owner check on every data account before deserialization
- [ ] Signer check on every authority account  
- [ ] Discriminator check on every deserialized account
- [ ] Key check on every fixed/known account (sysvars, programs)
- [ ] Duplicate mutable account check between any two mutable accounts
- [ ] Canonical bump stored at init, reused on subsequent calls
- [ ] `try_from_slice` used for all deserialization (no raw byte casting)
- [ ] Data length verified before deserialization
- [ ] No `unwrap()` or `expect()` in instruction handlers
- [ ] Checked arithmetic on all financial math
- [ ] CPI callee program ID verified before invoke
- [ ] Account data re-read after every CPI
- [ ] Account close performs: zero data → transfer lamports → assign to system program
- [ ] New accounts funded with `rent.minimum_balance(size)`, not hardcoded lamports
- [ ] Initialization guard (check data is zeroed / flag is false before init)

---

## 10. COMMON NATIVE BUILD & TOOLING ERRORS

### `cargo build-sbf` Not Found
Solana CLI not installed or not on PATH.
**Fix:** `sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"` then add to PATH:
`export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"`

### `cargo build-bpf` Deprecation Warning
Expected — BPF is deprecated in favor of SBF. Use `cargo build-sbf`. Anchor 0.30+ handles this automatically.

### Platform Tools Corruption After Install
```
[ERROR] The Solana toolchain is corrupted. Run cargo-build-sbf with --force-tools-install
```
Caused by insufficient disk space during platform-tools extraction (~2 GB needed).
**Fix:** `cargo build-sbf --force-tools-install`. If root partition is small, symlink `~/.cache/solana/` to a larger disk.

### `feature edition2024 is required` (Cargo 1.84 / platform-tools v1.48)
Platform-tools v1.48 bundles `cargo 1.84.0` which does not support `edition = "2024"`. Pin known breaking crates:
```bash
cargo generate-lockfile
cargo update -p blake3          --precise 1.8.2
cargo update -p constant_time_eq --precise 0.3.1
cargo update -p base64ct        --precise 1.7.3
cargo update -p indexmap        --precise 2.11.4
```
**Always commit `Cargo.lock`** — this is the single most effective prevention.

### `No space left on device`
Solana CLI + platform tools need 2–5 GB. Clean old versions:
```bash
rm -rf ~/.local/share/solana/install/releases/<old_version>/
rm -rf ~/.cache/solana/
```

### `agave-install not found`
Anchor 0.31+ migrates to `agave-install` for Solana ≥1.18.19.
**Fix:** Install via `sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"`.

### `solana-test-validator` Crashes or Hangs
```bash
pkill -f solana-test-validator && rm -rf test-ledger/
```
Check ports: `lsof -i :8899`. Consider **Surfpool** as a modern alternative.

### LiteSVM `undefined symbol: __isoc23_strtol`
LiteSVM 0.5.0 npm binary requires GLIBC ≥2.38. On Debian 12 / Ubuntu 22.04 (GLIBC 2.36) it fails.
**Fix:** Use `solana-bankrun` instead — verified working on GLIBC 2.36:
```bash
pnpm remove litesvm anchor-litesvm
pnpm add -D solana-bankrun anchor-bankrun
```

## references/pinocchio.md

# Pinocchio Framework — Patterns, Security & API Reference
# Frank Castle — Safe Solana Builder
# Read this AFTER shared-base.md when the user selects Pinocchio.
# Every rule here is in addition to — not instead of — shared-base.md.

---

## Overview

Pinocchio is Anza's zero-dependency, zero-copy Solana framework. It treats incoming transaction data as a single byte slice, reading it in-place. It delivers 88–95% compute unit reduction and ~40% smaller binaries vs. Anchor. **It is unaudited — use with caution in production.**

### When to Use Pinocchio
- High-throughput programs (DEXs, orderbooks, games)
- Compute units are a bottleneck
- Maximum control over memory needed
- Building infrastructure (tokens, vaults, escrows)

### When to Use Anchor Instead
- Rapid prototyping / MVPs
- Team unfamiliar with low-level Rust
- Tight audit timeline (more auditors know Anchor)

---

## 1. PROJECT SETUP

```toml
[package]
name = "my-program"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]

[dependencies]
pinocchio        = "0.10"
pinocchio-system = "0.4"
pinocchio-token  = "0.4"
bytemuck         = { version = "1.14", features = ["derive"] }

[profile.release]
overflow-checks = true
lto             = "fat"
codegen-units   = 1
opt-level       = 3
```

---

## 2. PROGRAM STRUCTURE

```rust
use pinocchio::{
    account_info::AccountInfo, entrypoint,
    program_error::ProgramError, pubkey::Pubkey, ProgramResult,
};

entrypoint!(process_instruction);

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    match instruction_data.first() {
        Some(0) => initialize(accounts, &instruction_data[1..]),
        Some(1) => execute(accounts, &instruction_data[1..]),
        _ => Err(ProgramError::InvalidInstructionData),
    }
}
```

### Entrypoint Options (ordered by CU cost, cheapest last)

| Macro | Use When | Notes |
|---|---|---|
| `no_allocator!()` + `entrypoint!` | Statically-sized ops, no `String`/`Vec`/`Box` | Maximum CU savings |
| `lazy_entrypoint!` | Single-instruction programs | Defers account parsing until needed |
| `entrypoint!` | General use | Auto heap + panic handler |

---

## 3. ACCOUNT DEFINITIONS (BYTEMUCK — PREFERRED)

Use `bytemuck` for fixed-size accounts. Prefer over Borsh for zero-copy reads.

```rust
use bytemuck::{Pod, Zeroable};

pub const VAULT_DISCRIMINATOR: u8 = 1;

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Vault {
    pub discriminator: u8,
    pub owner: [u8; 32],
    pub balance: u64,
    pub bump: u8,
    pub _padding: [u8; 6],   // align to 8 bytes — always add padding
}

impl Vault {
    pub const LEN: usize = std::mem::size_of::<Self>();

    pub fn from_account(account: &AccountInfo) -> Result<&Self, ProgramError> {
        let data = account.try_borrow_data()?;
        if data.len() < Self::LEN { return Err(ProgramError::InvalidAccountData); }
        if data[0] != VAULT_DISCRIMINATOR { return Err(ProgramError::InvalidAccountData); }
        Ok(bytemuck::from_bytes(&data[..Self::LEN]))
    }

    pub fn from_account_mut(account: &AccountInfo) -> Result<&mut Self, ProgramError> {
        let mut data = account.try_borrow_mut_data()?;
        if data.len() < Self::LEN { return Err(ProgramError::InvalidAccountData); }
        Ok(bytemuck::from_bytes_mut(&mut data[..Self::LEN]))
    }
}
```

**Security notes:**
- Always check discriminator before trusting any field (see shared-base §1.4).
- Always check data length before casting.
- Add `_padding` to align structs to 8 bytes — misalignment causes UB with `bytemuck`.

---

## 4. ACCOUNT VALIDATION PATTERNS

### Pattern A — TryFrom (Recommended)

```rust
pub struct DepositAccounts<'a> {
    pub vault: &'a AccountInfo,
    pub owner: &'a AccountInfo,
    pub system_program: &'a AccountInfo,
}

impl<'a> TryFrom<&'a [AccountInfo]> for DepositAccounts<'a> {
    type Error = ProgramError;

    fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
        let [vault, owner, system_program, ..] = accounts else {
            return Err(ProgramError::NotEnoughAccountKeys);
        };
        if !owner.is_signer()    { return Err(ProgramError::MissingRequiredSignature); }
        if !vault.is_writable()  { return Err(ProgramError::InvalidAccountData); }
        if system_program.key() != &pinocchio_system::ID {
            return Err(ProgramError::IncorrectProgramId);
        }
        Ok(Self { vault, owner, system_program })
    }
}
```

### Pattern B — Validation Macros

```rust
macro_rules! require {
    ($cond:expr, $err:expr) => { if !$cond { return Err($err); } };
}
macro_rules! require_signer   { ($a:expr) => { require!($a.is_signer(),   ProgramError::MissingRequiredSignature) }; }
macro_rules! require_writable { ($a:expr) => { require!($a.is_writable(), ProgramError::InvalidAccountData) }; }
```

**Mandatory validation order (same as native — see native-rust.md §1.1):**
1. Key check (fixed accounts / sysvars)
2. Owner check — `account.owner() != &expected_program::ID`
3. Signer check — `account.is_signer()`
4. Writable check — `account.is_writable()`
5. Discriminator check — `data[0] != DISCRIMINATOR`
6. Field range validation

---

## 5. PDA OPERATIONS

```rust
// At init: find canonical bump, verify, and store
let (pda, bump) = Pubkey::find_program_address(
    &[b"vault", user.key().as_ref()],
    program_id,
);
if ctx.vault.key() != &pda { return Err(ProgramError::InvalidSeeds); }
vault.bump = bump;  // store for reuse

// On subsequent calls: re-derive from stored bump (cheaper)
let pda = Pubkey::create_program_address(
    &[b"vault", user.key().as_ref(), &[vault.bump]],
    program_id,
)?;
if ctx.vault.key() != &pda { return Err(ProgramError::InvalidSeeds); }
```

**Security notes (same as shared-base §2):**
- Never allow the user to supply a bump.
- Use `create_program_address` on subsequent calls — not `find_program_address`.
- Always include the user's pubkey in seeds for user-specific accounts.

---

## 6. CPI PATTERNS

### System Program

```rust
use pinocchio_system::instructions::{CreateAccount, Transfer};

// Create account
CreateAccount { from: payer, to: new_account, lamports, space, owner: &crate::ID }.invoke()?;

// Transfer SOL (PDA signer)
Transfer { from: vault_pda, to: destination, lamports: amount }
    .invoke_signed(&[&[b"vault", owner.as_ref(), &[bump]]])?;
```

### Token Program

```rust
use pinocchio_token::instructions::{Transfer, MintTo};

Transfer { source, destination, authority: owner, amount }.invoke()?;

MintTo { mint, token_account: dest, authority: mint_auth_pda, amount }
    .invoke_signed(&[&[b"mint_auth", &[bump]]])?;
```

### Custom CPI

```rust
use pinocchio::{instruction::{AccountMeta, Instruction}, program::invoke};

let ix = Instruction {
    program_id: &external_program_id,
    accounts: &[AccountMeta::new(*account.key(), false)],
    data: &instruction_data,
};
invoke(&ix, &[account])?;
```

**Security notes (same as shared-base §5):**
- Always verify external program IDs before invoking.
- Reload account data after any CPI that may have modified it — re-borrow and re-cast.
- Never use `invoke_signed` to elevate non-signer accounts.
- Pass only the accounts the callee needs.

---

## 7. DATA SERIALIZATION

| Method | Use When | Notes |
|---|---|---|
| `bytemuck` | Fixed-size structs (account state) | Zero-copy, fastest; preferred for on-chain account layout |
| `wincode` | Variable-size instruction data; foreign type adaptation | Anza-built, bincode-compatible, in-place; no intermediate buffers |
| `borsh` | Variable-size data where borsh compatibility is required | Allocates; use only when the wire format must be Borsh |
| Manual parsing | Maximum control / simple scalar types | Safe with explicit length checks |

**Manual parsing example (always bounds-check first):**
```rust
pub fn parse_u64(data: &[u8]) -> Result<u64, ProgramError> {
    if data.len() < 8 { return Err(ProgramError::InvalidInstructionData); }
    Ok(u64::from_le_bytes(data[..8].try_into().unwrap()))
}
```

---

## 7a. WINCODE — IN-PLACE SERIALIZATION / DESERIALIZATION

`wincode` is Anza's fast, bincode-compatible serializer that eliminates intermediate
staging buffers by writing directly into final memory destinations. It is the natural
complement to Pinocchio's zero-copy philosophy for **instruction data** and any
variable-size payloads.

> **Compatibility:** `wincode` produces the same bytes as `bincode` (default config)
> for all covered shapes — clients using `bincode` can talk to programs using `wincode`
> for deserialization, and vice versa.

### Cargo setup

```toml
[dependencies]
wincode = { version = "0.4", features = ["derive"] }
```

The `derive` feature enables the `SchemaWrite` and `SchemaRead` proc-macro derives.

### Basic usage

```rust
use wincode::{SchemaWrite, SchemaRead};

#[derive(SchemaWrite, SchemaRead)]
pub struct DepositArgs {
    pub amount:   u64,
    pub deadline: i64,
}

// --- Deserializing instruction data inside a Pinocchio handler ---
pub fn deposit(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
    // Always bounds-check before deserializing
    let args: DepositArgs = wincode::deserialize(data)
        .map_err(|_| ProgramError::InvalidInstructionData)?;

    // Use args.amount, args.deadline ...
    Ok(())
}

// --- Serializing a response / off-chain client ---
let args = DepositArgs { amount: 1_000_000, deadline: 1_700_000_000 };
let bytes = wincode::serialize(&args).unwrap();
```

### Zero-copy deserialization

For padding-free `#[repr(C)]` structs, `wincode` can deserialize **in-place**
(zero allocations, zero copies). This is the highest-performance path and pairs
perfectly with Pinocchio's ethos.

**Rules for zero-copy eligibility:**
- Struct must be `#[repr(C)]`
- No implicit padding (reorder fields or add an explicit `_padding` field — same
  rule as `bytemuck`)
- No tuples (Rust does not guarantee tuple layout)

```rust
use wincode::{SchemaWrite, SchemaRead, ZeroCopy};

// Reorder fields to eliminate implicit padding (u32 first, then u16, then u8s)
#[repr(C)]
#[derive(SchemaWrite, SchemaRead)]
pub struct SwapArgs {
    pub min_out:   u64,   // 8 bytes
    pub max_in:    u64,   // 8 bytes
    pub deadline:  i64,   // 8 bytes
    pub slippage:  u16,   // 2 bytes
    pub side:      u8,    // 1 byte
    pub _padding:  u8,    // explicit padding — aligns to 8 bytes total
}

pub fn swap(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
    // Zero-copy: no heap allocation, reads directly from the incoming byte slice
    let args: &SwapArgs = wincode::config::ZeroCopy::deserialize(data)
        .map_err(|_| ProgramError::InvalidInstructionData)?;

    // args is a reference into `data` — no copy made
    let _ = args.min_out;
    Ok(())
}
```

### Adapting foreign / third-party types

When a third-party type uses `serde` and visits bytes element-by-element,
`wincode` can wrap it with `Pod<T>` to read the whole field in one pass —
without changing the wire format:

```rust
use wincode::{SchemaWrite, SchemaRead, Pod};

// Suppose `ExternalPubkey` is defined in another crate with a slow serde impl
#[derive(SchemaWrite, SchemaRead)]
#[wincode(from = "ExternalInstruction")]
pub struct MyInstruction {
    pub recipient: Pod<ExternalPubkey>,  // reads 32 bytes in one pass
    pub amount:    u64,
}
```

### Pluggable length encoding (short-vec / compact-u16)

For instruction payloads that embed Solana's compact-u16 length prefix
(used in transaction wire format), enable the `solana-short-vec` feature
and switch the `SeqLen` encoder:

```toml
[dependencies]
wincode = { version = "0.4", features = ["derive", "solana-short-vec"] }
```

```rust
use wincode::{SchemaWrite, SchemaRead};
use wincode::config::ShortVec; // compact-u16 length prefix

#[derive(SchemaWrite, SchemaRead)]
pub struct InstructionWithAccounts {
    #[wincode(seq_len = "ShortVec")]
    pub accounts: Vec<[u8; 32]>,
    pub data:     u64,
}
```

### wincode vs. bytemuck — choosing the right tool

| Concern | `bytemuck` | `wincode` |
|---|---|---|
| **Fixed-size on-chain account state** | ✅ Ideal | Possible but unnecessary overhead |
| **Variable-size instruction data** | ❌ Not suitable | ✅ Ideal |
| **Zero allocations** | ✅ Always | ✅ With `ZeroCopy` config |
| **Wire format** | Raw memory layout | bincode (widely supported) |
| **Derive macros** | `Pod` + `Zeroable` | `SchemaWrite` + `SchemaRead` |
| **Foreign type adaptation** | ❌ Cannot | ✅ Via `Pod<T>` wrapper |
| **Padding required** | ✅ Yes — explicit `_padding` | ✅ Yes for zero-copy — same rule |

**Rule of thumb:**
- `bytemuck` for **account data** (fixed-size, on-chain state).
- `wincode` for **instruction data** and any variable-size or dynamic payloads.
- Never mix both in the same struct — pick one ownership boundary.

### Security notes specific to wincode

- **Always validate deserialized values** — `wincode` guarantees byte layout, not
  business logic. Check amounts, deadlines, and enum variants after deserialization.
- **Fail loudly on bad data** — map `wincode` errors to `ProgramError::InvalidInstructionData`,
  never to a silent default. Never `unwrap()` inside an instruction handler.
- **Do not trust client-supplied sizes** — `wincode` enforces a default max size for dynamic
  structures to prevent allocation exhaustion; do not override this limit without a clear
  upper bound justified in comments.
- **Padding field must be zeroed** — for zero-copy structs, initialize `_padding` to `[0u8; N]`
  on construction; stale bytes in padding fields can leak information across instructions.

---

## 8. IDL GENERATION (SHANK)

Pinocchio does not auto-generate IDLs. Use Shank:

```rust
use shank::{ShankAccount, ShankInstruction};

#[derive(ShankAccount)]
pub struct Vault { pub owner: Pubkey, pub balance: u64 }

#[derive(ShankInstruction)]
pub enum ProgramInstruction {
    #[account(0, writable, signer, name = "vault")]
    #[account(1, signer, name = "owner")]
    Initialize,
    #[account(0, writable, name = "vault")]
    #[account(1, signer, name = "owner")]
    Deposit { amount: u64 },
}
```

For client code generation, use **Codama** with the Shank-generated IDL.

---

## 9. PINOCCHIO SECURITY CHECKLIST

All rules from `shared-base.md` apply. Additional Pinocchio-specific checks:

- [ ] Struct padded to 8-byte alignment (`_padding` field added)
- [ ] Discriminator checked before any field access
- [ ] Account data length checked before `bytemuck::from_bytes`
- [ ] Canonical bump found at init, stored in account, reused with `create_program_address`
- [ ] Owner checked via `account.owner()` before deserializing
- [ ] After CPI: re-borrow and re-cast to get updated state (no `.reload()` — manual)
- [ ] Validation struct uses `TryFrom` or equivalent — no inline ad-hoc checks
- [ ] `overflow-checks = true` in `[profile.release]`
- [ ] No `unwrap()` / `expect()` in instruction handlers
- [ ] **[wincode]** `wincode` errors mapped to `ProgramError::InvalidInstructionData` — never `unwrap()`
- [ ] **[wincode]** Zero-copy structs use `#[repr(C)]` with explicit `_padding` zeroed on construction
- [ ] **[wincode]** Deserialized instruction values range-checked before use (amounts > 0, deadlines in future, etc.)
- [ ] **[wincode]** Max-size limit for dynamic `Vec` fields not overridden without a documented upper bound

---

## 10. COMMON PINOCCHIO BUILD ERRORS

Pinocchio uses `cargo build-sbf` — the same toolchain as native Rust. See `native-rust.md §10` for full error reference. Key points:

- **Platform tools corruption:** `cargo build-sbf --force-tools-install` (needs ~2 GB free in `~/.cache/solana/`)
- **edition2024 errors:** Pin `blake3 =1.8.2`, `constant_time_eq =0.3.1`, `base64ct =1.7.3`, `indexmap =2.11.4` and commit `Cargo.lock`
- **`cargo build-sbf` not found:** Install Solana CLI and add to PATH
- **LiteSVM on GLIBC <2.38:** Use `solana-bankrun` instead
- **Shank IDL generation:** `shank idl -o idl.json -p src/lib.rs`. For client code, pipe through **Codama**.
- **`wincode` proc-macro not found:** Ensure `features = ["derive"]` is set in `Cargo.toml` — the derive macros are feature-gated.
- **`wincode` zero-copy fails to compile:** Struct has implicit padding — reorder fields or add explicit `_padding: [u8; N]` until the compiler stops complaining.

## references/shared-base.md

# Shared Base — Solana Security Rules & Best Practices
# Frank Castle — Safe Solana Builder
# Applies to ALL Solana programs regardless of framework (Native Rust or Anchor).
# Claude: read every line of this file before writing any program code.

---

## 1. ACCOUNT & IDENTITY VALIDATION

These are the most exploited categories in Solana. Every account that enters your program is untrusted by default.

### 1.1 Signer Checks
- **Always verify `is_signer`** on every account that must authorize an action. An account being present in the accounts list does NOT mean it signed.
- Never treat account presence as authorization. Solana passes all accounts in a flat list — position alone means nothing.
- For authority-based operations (admin actions, user mutations), `is_signer` must be checked explicitly, every time.

### 1.2 Ownership Checks
- **Always verify `account.owner == expected_program_id`** before reading or trusting any account's data.
- An attacker can craft an account with identical data layout owned by a malicious program. If you skip the owner check, you'll read and act on spoofed data.
- The system program, token program, and your own program each have different owner IDs — never conflate them.

### 1.3 Account Data Matching (has_one / constraints)
- Validate that related accounts actually belong together. A user's vault PDA must match the pubkey stored in the user's state account — not just be a valid vault.
- Use `has_one` (Anchor) or manual pubkey comparison (native) to enforce these cross-account relationships.
- Example failure: accepting any token account for a "user's" withdrawal, not just the one registered to that user.

### 1.4 Type Cosplay Prevention (Discriminators)
- Every account type must have a unique **discriminator** (first 8 bytes in Anchor; manually managed in native).
- An attacker can pass an `Admin` account where a `User` account is expected if you don't check the discriminator.
- Always deserialize into the specific expected type and validate its discriminant before trusting any fields.

### 1.5 Reinitialization Attacks
- **Verify an account has not already been initialized before running setup logic.**
- If `initialize` can be called twice, an attacker can overwrite the authority field and hijack the program.
- Use a dedicated `initialized: bool` flag, or rely on Anchor's `init` constraint which prevents reinit by checking account discriminator and ownership.

### 1.6 Writable Checks
- Only accounts explicitly marked as writable (`is_writable`) should be modified.
- Never modify an account not marked writable — doing so will cause a runtime error at best, and a silent corruption at worst in earlier runtime versions.
- `is_signer` and `is_writable` are per-transaction, not per-instruction. Never assume they differ across instructions in the same transaction.

---

## 2. PDA (PROGRAM DERIVED ADDRESSES) SECURITY

PDAs are the backbone of Solana state. A poorly designed PDA is a permanently exploitable backdoor.

### 2.1 Canonical Bumps Only
- **Always use `find_program_address`** to find the canonical (highest valid) bump.
- Never allow a user to supply an arbitrary bump seed. An attacker can pre-mine a bump that results in the same address as another account.
- **Store the canonical bump** in the account's data after creation and **reuse it** on subsequent calls — never brute-force it again on every invocation.
- Use `create_program_address` (with the stored bump) for re-derivation, not `find_program_address`, to save compute.

### 2.2 PDA Sharing Prevention
- Seeds must be specific enough that one PDA can never serve two different users or purposes.
- **Always include the user's `Pubkey` in seeds** for any user-specific state. Example: `[b"vault", user.key().as_ref()]`.
- A shared PDA means one user can affect another's state — this is a critical vulnerability.

### 2.3 Seed Collision Prevention
- Use **unique string prefixes** for different PDA types: `b"vault"`, `b"user_state"`, `b"config"`, etc.
- Seeds `["AB", "C"]` and `["A", "BC"]` produce the **same PDA** — this is a known footgun with concatenated seeds.
- Always use fixed-length seeds or canonical delimiters when seed components come from user input.
- Never assume a PDA derived from user-provided seeds is unique unless you fully control and validate the seed composition.

### 2.4 PDA Purpose Isolation
- Never use a single PDA across multiple logical domains or external programs.
- Each distinct capability (vault, escrow, config, staking position) must use a distinct PDA with distinct seeds.

---

## 3. ARITHMETIC & LOGIC SAFETY

A single overflow or division-before-multiplication can drain a protocol.

### 3.1 Checked / Saturating Math — No Exceptions
- **Never use standard `+`, `-`, `*` operators on financial values.** They will panic (debug) or silently overflow (release).
- Use `.checked_add()`, `.checked_sub()`, `.checked_mul()`, `.checked_div()` and propagate errors with `?`.
- Use `.saturating_sub()` only when a floor of zero is semantically correct (e.g., health factors).
- Treat every arithmetic error as a program bug, not a user error — return a descriptive custom error.

### 3.2 Multiply Before Divide
- **Always perform all multiplications first, then divide last.** Integer division truncates — doing it early loses precision permanently.
- Wrong: `(amount / total_supply) * price`
- Right: `(amount * price) / total_supply`

### 3.3 Price Slippage Checks
- In any function involving pricing, swapping, or purchasing: **require an `expected_price` or `min_amount_out` argument from the user.**
- Without this, MEV bots can manipulate price between submission and execution. The user's transaction lands at a worse price than intended.
- Reject the transaction if the actual price deviates beyond the user-supplied tolerance.

### 3.4 Lamport Balance Invariant
- After every instruction, **the total lamports across all accounts must remain equal.** Never create or destroy lamports — only redistribute.
- When closing an account, the rent-exempt lamports must go to a **trusted destination** (the original initializer or a program-controlled account). Never allow arbitrary destinations — this enables "rent stealing."
- Manually verify lamport math when implementing custom close or de-listing logic.

---

## 4. DUPLICATE MUTABLE ACCOUNT ATTACKS

Passing the same account twice for two different roles is a classic exploit vector.

### 4.1 The Attack
- If your instruction takes `account_a` (source) and `account_b` (destination) and an attacker passes the same account for both, your state writes will conflict. The last write wins (in Anchor, the last serialized field). The net effect is often a free "transfer" to self that bypasses balance checks.

### 4.2 Prevention
- **Always add a constraint ensuring two mutable accounts that must be distinct are actually distinct:**
  ```
  constraint = account_a.key() != account_b.key()
  ```
- If your logic updates different fields of the same account through two references, merge them into a single reference to ensure atomic state updates.
- Ask yourself for every pair of mutable accounts: *"What happens if an attacker passes the same account for both?"*

---

## 5. CROSS-PROGRAM INVOCATIONS (CPI) SAFETY

CPI is the most complex attack surface in Solana. Every CPI is a trust boundary.

### 5.1 Validate Program IDs — No Arbitrary CPI
- **Never invoke a program address provided by the user without verification.** An attacker will pass a malicious program that mimics success responses.
- For well-known programs (System, Token, Token-2022): hardcode their IDs and compare.
- For dynamic programs: check the provided address against a trusted allowlist stored in your program's state account.
- If using `AccountInfo` for the program account: `require_keys_eq!(cpi_program.key(), expected_program::ID)`.

### 5.2 Reload Stale Data After CPI
- **After any CPI that modifies a shared account, reload the account data before using it again.**
- Your in-memory deserialized struct does not update automatically when the on-chain state changes via CPI.
- Missing a reload means you're making decisions on stale balances or state — a classic logic error.

### 5.3 Signer Pass-Through Sanitization
- Any account marked as a signer in your current transaction **remains a signer** in CPIs you make.
- Before passing accounts into an external CPI call, iterate through them and verify `!account.is_signer` unless that privilege is explicitly required.
- Use **account isolation**: derive user-specific PDAs so a compromised CPI signer only has authority over one user's "blast radius," not the entire protocol.

### 5.4 SOL Balance Checks Around CPI (Slippage for SOL)
- Solana has no `msg.value` equivalent — a callee can spend SOL from a signing account.
- Record the signer's balance **before** the CPI: `let balance_before = ctx.accounts.signer.lamports();`
- After the CPI, verify: `require!(balance_before <= balance_after + max_spendable, ErrorCode::ExcessiveSpend);`

### 5.5 Post-CPI Ownership Verification
- An attacker-controlled program can use the `assign` instruction to change an account's owner during a CPI.
- **After any CPI involving an account you care about, verify the owner is still the expected program.**
- `require_keys_eq!(account.owner, &system_program::ID)` (or your program's ID as appropriate).

### 5.6 CPI Return Values — Always Propagate Errors
- **Always wrap CPI calls with the `?` operator** to ensure that if the inner call fails, the entire transaction reverts.
- Never call a CPI and discard its result. Be aware that some programs return "Success" even if their internal conditional logic (like a guarded transfer) did not execute.

### 5.7 invoke vs invoke_signed
- **Prefer `invoke` over `invoke_signed`** wherever possible. Only use `invoke_signed` when a PDA must sign.
- With `invoke_signed`, only extend signer privileges to accounts that are already signers in the current instruction — never elevate non-signers.
- Minimize the accounts passed to any CPI call — pass only what is required, nothing more.

### 5.8 Architecture: Defense-in-Depth
- **Avoid a single "Global Vault" PDA for all users.** If exploited, all user funds are at risk.
- Use **user-specific PDAs for deposits.** A CPI exploit then drains only the affected user's funds — not the entire protocol.

---

## 6. ACCOUNT STORAGE & LIFECYCLE

### 6.1 Storage Rules
- Never store program state in the program account itself. Always use separate data accounts.
- Always set the `owner` field of state accounts to your program's address. This is your primary access control for account data.
- Never allow an account's data to be modified by a program that does not own it.
- Never allow accounts to exceed **10 MiB** of data. Never allow total per-transaction resize to exceed **20 MiB**.

### 6.2 Rent Exemption
- **Always fund new accounts with at least two years' worth of rent** (the rent-exempt threshold).
- Never leave an account in the `0 < balance < minimum_balance` range — it becomes eligible for garbage collection.

### 6.3 Account Closing (Anti-Revival)
- **Never close an account by only draining its lamports.** The account can be "revived" by refunding its rent.
- Proper close sequence:
  1. Set all data bytes to zero (`memset` / `fill(0)`)
  2. Transfer all lamports to the recipient
  3. Transfer ownership back to the System Program
- The destination for rent lamports must be a **trusted address** (original initializer or a controlled account) — never arbitrary.

### 6.4 Sysvar Verification
- When reading from a sysvar (Clock, Rent, SlotHashes, etc.), always verify the account's public key matches the known sysvar address.
- Never trust a sysvar account passed by the user without verification. (The Wormhole hack involved sysvar spoofing.)

---

## 7. TOKEN-2022 COMPATIBILITY

Mixing legacy token functions with Token-2022 mints causes silent DoS.

- **Never use `anchor_spl::token::transfer` (or its native equivalent) for programs that may encounter Token-2022 mints.**
- It hardcodes the legacy Token Program ID and will fail or misbehave with Token-2022 accounts.
- **Always use `transfer_checked`** and the interface-aware versions that dynamically detect the correct program.
- Always provide the `mint` account and `decimals` in transfers — required by `transfer_checked`.
- Token-2022 features (transfer hooks, confidential transfers, interest-bearing tokens) have **expanded attack surface** — flag them in the security checklist for extra manual review.

---

## 8. TRANSACTION MODEL SAFETY

### 8.1 Atomicity
- Compose multiple operations into a single transaction when you need all-or-nothing guarantees.
- Solana's transaction atomicity means either all instructions succeed or all revert — design your program to take advantage of this.

### 8.2 Compute Budget
- Never assume a transaction will succeed past compute budget limits.
- For complex instructions, use `SetComputeUnitLimit` and budget compute units accordingly.
- Unbounded loops over `remaining_accounts` or variable-length collections are a compute DoS vector.

### 8.3 Address Lookup Tables
- Never include signer accounts in an Address Lookup Table. Signer pubkeys must always be inline in the transaction.

### 8.4 Durable Nonces
- Always place `AdvanceNonceAccount` as the **first instruction** in the transaction.
- Never use a nonce account whose blockhash is already recent — this defeats its purpose.

---

## 9. SAFE RUST PATTERNS

### 9.1 Vector Initialization
- To declare a vector of length `N` filled with zeros: use `vec![0; N]` **(semicolon)**.
- **Never use `vec![0, N]` (comma)** — this creates a two-element vector `[0, N]`, not N zeroes. Accessing index 2+ will panic.

### 9.2 Avoid Unsafe Rust
- Unless absolutely necessary for performance, stay within safe Rust.
- The Rust compiler's memory protections are your last line of defense against memory corruption bugs.
- Every `unsafe` block requires an explicit justification comment.

### 9.3 Handle `remaining_accounts` With Full Rigor
- If you iterate over `ctx.remaining_accounts`, apply the **same ownership, signer, and type checks** as you do for named accounts.
- `remaining_accounts` is the easiest place to inject malicious accounts because developers assume they've already been validated.

---

## 10. THE CURIOSITY PRINCIPLE (Mindset)

Security is not a static checklist — it is an adversarial mindset applied at design time.

For every account input in your program, ask:
1. **"What happens if I pass the same account twice?"** → Duplicate mutable account attack.
2. **"What happens if this account is owned by a different program?"** → Type cosplay / ownership bypass.
3. **"What happens if this is a Token-2022 mint instead of a legacy mint?"** → DoS / wrong program invoked.
4. **"What happens if the CPI I'm calling returns success but didn't actually do anything?"** → Silent logic failure.
5. **"What happens if an attacker passes a valid-looking but malicious program ID?"** → Arbitrary CPI.
6. **"What's the worst-case scenario if this account's bump is not canonical?"** → PDA collision.

Apply this curiosity to every design decision, not just during code review.

---

## 11. ORACLE VALIDATION

- **Validate oracle confidence interval**: reject prices where `conf / price` exceeds a configurable threshold (e.g., 2–5%). Wide confidence means the price is unreliable — acting on it enables oracle manipulation.
- **Check staleness**: verify the price timestamp is within a configurable max age. Never use a stale feed.
- Make confidence and staleness thresholds admin-configurable, not hardcoded.
- **Never use the current oracle price retroactively for settled positions.** Store the reference price at action time (borrow, deposit) in the account data and use it at settlement — not the live price.

---

## 12. FEE COMPLETENESS

- Apply all fees to **every code path** — redemption, withdrawal, single-asset, multi-asset. Fee bypasses on edge-case routes are a consistent source of protocol drain.
- Deduct fees from tracked totals (collateral value, pool balance) **atomically with the principal deduction** — never in a separate step that can be skipped or reordered.
- Use a **consistent amount** (pre-fee or post-fee) for both capacity checks and execution. Mixing them causes overfills or incorrect limit-order behavior.
- Apply fee calculations to the **input token** unless the protocol explicitly specifies output-side fees.

---

## 13. TOKEN DUST & TIME-LIMITED ACCOUNT DoS

> Account close sequence (zero → lamports → assign to system program) is in §6.3. This section covers dust and lifecycle timing.

- **Before closing any token account, sweep or burn the residual balance.** An attacker can deposit a dust amount to make `close` permanently fail (account poisoning DoS). Never assume balance is zero.
- After any transfer, reload and verify the account balance to detect unexpected deposits.
- Define a dust threshold. Either sweep dust to the protocol treasury or reject operations where remaining amount is below threshold. Never let dust block a settlement or close.
- **Close time-limited accounts (offers, escrows, locks) at expiry.** Leaving expired accounts open leaks rent and enables griefing. Allow anyone — not just the creator — to trigger closure after expiry.
- Avoid `init_if_needed` for accounts an adversary can pre-initialize with harmful state (also in anchor.md §2.4). Use `init` for one-time initialization.

---

## 14. STATE MANAGEMENT — COUPLED FIELDS & COUNTERS

- Reset **all logically coupled fields atomically** in completion and close paths. Never leave a derived field (e.g., `shares_pending`, `rewards_owed`) non-zero after its parent quantity is zeroed. Inconsistent state breaks protocol invariants permanently.
- When migrating positions, transfer **pending (locked)** and **withdrawable (matured)** balances as separate quantities. Never merge them or reapply a lockup to already-unlocked amounts.
- Update all counters and statistics **atomically with the operation that triggers them** (fill count, volume, total supply). A counter that drifts out of sync is a protocol invariant violation and a potential exploit surface.

---

## 15. SHARED POSITION & POOL LOGIC

- Before transferring shares or liquidity between positions, **preprocess both source and destination** (settle pending fees, snapshot reward accumulators). Skipping the destination lets a user claim fees they never earned, potentially draining the pool.
- Never allow a no-op or self-transfer pattern to inflate fee claims. Verify `source != destination` before any share movement (also see §4 on duplicate accounts).
- If directional fee asymmetry (buy vs. sell) is intentional, document and test it explicitly. If symmetry is required, apply fees on the input side for both directions.

---

## 16. CLOCK & TIMING

- Use a **single canonical time unit** (slots *or* seconds) throughout all time-dependent logic. Mixing units silently corrupts comparisons — a vesting window in seconds compared to raw slots can unlock 4× earlier than intended.
- When comparing durations across unit boundaries, apply the correct scale factor explicitly (e.g., multiply slot count by `SLOTS_PER_SECOND` before comparing to a seconds-based deadline).
- Annotate time fields with their unit in code (`vesting_end_slot: u64`, `unlock_timestamp_secs: i64`) to prevent silent misuse as code evolves.

---

## 17. TOKEN / MINT INTEGRITY

- Assert that the **mint close authority is `None`** during mint initialization. A mint with a close authority can be closed and re-initialized at the same address with different decimals, silently breaking all downstream accounting.
- Store immutable mint properties (decimals, supply cap, authorities) at account creation. Re-validate them on **every instruction** that depends on them — do not assume they cannot change between calls.
- Never allow a reinitialized account at a recycled address to inherit state from its previous lifetime. Validate all fields as if the account is fresh.

---

## 18. INPUT VALIDATION — PROTOCOL-LEVEL

> Data length and instruction data validation are in native-rust.md §2 / anchor.md §1. This section covers protocol-semantic validation.

- Validate token mints against a protocol allowlist or framework constraints (`mint::authority`, `mint::decimals`). An unconstrained mint allows arbitrary tokens to be injected into protocol flows.
- Reject same-asset operations where distinct assets are required: `require!(input_mint != output_mint)`. Same-token operations can be exploited to manipulate fee accounting or pool invariants.
- Enforce maximum sizes on variable-length inputs (messages, payloads, URIs) **before encoding**. Unbounded inputs cause compute overruns and silent log truncation.
- Verify protocol-owned addresses (fee recipients, config accounts) are the expected, constrained accounts **before updating them**. An unconstrained update enables fee redirection to attacker-controlled accounts.

---

## 19. TYPE NARROWING & INTEGER SAFETY

> Checked arithmetic and multiply-before-divide are in §3. This section covers type conversion safety.

- Keep numeric types **consistent across instruction params, on-chain state, and emitted events**. Never silently narrow a wider integer type (e.g., `u64 → u32`). On-chain state and events diverge, breaking auditability.
- Before any narrowing cast, assert an explicit upper-bound: `require!(val <= u32::MAX as u64, ErrorCode::Overflow)`.
- Validate all amounts at **instruction entry** (`> 0`, within protocol min/max bounds) before passing them into math helpers. Deep validation catches bugs late and produces confusing error codes.

---

## 20. EVENT LOGGING

- Keep individual log messages concise. Solana truncates transaction logs at ~10 KB per transaction — long free-form strings are silently dropped mid-audit trail.
- Emit critical state (amounts, authorities, timestamps, before/after balances) as **structured, fixed-size on-chain events** — not free-form strings.
- Never rely solely on logs for auditability. Persist critical state in on-chain accounts — logs are ephemeral and truncatable by the runtime.

---

## 21. REWARD ACCOUNTING — PROPORTIONAL SCALING & DEBT SETTLEMENT

Reward math is the single most exploited category in staking and yield protocols. Every pattern below maps to a validated Critical or High finding.

### 21.1 Settle Before Shrinking (Rounding Mismatch in Partial Unstake)
- **Never scale `reward_debt` proportionally when reducing a position without settling first.**
- Independent floor divisions create a gap: `attacker calls partial_unstake(1) + claim_rewards` in a loop to manufacture rewards with zero new time elapsed.
- Fix: settle all pending rewards (compute `pending = accrued - reward_debt`, pay out) **before** shrinking the position, then reset `reward_debt` to a fresh checkpoint against the new, smaller principal.

### 21.2 Reward Debt Must Be Updated on Every Payout Path — No Exceptions
- If `claim_rewards` subtracts `reward_debt` but `unstake_locked` does not, a user who claims then unstakes receives the same rewards twice.
- **Every instruction that pays out rewards must follow the same formula:** `pending = total_accrued - reward_debt`, pay `pending`, then set `reward_debt = total_accrued`.
- Audit every code path that touches balances: claim, unstake, withdraw, liquidate, emergency exit. Missing even one is a Critical.

### 21.3 Never Retroactively Apply a Changed Rate
- Never store a single mutable global `reward_rate` and multiply it by `total_elapsed` across the full duration.
- When `update_reward_rates` overwrites the rate, every rate change retroactively alters all existing positions — attackers front-run rate increases to steal yield.
- Use one of:
  - **Per-position rate snapshots**: store `rate_at_stake_time` in the position account, compute rewards against that.
  - **Global accumulator pattern**: maintain `reward_per_token_stored` (updated atomically on every rate change or interaction), store `reward_per_token_paid` per position; `pending = (reward_per_token_stored - reward_per_token_paid) * position_size`.

### 21.4 Dead Share Price — Yield Must Update the Exchange Rate
- In share-based (stX/totalStaked) models, there must be a code path that increases the accounting numerator (`total_staked`) independently of the share supply.
- If yield enters the vault but the exchange rate variable is never updated, the share price is permanently frozen — new depositors receive the same shares as if no yield had accumulated.
- Any yield accrual instruction must call `total_staked = total_staked.checked_add(yield_amount)?` before any share math.

### 21.5 Inflation Attack / First Depositor
- In share-based pools, the first depositor can stake a dust amount, burn most receipt tokens directly via SPL, inflate the exchange rate, then steal from subsequent depositors via rounding.
- **Fix (require one of):**
  - Mint dead shares to a burn address on the first deposit (e.g., 1000 locked shares).
  - Enforce a minimum initial deposit large enough to make the inflation attack economically infeasible.
  - Use virtual balances: add a virtual offset to both numerator and denominator before computing shares.

### 21.6 Fee-on-Transfer Delta Accounting (Token-2022)
- When using Token-2022 mints with transfer fees, the vault receives `amount - fee` but a naive implementation records `amount`.
- **Always use balance-delta accounting:**
  ```rust
  let before = ctx.accounts.vault.amount;
  token_interface::transfer_checked(cpi_ctx, amount, decimals)?;
  ctx.accounts.vault.reload()?;
  let actual_received = ctx.accounts.vault.amount.checked_sub(before).ok_or(ErrorCode::Arithmetic)?;
  // Use actual_received for all state updates — never `amount`
  ```
- Call `reload()` after every CPI before reading any account field.

### 21.7 Rewards Must Come from a Funded Reward Source — Not Principal
- If reward payouts are sourced from the same vault that holds user principal, the protocol is structurally insolvent from the first claim — rewards come from other users' deposits.
- Rewards must come from a **dedicated rewards vault**, a funded reserve, or an external yield source.
- At `initialize` time, assert that a rewards vault exists and is sufficiently funded for the program's stated duration.
- Add a `check_solvency` view instruction that clients can call before staking.

---

## 22. VAULT & POOL ARCHITECTURE — WITHDRAWAL PATHS & SOLVENCY

### 22.1 Every PDA-Controlled Vault Must Have a Withdrawal Path
- If an instruction creates a PDA-controlled token vault (e.g., donation vault, insurance fund, fee accumulator), there must be a corresponding instruction to withdraw from it.
- Without a withdrawal path, tokens sent to that vault are permanently locked with no recovery mechanism.
- Before shipping: trace every token flow into PDA-controlled accounts and confirm a corresponding outflow instruction exists with appropriate access control.

---

## 23. TOKEN-2022 EXTENSION VALIDATION AT INITIALIZATION

Accepting an arbitrary Token-2022 mint without extension whitelisting opens critical attack surface.

### 23.1 Validate Extensions at `initialize` — Reject Dangerous Ones
At program initialization (or when a new mint is registered), validate the mint's Token-2022 extensions:

- **Reject `PermanentDelegate`**: a permanent delegate can seize tokens from any account associated with the mint — including your vault. This is a complete vault drain vector.
- **Reject uncontrolled `FreezeAuthority`**: an external freeze authority can freeze your vault's token account, permanently DoS-ing withdrawals.
- **Require `TransferHook`-compatible CPI patterns**: if the mint uses a transfer hook, your `transfer_checked` CPI must forward `remaining_accounts` containing the hook's accounts. Skipping this causes the CPI to fail silently or revert.
- **Reject `ConfidentialTransfers`** unless your program explicitly handles the confidential transfer protocol.

```rust
// Example: reject PermanentDelegate at init
let mint_data = ctx.accounts.staking_mint.to_account_info();
if let Ok(Some(_)) = get_extension::<PermanentDelegate>(&mint_data.data.borrow()) {
    return Err(ErrorCode::UnsupportedMintExtension.into());
}
```

- Maintain an explicit **extension allowlist** in your program's config: only mints with approved extension sets can be registered.

---

## 24. ACCESS CONTROL — LOCKUP ENFORCEMENT & ADMIN KEY ROTATION

### 24.1 Lockup Must Be Enforced on ALL Reward Claim Paths
- If a locked staking protocol offers both `claim_rewards` (mid-lockup, yield only) and `instant_unlock` (no yield, principal only), the `claim_rewards` instruction must explicitly enforce lockup expiry.
- Without this check, users collect full yield then instantly exit — the lockup incentive mechanism is entirely bypassed.
- Fix: add `require!(clock.unix_timestamp >= entry.unlock_at, ErrorCode::LockupNotExpired)` to **every** yield-paying instruction, not just `unstake`.

### 24.2 Admin Key Must Be Rotatable (Two-Step Pattern)
- A single immutable admin key is a permanent single point of failure. Key compromise = full protocol takeover with no recovery.
- **Always implement a two-step rotation:**
  ```rust
  // Step 1: current admin proposes a new admin
  pub fn propose_admin(ctx: Context<ProposeAdmin>, new_admin: Pubkey) -> Result<()> { ... }
  // Step 2: new admin accepts (proves key control)
  pub fn accept_admin(ctx: Context<AcceptAdmin>) -> Result<()> { ... }
  ```
- Store `pending_admin: Option<Pubkey>` in your config account.
- For 🔴 Critical programs: wrap admin key rotation in a timelock (e.g., 48-hour delay before acceptance is valid).

---

## 25. BPF RUNTIME — STACK FRAME LIMIT

### 25.1 Stack Frame Hard Limit: 4096 Bytes
- The BPF VM enforces a hard **4096-byte stack frame limit** per instruction invocation.
- Anchor instruction contexts with 6+ `InterfaceAccount` or `Account` fields — especially alongside large state accounts — can exceed this limit, causing **runtime access violations** (not compile-time errors).
- This is a complete DoS: the instruction always reverts. There is no graceful degradation.

**Detection:** After `anchor build`, check for:
```
Stack offset of XXXX exceeded max offset of 4096 by YYY bytes
```
Treat this as a hard blocker — it must be resolved before deployment.

**Fix:** Wrap large account fields in `Box<>` to move them from the stack to the heap:
```rust
// Before (stack allocated — dangerous with many accounts)
pub vault: Account<'info, VaultState>,

// After (heap allocated — safe)
pub vault: Box<Account<'info, VaultState>>,
```
- Apply `Box<>` to the largest account types first (`InterfaceAccount<Mint>`, `InterfaceAccount<TokenAccount>`, large custom state accounts).
- For Native Rust / Pinocchio: avoid large local variable declarations inside instruction handlers; pull complex structs behind references or allocate on the heap explicitly.

