Skip to content
Open
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.


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

on:
pull_request:
branches: [ master ]

jobs:
cargo-test:
name: Rust tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

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

- name: Run cargo tests
run: |
cargo test -p jetty --workspace --verbose

anchor-test:
name: Anchor integration tests
runs-on: ubuntu-latest
needs: [cargo-test, security-scan, sdk-tests]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install JS deps
working-directory: .
run: |
if [ -f package-lock.json ]; then npm ci; elif [ -f yarn.lock ]; then yarn install --frozen-lockfile; fi

- name: Install Solana CLI
run: |
curl -sSfL https://release.solana.com/v1.14.17/install | sh
echo "::add-path::$HOME/.local/share/solana/install/active_release/bin"

- name: Install Anchor CLI
env:
CARGO_HOME: $HOME/.cargo
run: |
rustup default stable
cargo install --git https://github.com/coral-xyz/anchor --tag v0.27.0 --locked --bin anchor-cli || cargo install anchor-cli || true

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

- name: Run anchor test
env:
ANCHOR_WALLET: ${{ runner.temp }}/test-keypair.json
run: |
# Run Anchor tests (this will start a local validator)
anchor test

security-scan:
name: Security & dependency scans
runs-on: ubuntu-latest
needs: cargo-test
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

- name: Install cargo-audit
run: |
rustup component add rustfmt || true
cargo install cargo-audit || true

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

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: NPM audit for repo
run: |
if [ -f package-lock.json ]; then npm ci; fi
npm audit --audit-level=moderate || true

- name: NPM audit for sdk
working-directory: sdk
run: |
if [ -f package-lock.json ]; then npm ci; fi
npm audit --audit-level=moderate || true

sdk-tests:
name: SDK unit tests
runs-on: ubuntu-latest
needs: cargo-test
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Install SDK deps
working-directory: sdk
run: |
npm ci

- name: Run SDK tests
working-directory: sdk
run: |
npm test
118 changes: 118 additions & 0 deletions app/dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="Jetty dashboard shell for viewing policy state and preparing admin actions."
/>
<title>Jetty Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div class="shell">
<aside class="sidebar">
<div>
<p class="eyebrow">Jetty</p>
<h1>Policy dashboard</h1>
<p class="lede">
A standalone shell for viewing mint policy state and future admin controls.
</p>
</div>

<nav class="nav">
<a class="nav-item active" href="#overview">Overview</a>
<a class="nav-item" href="#policy">Policy</a>
<a class="nav-item" href="#activity">Activity</a>
</nav>

<div class="sidebar-card">
<p class="sidebar-label">Deployment mode</p>
<strong>Standalone preview</strong>
<span>Safe to delete or replace later.</span>
</div>
</aside>

<main class="content">
<section class="hero" id="overview">
<div>
<p class="eyebrow">Preview</p>
<h2>Production-grade layout, minimal surface area</h2>
<p class="lede">
This shell is intentionally small. It gives us a real place to wire future mint
policy screens without locking the repo into a frontend framework too early.
</p>
</div>

<div class="hero-actions">
<button class="primary" type="button">Connect wallet</button>
<button class="secondary" type="button">Load mint state</button>
</div>
</section>

<section class="grid" id="policy">
<article class="card accent">
<div class="card-header">
<span>Paused</span>
<strong>Off</strong>
</div>
<p>Global pause state for the currently selected mint.</p>
</article>

<article class="card">
<div class="card-header">
<span>Allowlist</span>
<strong>Disabled</strong>
</div>
<p>Allowlist enforcement status and future entry management.</p>
</article>

<article class="card">
<div class="card-header">
<span>Max transfer</span>
<strong>0</strong>
</div>
<p>Volume ceiling for the active policy configuration.</p>
</article>
</section>

<section class="panel">
<div class="panel-header">
<div>
<p class="eyebrow">Mint lookup</p>
<h3>Read-only policy view</h3>
</div>
<span class="badge">Planned</span>
</div>

<label class="field">
<span>Mint address</span>
<input type="text" placeholder="Paste a mint address" />
</label>
</section>

<section class="panel" id="activity">
<div class="panel-header">
<div>
<p class="eyebrow">Activity</p>
<h3>Recent events</h3>
</div>
<span class="badge muted">Empty state</span>
</div>

<div class="empty-state">
<strong>No activity yet</strong>
<p>When the contract emits events, the dashboard can render them here.</p>
</div>
</section>
</main>
</div>
</body>
</html>
Loading
Loading