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
50 changes: 42 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,57 @@ jobs:
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/v${SOLANA_VERSION}/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"
# cargo-binstall fetches a prebuilt anchor-cli binary in ~10s instead
# of compiling from source (~5-7 min, which has been intermittently
# cancelled on ubuntu-latest runners during the dependency-fetch
# phase). Drops total anchor-build job time from ~10 min to ~2 min.
- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@main
# cargo-binstall was the original choice for speed but silently
# no-ops on anchor-cli 0.32.x: it exits 0 without installing the
# `anchor` binary on PATH (verified across multiple CI runs — the
# Install Anchor CLI step reports 0 seconds and success, then
# `anchor build` exits in 1 second with "command not found").
# cargo install --locked compiles from source (~5-7 min cold, cached
# by Swatinem/rust-cache@v2) and reliably places `anchor` in
# ~/.cargo/bin. The trailing `anchor --version` is a load-bearing
# assertion so future install regressions fail here rather than
# leaking to the build step where the symptom is opaque.
- name: Install Anchor CLI
run: cargo binstall --no-confirm --version ${ANCHOR_VERSION} anchor-cli
run: |
cargo install --locked --version ${ANCHOR_VERSION} anchor-cli
anchor --version
# Capture anchor build's full output to a file and upload it as a
# workflow artifact when the job fails — needed because the actual
# error message is otherwise only visible via the GitHub Actions
# web UI logs page (the Composio integration this repo uses for
# programmatic CI inspection does not expose log download).
# Solana 3.0.10's bundled platform-tools v1.51 ships cargo 1.84,
# which can't parse edition2024 manifests (blake3 0.12, hashbrown,
# digest, crypto-common — all transitive deps of Anchor 0.32.1's SPL
# deps). cargo-build-sbf 3.0.10's `--tools-version` flag is silently
# ignored, and `[workspace.metadata.solana] tools-version = "v1.54"`
# isn't honored either, so we replace the cached platform-tools
# directory with v1.54 contents (cargo 1.89) before `anchor build`
# invokes cargo-build-sbf. The cache key stays `v1.51` because
# cargo-build-sbf 3.0.10 hardcodes it.
- name: Pin platform-tools v1.54 (edition2024 fix)
run: |
set -euo pipefail
curl -sSL -o /tmp/platform-tools.tar.bz2 \
"https://github.com/anza-xyz/platform-tools/releases/download/v1.54/platform-tools-linux-x86_64.tar.bz2"
CACHE_DEST="$HOME/.cache/solana/v1.51/platform-tools"
rm -rf "$CACHE_DEST"
mkdir -p "$CACHE_DEST"
tar xjf /tmp/platform-tools.tar.bz2 -C "$CACHE_DEST"
Comment on lines +92 to +100

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Verify the platform-tools archive before extracting it.

This step untars an executable toolchain straight from a network download into the runner cache. Without checking the published checksum or signature first, a compromised release asset becomes code execution in CI.

Proposed fix
       - name: Pin platform-tools v1.54 (edition2024 fix)
         run: |
           set -euo pipefail
           curl -sSL -o /tmp/platform-tools.tar.bz2 \
             "https://github.com/anza-xyz/platform-tools/releases/download/v1.54/platform-tools-linux-x86_64.tar.bz2"
+          curl -sSL -o /tmp/platform-tools.tar.bz2.sha256 \
+            "https://github.com/anza-xyz/platform-tools/releases/download/v1.54/platform-tools-linux-x86_64.tar.bz2.sha256"
+          sha256sum -c /tmp/platform-tools.tar.bz2.sha256
           CACHE_DEST="$HOME/.cache/solana/v1.51/platform-tools"
           rm -rf "$CACHE_DEST"
           mkdir -p "$CACHE_DEST"
           tar xjf /tmp/platform-tools.tar.bz2 -C "$CACHE_DEST"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Pin platform-tools v1.54 (edition2024 fix)
run: |
set -euo pipefail
curl -sSL -o /tmp/platform-tools.tar.bz2 \
"https://github.com/anza-xyz/platform-tools/releases/download/v1.54/platform-tools-linux-x86_64.tar.bz2"
CACHE_DEST="$HOME/.cache/solana/v1.51/platform-tools"
rm -rf "$CACHE_DEST"
mkdir -p "$CACHE_DEST"
tar xjf /tmp/platform-tools.tar.bz2 -C "$CACHE_DEST"
- name: Pin platform-tools v1.54 (edition2024 fix)
run: |
set -euo pipefail
curl -sSL -o /tmp/platform-tools.tar.bz2 \
"https://github.com/anza-xyz/platform-tools/releases/download/v1.54/platform-tools-linux-x86_64.tar.bz2"
curl -sSL -o /tmp/platform-tools.tar.bz2.sha256 \
"https://github.com/anza-xyz/platform-tools/releases/download/v1.54/platform-tools-linux-x86_64.tar.bz2.sha256"
sha256sum -c /tmp/platform-tools.tar.bz2.sha256
CACHE_DEST="$HOME/.cache/solana/v1.51/platform-tools"
rm -rf "$CACHE_DEST"
mkdir -p "$CACHE_DEST"
tar xjf /tmp/platform-tools.tar.bz2 -C "$CACHE_DEST"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 86 - 94, The CI step downloads and
extracts platform-tools without verification; update the job to verify the
release artifact before extracting by fetching the corresponding checksum or
signature and validating it against the downloaded file (the curl download to
/tmp/platform-tools.tar.bz2) prior to using tar to unpack into CACHE_DEST; use
the published SHA256 (or a GPG signature) from the release, fail the step if
verification fails, and only then proceed with rm -rf, mkdir -p and tar xjf into
CACHE_DEST so the pipeline never extracts unverified network content.

"$CACHE_DEST/rust/bin/cargo" --version
"$CACHE_DEST/rust/bin/rustc" --version
# `anchor build` invokes `cargo-build-sbf` for the BPF compile AND
# `anchor idl build` for IDL generation. The IDL step requires a
# nightly Rust toolchain (Anchor 0.32.1 hasn't migrated to stable IDL
# gen yet). Since this workflow only installs stable, we run with
# `--no-idl` and treat IDL generation as a follow-up workstream — it
# produces TS client types but isn't a blocker for the on-chain
# program. A later PR can add `dtolnay/rust-toolchain@nightly` plus
# a dedicated IDL-build step.
- name: Anchor build
run: |
set -o pipefail
anchor build 2>&1 | tee /tmp/anchor-build.log
anchor build --no-idl 2>&1 | tee /tmp/anchor-build.log
- name: Upload anchor build log on failure
if: failure()
uses: actions/upload-artifact@v4
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## [Unreleased — m5: salvage_pool execution path]

### Added
- **GraveVault salvage_pool execution path** end-to-end (m5):
- `cpi/raydium_v4.rs` — real Raydium V4 `withdraw` CPI (vault_authority PDA-signs `user_owner`; 18-account list; 9-byte data `[tag=4][amount_le]`; AMM authority constant validation; pre/post balance deltas).
- `cpi/jupiter.rs` — Jupiter v6 swap CPI helper (forwards salvor's pre-computed route data + accounts; vault_authority signs).
- `cpi/raydium_clmm.rs`, `cpi/orca_whirlpool.rs`, `cpi/pump_swap.rs` — honest-stub adapters; revert `AmmCpiUnimplemented` (7017).
- `cpi/mod.rs` — dispatcher by `pool.owner`.
- **salvage_pool handler** rewritten to wire: salvor→vault LP transfer, dispatched remove_liquidity CPI, Jupiter swap (or dust skip), WSOL→SOL unwrap via `close_account` to `vault_sol_holding_account`, 40/40/20 distribution via three `system_program::transfer` calls, PoolRegistry + SalvageReceipt population, `PoolSalvaged` + `SalvageCompleted` emit.
- **Five new error codes** (7015-7019): `AmmRedemptionFailed`, `JupiterSwapFailed`, `AmmCpiUnimplemented`, `InvalidSnapshotData`, `UnsupportedBaseToken`. Mirrored to `docs/error_codes.md` in lock-step per the sync convention.
- **New PDA seeds**: `VAULT_AUTHORITY_SEED` (singleton signer), `VAULT_SOL_HOLDING_SEED` (per-pool, transient native-SOL holding for unwrap).
- **New constants**: `WSOL_MINT`, `RAYDIUM_V4_PROGRAM_ID`, `RAYDIUM_V4_AMM_AUTHORITY` (`5Q544...`), `RAYDIUM_CLMM_PROGRAM_ID`, `ORCA_WHIRLPOOL_PROGRAM_ID`, `PUMP_SWAP_PROGRAM_ID`, `JUPITER_V6_PROGRAM_ID`, `RAYDIUM_V4_INSTRUCTION_TAG_WITHDRAW = 4`, `RAYDIUM_V4_WITHDRAW_REMAINING_ACCOUNTS_REQUIRED = 11`, `BPS_DENOMINATOR = 10_000`, `HARD_MAX_SLIPPAGE_BPS = 1_000`.
- **PRE_MAINNET_CHECKLIST**: new rows `CPI-006/007/008` (CLMM/Orca/PumpSwap stubs) + `CPI-009` (Raydium V4 account-ordering verification against a live mainnet pool — blocking row).

### Changed
- `salvage_pool` instruction signature now takes `Context<'_, '_, '_, 'info, SalvagePool<'info>>` (explicit `'info` threading per Anchor 0.31+ lifetime invariance — see failure-pattern memory).
- `SalvagePoolParams` extended with `salvor_lp_amount`, `jupiter_route_data: Vec<u8>`, `max_slippage_bps_override: Option<u16>`, `jupiter_route_accounts_len: u8`.
- `SalvagePool` Accounts struct extended with `vault_authority`, `vault_sol_holding_account`, `salvor_lp_token_account`, `vault_lp_token_account`, `vault_base_token_account`, `vault_memecoin_token_account`, `lp_mint`, `memecoin_mint`, `wsol_mint` (pinned via `address` constraint), `token_program`, `associated_token_program`.

### Unverified
- BPF compile via `anchor build` (deferred to CI on this PR).
- Live Raydium V4 fork test of the exact 18-account ordering. The `amm_authority` constant check provides one assertion; full integration is `CPI-009` in `PRE_MAINNET_CHECKLIST.md`.
- Real Jupiter v6 swap end-to-end. The CPI helper forwards what the salvor's bot quotes; verification is a localnet smoke test post-merge.
- Pool orientation: `base_is_coin_side` is currently hardcoded `true` (assumes WSOL is the pool's coin side). A SOL/X pool where WSOL is the PC side will need the bot to invert its submission ordering; a runtime parse of pool data to detect orientation is in `PRE-MAINNET-TODO(CPI)` comments in `salvage_pool.rs`.

Comment on lines +22 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Pool orientation concern mentioned but not tracked in PRE_MAINNET_CHECKLIST.

Line 26 documents a pool orientation hardcoding issue with "PRE-MAINNET-TODO(CPI) comments" in the source, but no corresponding CPI-010 (or similar) row appears in PRE_MAINNET_CHECKLIST.md. Per the checklist convention (line 4: "Each row maps to one or more PRE-MAINNET-TODO markers in source"), this represents a tracking gap for an acknowledged pre-mainnet concern.

If the pool orientation assumption is a blocking or high-priority concern, add a numbered row (e.g., CPI-010) to the checklist with appropriate status (🟥/🟧) and verification criteria. If it's lower priority or addressed differently, clarify the relationship between this Unverified item and the checklist scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 22 - 27, The CHANGELOG notes a hardcoded pool
orientation (base_is_coin_side in salvage_pool.rs) marked with
PRE-MAINNET-TODO(CPI) but there is no corresponding entry in
PRE_MAINNET_CHECKLIST.md; add a new checklist row (e.g., CPI-010) that
references the PRE-MAINNET-TODO marker, gives a status (🟥/🟧) and concrete
verification criteria (runtime detection of pool orientation or a documented
inversion procedure), or update the CHANGELOG text to point to an existing CPI
entry if this is already tracked; ensure the checklist row ID matches the TODO
marker and the description mentions salvage_pool.rs and base_is_coin_side so
reviewers can locate the code.

All notable changes to the GraveYield protocol monorepo are documented here.
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Expand Down
4 changes: 4 additions & 0 deletions docs/PRE_MAINNET_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ Status legend: 🟥 blocking · 🟧 high-priority · 🟡 medium · ⬜ trackin
| CPI-003 | `programs/grave-scanner/src/adapters/orca_whirlpool.rs` | 🟧 | Orca Whirlpool layout + token-vault reserve aggregation. |
| CPI-004 | `programs/grave-scanner/src/adapters/pumpswap.rs` | 🟧 | PumpSwap pool layout parsing. |
| CPI-005 | `programs/grave-scanner/src/adapters/meteora.rs` | 🟡 | Meteora DLMM / Dynamic AMM pool layout parsing. v1.1 milestone. |
| CPI-006 | `programs/grave-vault/src/cpi/raydium_clmm.rs` | 🟧 | Raydium CLMM (concentrated liquidity) `remove_liquidity` CPI for GraveVault. v1.1 milestone. Reverts with `AmmCpiUnimplemented`. |
| CPI-007 | `programs/grave-vault/src/cpi/orca_whirlpool.rs` | 🟧 | Orca Whirlpool position-burn CPI for GraveVault. v1.1 milestone. Reverts with `AmmCpiUnimplemented`. |
| CPI-008 | `programs/grave-vault/src/cpi/pump_swap.rs` | 🟧 | PumpSwap `remove_liquidity` CPI for GraveVault. v1.1 milestone. Reverts with `AmmCpiUnimplemented`. |
| CPI-009 | `programs/grave-vault/src/cpi/raydium_v4.rs` | 🟥 | Verify Raydium V4 withdraw account ordering against a live mainnet pool (e.g. `9d9mb8kooFfaD3SctgZtkxQypkshx6ezhbKio89ixyy2`) via `solana-program-test` fork test before mainnet. The `amm_authority` constant check catches an obviously-wrong layout but not subtle swaps. |

### KEYS

Expand Down
16 changes: 10 additions & 6 deletions docs/error_codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,12 @@ future v4.x additions to the pre-anchor error space.
| 6018 | `AnchorNotStale` | `sweep_stale_anchor` called before the staleness window elapsed. |
| 6019 | `CertTtlBelowMinimum` | `update_protocol_config` rejected a `cert_ttl_seconds` value below `MIN_CERT_TTL_SECONDS` (600s = 10 min). |

## GraveVault — 7000-7014
## GraveVault — 7000-7019

Source: [`../programs/grave-vault/src/errors.rs`](../programs/grave-vault/src/errors.rs).
New error codes from milestones m5/m6/m7 (e.g. `AmmRedemptionFailed`,
`JupiterSwapFailed`, `AmmCpiUnimplemented`, `InvalidSnapshotData`,
`UnsupportedBaseToken`) will append at 7015+ and must be added here in
lock-step with the Rust source.
Codes 7015-7019 added by m5 (salvage_pool execution path). Future m6/m7
additions append at 7020+ and must land in lock-step with the Rust
source per the sync convention.

| Code | Name | Condition |
|------|------|-----------|
Expand All @@ -74,6 +73,11 @@ lock-step with the Rust source.
| 7012 | `BelowDustThreshold` | Quote output below the Jupiter dust threshold; salvage skipped or aborted. |
| 7013 | `PreflightFailed` | Pre-flight check against the on-chain pool failed. |
| 7014 | `TimelockNotElapsed` | Timelock window has not yet elapsed for a queued parameter change. |
| 7015 | `AmmRedemptionFailed` | AMM `remove_liquidity` CPI returned an error or zero output. |
| 7016 | `JupiterSwapFailed` | Jupiter v6 swap CPI returned an error or zero output. |
| 7017 | `AmmCpiUnimplemented` | AMM CPI adapter is a pre-mainnet stub (CLMM / Orca Whirlpool / PumpSwap). Pool owner is not the Raydium V4 program. See [`PRE_MAINNET_CHECKLIST.md`](PRE_MAINNET_CHECKLIST.md). |
| 7018 | `InvalidSnapshotData` | Salvor's `lp_total_supply_at_snapshot` does not match the on-chain LP mint supply at salvage time. |
| 7019 | `UnsupportedBaseToken` | Pool base token is not WSOL. USDC/USDT base support is a v1.1 deliverable. |

## Drift from the v3.0 .docx snapshot

Expand All @@ -96,4 +100,4 @@ rather than re-tabulating the codes.

---

*Mirrored from `errors.rs` files on 2026-05-16.*
*Mirrored from `errors.rs` files on 2026-05-16. Last verified at PR m5 (GraveVault 7000-7019, GraveScanner 6000-6019).*
2 changes: 1 addition & 1 deletion programs/grave-vault/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ idl-build = [
anchor-debug = []

[dependencies]
anchor-lang = { workspace = true }
anchor-lang = { workspace = true, features = ["init-if-needed"] }
anchor-spl = { workspace = true }
grave-scanner = { path = "../grave-scanner", features = ["cpi"] }
62 changes: 62 additions & 0 deletions programs/grave-vault/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// GraveVault constants. Charter invariants are encoded here as `const` and
// asserted by every code path that depends on them.

use anchor_lang::prelude::*;

// =====================================================================
// Charter-locked invariants. Governance CANNOT change these.
// =====================================================================
Expand All @@ -23,6 +25,9 @@ pub const DEFAULT_LP_HOLDER_SHARE_BPS: u16 = 4_000;
/// Default salvor share at launch (40%).
pub const DEFAULT_SALVOR_SHARE_BPS: u16 = 4_000;

/// Basis-point denominator. All share math: (amount * share_bps) / BPS_DENOMINATOR.
pub const BPS_DENOMINATOR: u64 = 10_000;

// =====================================================================
// Operational defaults (governance-tunable within bounds).
// =====================================================================
Expand All @@ -36,6 +41,10 @@ pub const DEFAULT_MAX_PRIORITY_FEE_CEILING_LAMPORTS: u64 = 1_000_000_000;
/// Default maximum slippage in basis points for the Jupiter swap leg (3%).
pub const DEFAULT_MAX_SLIPPAGE_BPS: u16 = 300;

/// Hard maximum slippage in basis points (10%). `update_protocol_config`
/// rejects any value above this regardless of multisig vote.
pub const HARD_MAX_SLIPPAGE_BPS: u16 = 1_000;

/// Default Jupiter dust threshold in lamports — skip swap if quote output
/// would be below this. Matches the operating-parameter brief.
pub const DEFAULT_JUPITER_DUST_THRESHOLD_LAMPORTS: u64 = 666_666;
Expand All @@ -54,5 +63,58 @@ pub const SALVAGE_RECEIPT_SEED: &[u8] = b"salvage_receipt";
pub const CLAIM_RECORD_SEED: &[u8] = b"claim_record";
pub const PROTOCOL_TREASURY_SEED: &[u8] = b"protocol_treasury";

/// Singleton vault authority PDA. Signs inner CPIs (Raydium withdraw,
/// Jupiter swap, system transfers from `vault_sol_holding_account`).
pub const VAULT_AUTHORITY_SEED: &[u8] = b"vault_authority";

/// Per-pool transient SOL holding PDA. Receives native SOL when the vault's
/// WSOL token account is closed after the Jupiter swap, before the 40/40/20
/// distribution transfers fan out. Lazy-init via `create_account` CPI on
/// first salvage of a given pool (same pattern as `lp_holder_pool_vault`).
pub const VAULT_SOL_HOLDING_SEED: &[u8] = b"vault_sol_holding";

// Cross-program seeds we read from GraveScanner.
pub const ELIGIBILITY_CERT_SEED: &[u8] = b"eligibility_cert";

// =====================================================================
// External program IDs (mainnet).
// =====================================================================

/// Wrapped SOL mint — fixed Solana network constant.
pub const WSOL_MINT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");

/// Raydium V4 AMM program — mainnet.
pub const RAYDIUM_V4_PROGRAM_ID: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");

/// Raydium V4 AMM authority — fixed PDA derived from the V4 program.
/// Used to validate the `amm_authority` account passed by the salvor in
/// `remaining_accounts` rather than trusting it blindly.
pub const RAYDIUM_V4_AMM_AUTHORITY: Pubkey =
pubkey!("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1");

/// Raydium CLMM (concentrated liquidity) program — m5 honest-stub target.
pub const RAYDIUM_CLMM_PROGRAM_ID: Pubkey = pubkey!("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK");

/// Orca Whirlpool program — m5 honest-stub target.
pub const ORCA_WHIRLPOOL_PROGRAM_ID: Pubkey =
pubkey!("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc");

/// PumpSwap program — m5 honest-stub target.
pub const PUMP_SWAP_PROGRAM_ID: Pubkey = pubkey!("PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP");

/// Jupiter v6 aggregator program — mainnet.
pub const JUPITER_V6_PROGRAM_ID: Pubkey = pubkey!("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4");

// =====================================================================
// Raydium V4 withdraw CPI layout.
// =====================================================================

/// Instruction discriminator for Raydium V4 `Withdraw`. Per Raydium V4
/// `instruction.rs`, the tag is u8 = 4. Instruction data layout:
/// [tag: u8 = 4] [amount: u64 LE] = 9 bytes total.
pub const RAYDIUM_V4_INSTRUCTION_TAG_WITHDRAW: u8 = 4;

/// Number of `remaining_accounts` salvor must supply for the Raydium V4
/// withdraw CPI (pool internals + OpenBook market accounts that aren't in
/// the named `Accounts` struct). See `cpi/raydium_v4.rs` for the layout.
pub const RAYDIUM_V4_WITHDRAW_REMAINING_ACCOUNTS_REQUIRED: usize = 11;
Loading
Loading