Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/instructions/security.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Anchor / Solana Rust Best Practices

Purpose: enforce project-wide on-chain best practices for Anchor + Solana Rust code. These guidelines are for authors and reviewers and should be followed for any change that touches `programs/`.

Scope: applies to all on-chain Rust code in `programs/` and tests that exercise on-chain logic. This file is not prescriptive about off-chain tooling except where it affects on-chain safety (for example, tests and CLIs).

## Arithmetic

- Never use raw `+`, `-`, `*`, `/` for financial or state math. Use checked ops: `checked_add`, `checked_sub`, `checked_mul`, `checked_div`, `checked_pow`.
- Convert checked failures into program errors (return `err!()`), never `unwrap()`.
- Use wider intermediate types (e.g., `u128`) when multiplying to prevent overflow; multiply before divide to reduce precision loss.
- Do not use floats on-chain. Use fixed-point integer math and basis points for fractions.
- Enable overflow checks in release builds (add `overflow-checks = true` under `[profile.release]` in `Cargo.toml` or enable in CI).

## Error handling

- Never use `unwrap()` or `expect()` in program code.
- Use Anchor helpers and explicit propagation: `ok_or(...) ?`, `require!()`, `require_eq!()`, `require_keys_eq!()`, `err!()`.
- Define explicit `#[error_code]` enums in `error.rs` for all domain failures.

## Account security

- Validate every account for ownership, PDA seeds, signer flags, authority, mint association, and token owner where relevant.
- Prefer Anchor account constraints over raw `AccountInfo` where possible. Use `Program<'info, Token>` or `InterfaceAccount` types instead of unchecked access.
- Never trust client-side validation; enforce invariants server-side.

## PDA / authority design

- Prefer PDA authorities over ephemeral wallet authorities for long-lived controllers.
- Store seed constants in `constants.rs` and reuse them.
- Verify signer seeds explicitly when performing CPI with `invoke_signed`.
- Design seed namespaces to avoid collisions; document seed choices in code and docs.

## State management

- Minimize `mut` accounts in hot/execute paths.
- Keep account structs small and use fixed-size fields; avoid unbounded `Vec` on-chain.
- Use explicit `space` and `INIT_SPACE` constants; include an account `version` field to support migrations.
- Prevent accidental reinitialization: prefer guarded `init` flows over `init_if_needed` unless fully audited.

## Program structure

- Keep instructions single-purpose and small. Separate phases clearly:
1. Validation (read-only checks)
2. Computation (pure logic)
3. State mutation (writes)
4. Token transfers / CPI
- Emit events for critical state changes using `#[event]`.
- Avoid deep CPI chains; prefer small, auditable calls.
- Organize code into `instructions/`, `state/`, `errors.rs`, `events.rs`, `constants.rs`, and `utils/`.

## Testing

- Test failure paths more than happy paths. Required tests include:
- overflow/underflow
- wrong PDA
- wrong signer
- wrong mint
- replay attacks
- zero and max value edge cases
- unauthorized access
- duplicate accounts and re-init guards
- Keep test helpers minimal and deterministic; prefer explicit airdrops and deterministic keypairs.

## Rust safety

- Use explicit numeric types and avoid `panic!` in programs.
- Minimize `unsafe` usage; prefer `?` for error propagation.
- Use `unwrap()` only in tests where failure is provably impossible.

## Compute / performance

- Avoid excessive logging in hot paths.
- Avoid unnecessary `mut` or large account reallocations on transfers.
- Use checked arithmetic but be mindful of compute cost; refactor heavy math into fewer operations.

## Security mindset checklist

Add these as comments in each instruction's handler as applicable:

- Can signer privileges be spoofed? Are signer keys asserted against owners?
- Can accounts be substituted or reordered by a caller? Are seeds and ownership checked?
- Can arithmetic overflow or underflow? Are intermediate widths chosen safely?
- Can state desync occur across CPIs or upgrades? Are versions present?
- Can token accounts be swapped to change semantics? Are mints/owners validated?
- Can this be replayed or frontrun? Are idempotency and ordering handled?

## Common good patterns

- Use `require!` macros liberally to encode invariants.
- Emit events for indexers and monitoring.
- Prefer deterministic, explicit error codes for calling clients.

## Common bad patterns (avoid these)

- `unwrap()` / `expect()` in program code
- Floating point math on-chain
- Unchecked `AccountInfo` access without Anchor constraints
- `init_if_needed` without authority and size guards
- Giant monolithic instructions with many responsibilities

## Implementation notes

- Add a `CONTRIBUTING.md` or incorporate these points into `README.md` for reviewers.
- Run `cargo test` and `anchor test` in CI; add lint/format checks.


178 changes: 178 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: CI

on:
pull_request:
branches: [ master ]

# Cancel in-flight runs for the same PR
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
# ── 1. Build & unit-test the Rust workspace ──────────────────────────────
# Produces a warmed cargo registry cache that every downstream job reuses.
cargo-test:
name: Rust tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-

- run: cargo test --workspace --verbose

# ── 2. Anchor integration tests ──────────────────────────────────────────
# Runs right after cargo-test — does NOT wait for security-scan or sdk-tests.
# Those are correctness/hygiene checks that don't gate integration testing.
anchor-test:
name: Anchor integration tests
runs-on: ubuntu-latest
needs: cargo-test
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: actions/setup-node@v4
with:
node-version: 20

# Reuse the warmed cargo registry from cargo-test
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-

# Agave/Solana CLI — pinned to exact version, cached by version string
- uses: actions/cache@v4
id: cache-solana
with:
path: ~/.local/share/solana
key: ${{ runner.os }}-agave-v3.1.10

- name: Install Agave/Solana CLI
if: steps.cache-solana.outputs.cache-hit != 'true'
run: curl -sSfL --retry 5 https://release.anza.xyz/v3.1.10/install | sh

- name: Add Solana to PATH
run: echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH

# Anchor CLI — prebuilt binary, ~5 seconds vs ~8 minutes from source
- uses: actions/cache@v4
id: cache-anchor
with:
path: ~/.cargo/bin/anchor
key: ${{ runner.os }}-anchor-v1.0.2

- name: Install Anchor CLI
if: steps.cache-anchor.outputs.cache-hit != 'true'
run: |
curl -sSfL https://github.com/otter-sec/anchor/releases/download/v1.0.2/anchor-1.0.2-x86_64-unknown-linux-gnu \
-o ~/.cargo/bin/anchor
chmod +x ~/.cargo/bin/anchor

# JS deps — root workspace (yarn.lock at repo root)
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-yarn-root-${{ hashFiles('yarn.lock') }}

- name: Install root JS deps
run: yarn install --frozen-lockfile

# Keypair for the test validator
- name: Generate test keypair
run: |
mkdir -p ~/.config/solana
solana-keygen new --no-bip39-passphrase --outfile ~/.config/solana/id.json

- name: Show versions
run: |
solana --version
anchor --version
node --version

- name: Run anchor test
run: anchor test --validator legacy

# ── 3. SDK unit tests ────────────────────────────────────────────────────
# Runs in parallel with anchor-test — no Rust needed here at all.
sdk-tests:
name: SDK unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20

- uses: actions/cache@v4
with:
path: sdk/node_modules
key: ${{ runner.os }}-yarn-sdk-${{ hashFiles('sdk/yarn.lock') }}

- name: Install SDK deps
working-directory: sdk
run: yarn install --frozen-lockfile

- name: Run SDK tests
working-directory: sdk
run: yarn test

# ── 4. Security scans ────────────────────────────────────────────────────
# Runs in parallel with everything — purely informational, never blocks merge.
security-scan:
name: Security & dependency scans
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-

- name: Install cargo-audit
run: cargo install cargo-audit || true

- name: Run cargo audit
run: cargo audit || true

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Audit root deps
run: |
if [ -f yarn.lock ]; then
yarn install --frozen-lockfile
yarn audit || true
fi

- name: Audit SDK deps
working-directory: sdk
run: |
if [ -f yarn.lock ]; then
yarn install --frozen-lockfile
yarn audit || true
fi
7 changes: 6 additions & 1 deletion Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package_manager = "yarn"
resolution = true
skip-lint = false



[programs.localnet]
jetty = "ACaZ8PavSWsp5vaQgvjH5zhkTU6oWzCMo8SNAfJY5Bks"
jetty = "4DcxDMd7iFppUn6aGkuJY3xNaF9FFNduchqByYmXiKku"

[provider]
cluster = "localnet"
Expand All @@ -15,4 +17,7 @@ wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn test"

[test]
startup_wait = 10000

[hooks]
Loading
Loading