feat(grave-vault): m3 salvage_pool pre-flight + cert freshness gates#12
feat(grave-vault): m3 salvage_pool pre-flight + cert freshness gates#12graveyieldprotocol wants to merge 1 commit into
Conversation
- scanner: ProtocolConfig.cert_ttl_seconds field (governance-configurable) - scanner: MIN_CERT_TTL_SECONDS=600 hardcoded floor + error 6019 - scanner: phase2 reads cfg.cert_ttl_seconds (was hardcoded const) - vault: salvage_pool full pre-flight (pause, freshness, criteria, bind) - vault: typed Account<EligibilityCert> cross-program account binding - vault: lp_holder_pool_vault as SystemAccount + init_if_needed - vault: claim_lp_proceeds aligned to SystemAccount type - vault: Cargo.toml +init-if-needed feature on anchor-lang Lands milestone 3 of canonical 10-step build sequence. AMM remove, Jupiter swap, and 40/40/20 distribution remain honest-stubbed (m5-m7).
📝 WalkthroughWalkthroughThe PR implements governance-configurable certificate TTL validation with minimum enforcement and strengthens GraveVault account type safety by migrating from unchecked accounts to typed PDAs, adding pre-flight gating logic for protocol pause, certificate freshness, criteria enforcement, and parameter binding verification in salvage_pool. ChangesTTL Configuration and Account Type Strengthening
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes The PR introduces coordinated changes across TTL configuration and account type safety with clear validation logic and pre-flight gating, but requires careful review of the handler's gating sequence and state schema impact on serialization size. Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
programs/grave-vault/src/instructions/salvage_pool.rs (1)
29-35: ⚡ Quick winConsider adding a test assertion to verify mask synchronization.
The intentional duplication is reasonable for compile-time safety, but without an automated check, the two constants could silently drift during refactoring. A simple test assertion would catch this:
#[cfg(test)] mod tests { use super::*; #[test] fn all_criteria_mask_matches_scanner() { assert_eq!( ALL_CRITERIA_MASK, grave_scanner::criteria::ALL_CRITERIA_MASK, "ALL_CRITERIA_MASK drifted from grave_scanner" ); } }🤖 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 `@programs/grave-vault/src/instructions/salvage_pool.rs` around lines 29 - 35, Add a unit test that asserts the local ALL_CRITERIA_MASK equals grave_scanner::criteria::ALL_CRITERIA_MASK to prevent drift; create a #[cfg(test)] mod tests with a #[test] fn (e.g. all_criteria_mask_matches_scanner) that calls assert_eq!(ALL_CRITERIA_MASK, grave_scanner::criteria::ALL_CRITERIA_MASK, "...") so CI fails if the two constants diverge.
🤖 Prompt for all review comments with 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.
Nitpick comments:
In `@programs/grave-vault/src/instructions/salvage_pool.rs`:
- Around line 29-35: Add a unit test that asserts the local ALL_CRITERIA_MASK
equals grave_scanner::criteria::ALL_CRITERIA_MASK to prevent drift; create a
#[cfg(test)] mod tests with a #[test] fn (e.g.
all_criteria_mask_matches_scanner) that calls assert_eq!(ALL_CRITERIA_MASK,
grave_scanner::criteria::ALL_CRITERIA_MASK, "...") so CI fails if the two
constants diverge.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9db1dbe3-f121-4414-aa2a-cc48198856ac
📒 Files selected for processing (10)
CHANGELOG.mdprograms/grave-scanner/src/constants.rsprograms/grave-scanner/src/errors.rsprograms/grave-scanner/src/instructions/evaluate_pool_phase2.rsprograms/grave-scanner/src/instructions/initialize.rsprograms/grave-scanner/src/instructions/update_protocol_config.rsprograms/grave-scanner/src/state/protocol_config.rsprograms/grave-vault/Cargo.tomlprograms/grave-vault/src/instructions/claim_lp_proceeds.rsprograms/grave-vault/src/instructions/salvage_pool.rs
Implements Raydium V4 AmmInfo (752-byte) pool account parsing. Reads coin_vault, pc_vault, coin_vault_mint, pc_vault_mint, lp_mint at the documented byte offsets (336/368/400/432/464); resolves the actual vault token accounts and lp_mint via remaining_accounts; returns PoolData with real reserves and LP supply. Defense in depth: vault accounts' mint fields are verified against AmmInfo's claimed mints (PoolDataParseError on mismatch), so an attacker who supplies the genuine pool account but forged vault accounts (e.g. vaults pointing to a different mint) is rejected. Architectural changes (touch all 5 adapters + 2 handlers): - adapters::extract_pool_data takes a third arg: remaining_accounts - All AMM adapter parse() signatures accept remaining_accounts: &[AccountInfo] - PoolData gains lp_mint: Pubkey (no longer aliased to base_mint) - Phase 1 and Phase 2 handlers pass ctx.remaining_accounts through - Locker check fix: now receives true lp_mint (was using base_mint placeholder per inaugural-commit comment) Other AMM adapters (raydium_clmm, orca_whirlpool, pumpswap, meteora) keep the new signature but continue to revert AmmAdapterUnimplemented — honest- stub pattern unchanged. Retires PRE-MAINNET-TODO CPI-001 (raydium_v4 layout). Adds a "Retired" section to docs/PRE_MAINNET_CHECKLIST.md tracking the row with a post- merge SHA pointer (filled by a tiny follow-up commit after merge). Tests: 4 pure-Rust unit tests for AMM_INFO_SIZE constant, read_pubkey byte offset extraction, out-of-bounds rejection, and synthetic 752-byte fixture roundtrip across all five Pubkey offsets. Full parse() integration test against a real mainnet pool (requires solana-program-test) is flagged as m4.1 follow-up — not in this PR. last_swap_unix_ts handling: Raydium V4 AmmInfo has no last-swap-timestamp field; adapter returns 0 as sentinel. Handler continues to read last_swap_unix_ts from instruction params until ORACLE-002 retires with indexer-signed attestation. ORACLE-002 row updated to note this. CHANGELOG.md not touched — that file is introduced by PR #12 (GraveVault m3). After #12 merges, m4 work gets a CHANGELOG entry in a small follow-up. Refs: - Whitepaper v4.0.1 §3 (Six derelict-pool criteria) - Combined Tech Doc v3.0.1 §3.5 (sweep_stale_anchor + criteria) - v4.0 canonical build sequence step 5 (AMM remove_liquidity precondition is the GraveVault m5 deliverable; Scanner-side layout parse is m4)
Aligns the toolchain to https://solana.com/docs/intro/installation: Solana CLI: 2.2.18 -> 3.0.10 Anchor CLI: 0.31.1 -> 0.32.1 Rust (host): 1.85.0 -> 1.91.1 Node.js: 20 -> 24 rust-version: 1.79 -> 1.91 platform-tools v1.54 pin retained in Cargo.toml [workspace.metadata.solana] — Solana 3.0.10's default platform-tools (v1.52, cargo 1.84) still cannot parse edition2024 manifests; v1.54 (cargo 1.85) is required. Anchor 0.32 migration: - 5 adapter files: removed unused `use anchor_lang::solana_program::pubkey;` (macro now resolves via prelude after 0.32's `solana-program → smaller crates` refactor) - No other source changes required Cargo.toml repository URL also corrected from stale lowercase graveyieldprotocol/protocol to org-canonical GraveYield-Protocol/protocol. Verification: - cargo fmt --all -- --check: clean - cargo clippy --workspace --all-targets -- -D warnings: clean on Rust 1.91.1 + Anchor 0.32.1 (locally verified) - cargo test -p grave-scanner --lib: 19/19 pass - pnpm typecheck: clean on Node 24 - anchor build: BPF compile succeeds via Solana 3.0.10 + platform-tools v1.54 (CI verified, 338s) - terminology-lint.sh: clean All 5 CI jobs green on commit 78dbce1. Follow-up not in this PR: PR #12 (GraveVault m3) will rebase against new main and inherit this toolchain. Any Anchor 0.32 surface that touches GraveVault's `init_if_needed` on SystemAccount will be handled at that rebase.
…(rebased) Re-submitting PR #12's m3 work, rebased onto current main (post-m4 Scanner + post-Anchor 0.32 toolchain bump). The original PR #12 was opened on 2026-05-10 and is now 4 commits behind main; this branch supersedes it. What ships: ### Scanner — cert_ttl_seconds governance-configurable - state/protocol_config.rs: new `cert_ttl_seconds: i64` field - constants.rs: new MIN_CERT_TTL_SECONDS=600 (hardcoded floor) and DEFAULT_CERT_TTL_SECONDS=3600 (default 1h) - errors.rs: new 6019 CertTtlBelowMinimum - instructions/initialize.rs: cert_ttl_seconds param with floor validation - instructions/update_protocol_config.rs: cert_ttl_seconds Option with floor - instructions/evaluate_pool_phase2.rs: reads cfg.cert_ttl_seconds (preserves m4's adapter wiring with remaining_accounts + pool_data.lp_mint) ### Vault — salvage_pool pre-flight + 5 gates - programs/grave-vault/src/instructions/salvage_pool.rs (flagship): 1. !cfg.emergency_paused -> ProtocolPaused 2. !cert.is_expired(now) -> EligibilityCertExpired 3. cert.criteria_bitmap == 0x3F -> InvalidEligibilityCert 4. cert.amm_program_id / pool_address match params -> InvalidEligibilityCert 5. pool.key() == params.pool_address -> PreflightFailed - eligibility_cert migrated UncheckedAccount -> Account<'info, EligibilityCert> for automatic owner-program validation against grave_scanner::ID - programs/grave-vault/src/instructions/claim_lp_proceeds.rs: lp_holder_pool_vault aligned to SystemAccount<'info> for type consistency ### Anchor 0.32 migration (NEW work, not in original PR #12) Anchor 0.32 rejects `init`/`init_if_needed` on `SystemAccount` with a deliberate compile error. PR #12's design used init_if_needed for the lp_holder_pool_vault PDA; this rebase replaces it with the canonical Anchor 0.32 pattern: - lp_holder_pool_vault changes from `SystemAccount<'info>` with `init_if_needed` to `UncheckedAccount<'info>` with `mut, seeds, bump` (PDA validation only; no init constraint) - Handler issues a manual `anchor_lang::system_program::create_account` CPI when `vault.lamports() == 0` (lazy first-salvage init) - grave-vault/Cargo.toml: removed the now-unused `init-if-needed` feature - Net result identical to the original design: system-owned, space=0, rent-exempt PDA created on first salvage of each pool CHANGELOG.md introduced with v1.0.6 entry naming each change. Local verification on the official Solana 3.x stack (rust 1.91.1, anchor 0.32.1, solana 3.0.10, platform-tools v1.54): - cargo fmt --all -- --check: clean - cargo clippy --workspace --all-targets -- -D warnings: clean - cargo test --workspace --lib: 20/20 pass (19 grave-scanner + 1 grave-vault) - cargo-build-sbf --tools-version v1.54: BPF compile succeeds in 51s Supersedes #12. Once this PR merges, #12 should be closed as `not-planned` (superseded by #15).
|
Superseded by #15. The rebased version against current main (post-m4, post-Solana 3.x toolchain bump) is now open at #15 and is fully verified locally:
Migration delta vs this PR: Anchor 0.32 rejects Closing this PR as |
…15) Lands milestone 3 of the canonical 10-step GraveYield build sequence: GraveVault salvage_pool pre-flight + PoolRegistry init + 5 cert/pool gates. Supersedes PR #12 (rebased onto current main with Anchor 0.32 migration). ## Scanner: cert_ttl_seconds governance-configurable - state/protocol_config.rs: new cert_ttl_seconds: i64 field - constants.rs: MIN_CERT_TTL_SECONDS=600 floor, DEFAULT_CERT_TTL_SECONDS=3600; old ELIGIBILITY_CERT_TTL_SECONDS kept as #[deprecated] alias - errors.rs: new 6019 CertTtlBelowMinimum - instructions/initialize.rs + update_protocol_config.rs: cert_ttl_seconds with floor validation - instructions/evaluate_pool_phase2.rs: reads cfg.cert_ttl_seconds; preserves all m4 adapter wiring (remaining_accounts, lp_mint, criteria bitmap match) ## Vault: salvage_pool pre-flight + 5 gates - instructions/salvage_pool.rs: 1. !cfg.emergency_paused -> ProtocolPaused 2. !cert.is_expired(now) -> EligibilityCertExpired 3. cert.criteria_bitmap == 0x3F -> InvalidEligibilityCert 4. cert.amm_program_id / pool_address match params -> InvalidEligibilityCert 5. pool.key() == params.pool_address -> PreflightFailed - eligibility_cert: UncheckedAccount -> Account<'info, EligibilityCert> (Anchor 0.32 handles discriminator + cross-program owner check) - instructions/claim_lp_proceeds.rs: lp_holder_pool_vault as SystemAccount - Cargo.toml: removed unused init-if-needed feature ## Anchor 0.32 migration (new work, not in original PR #12) Anchor 0.32 rejects init / init_if_needed on SystemAccount. Replaced: - lp_holder_pool_vault: SystemAccount + init_if_needed -> UncheckedAccount + mut/seeds/bump (PDA validation only) - Handler issues manual anchor_lang::system_program::create_account CPI when vault.lamports() == 0 (lazy first-salvage init, signed with PDA bump) - Net result identical to original design: system-owned, space=0, rent-exempt PDA created on first salvage ## CHANGELOG.md Introduced with v1.0.6 entry. Note: CodeRabbit flagged 2 minor wording inaccuracies in the CHANGELOG (describes the init_if_needed approach we replaced) — addressed in a follow-up fix-up commit. ## Verification Local on rust 1.91.1 + anchor 0.32.1 + solana 3.0.10 + platform-tools v1.54: - cargo fmt --all -- --check: clean - cargo clippy --workspace --all-targets -- -D warnings: clean - cargo test --workspace --lib: 20/20 pass - cargo-build-sbf --tools-version v1.54: BPF compile clean in 51s CodeRabbit reviewed: 2 minor 🟡 nits, both CHANGELOG.md wording (no code findings). Honest stubs: m5 (Raydium V4 remove_liquidity CPI), m6 (Jupiter swap + Merkle proof), m7 (40/40/20 distribution) remain pending.
|
Superseded by #15, which has now merged to main as #15 contains this PR's full diff plus the Anchor 0.32 migration delta required by the toolchain bump in #14 (replacing Please close this PR as |
Summary
Lands milestone 3 of the canonical 10-step GraveYield build sequence: GraveVault
salvage_poolpre-flight + PoolRegistry. The instruction now opens with all the right gates wired; AMMremove_liquidity(m5), Jupiter swap (m6), and 40/40/20 distribution (m7) remain honest-stubbed below the pre-flight section.Resolves Seth's Telegram lock from 2026-05-10:
MIN_CERT_TTL_SECONDS = 600(10 min)Changes
Scanner (cert_ttl_seconds migration to governance-configurable + floor)
programs/grave-scanner/src/state/protocol_config.rs— addedcert_ttl_seconds: i64field._reservedstays at 64 bytes;INIT_SPACEgrows by 8 bytes (pre-mainnet, no migration concern).programs/grave-scanner/src/constants.rs— addedMIN_CERT_TTL_SECONDS: i64 = 600(hardcoded floor; raising requires program upgrade) andDEFAULT_CERT_TTL_SECONDS: i64 = 3600. OldELIGIBILITY_CERT_TTL_SECONDSconst kept as#[deprecated]alias for backwards-compatible test fixtures.programs/grave-scanner/src/errors.rs— added error 6019CertTtlBelowMinimum.programs/grave-scanner/src/instructions/initialize.rs—InitializeParams.cert_ttl_seconds: i64field with default-handling (0 →DEFAULT_CERT_TTL_SECONDS) and floor validation.programs/grave-scanner/src/instructions/update_protocol_config.rs—UpdateProtocolConfigParams.cert_ttl_seconds: Option<i64>with floor validation. RevertsCertTtlBelowMinimumfor any value < 600s.programs/grave-scanner/src/instructions/evaluate_pool_phase2.rs— readscfg.cert_ttl_secondsinstead of the hardcoded const when stampingcert.expires_at.Vault (salvage_pool full pre-flight)
programs/grave-vault/src/instructions/salvage_pool.rs— flagship change. Five gates wired:!cfg.emergency_paused→ProtocolPaused(existing).!cert.is_expired(now)→EligibilityCertExpired(NEW). TTL window comes from Scanner ProtocolConfig, floored at 600s.cert.criteria_bitmap == 0x3F→InvalidEligibilityCert(NEW). All six derelict-pool criteria must have passed at Phase 2.cert.amm_program_id == params.amm_program_idANDcert.pool_address == params.pool_address→InvalidEligibilityCert(NEW). Defense in depth against forged seed match.pool.key() == params.pool_address→PreflightFailed(existing).eligibility_certmigratedUncheckedAccount→Account<'info, EligibilityCert>. Anchor now handles 8-byte discriminator + owner-program (grave_scanner::ID) automatically; manual ownership require! removed as redundant.lp_holder_pool_vaultmigratedUncheckedAccount→SystemAccount<'info>withinit_if_needed+space = 0. First salvage of a pool creates the system-owned 0-data PDA; subsequent salvages of the same pool blocked upstream bypool_registryinit constraint, so the init_if_needed footgun does not apply.min_quote_output_lamportsto keep the unused-variable lint quiet through m6.programs/grave-vault/src/instructions/claim_lp_proceeds.rs—lp_holder_pool_vaultaligned toSystemAccount<'info>for type consistency with salvage_pool.programs/grave-vault/Cargo.toml— addedinit-if-neededfeature toanchor-langdependency.CHANGELOG.md— created with v1.0.6 entry naming each change + honest-stub status.Verification status
35afe30).Account<'info, EligibilityCert>works becausegrave-scanner = { features = ["cpi"] }and#[account]macro generates a stableOwnerimpl that resolves tograve_scanner::IDregardless of consuming crate.cargo check/cargo clippy -D warningsnot yet run. Pending Seth's ship-now-vs-verify-first call. v1.0.7 will be the post-verification patch with any compile fixes named in CHANGELOG (per established cadence).Honest-stub status (m4–m7 still pending)
remove_liquidityRaydium V4: wired in v1.0.5 background work; not yet integration-tested against seeded localnet pool (OpenBook seed harness is v1.1).AmmAdapterUnimplemented.LockerAdapterUnimplemented.SalvageReceiptdistribution fields are zeroed at init.claim_lp_proceeds: returnsInvalidClaimProofuntil m6.Pre-mainnet checklist additions
cert_ttl_secondschangesINIT_SPACE. Existing config accounts will failreallocunless rotated through a freshinitialize. (No-op on the canonical pre-mainnet deploy path since no live config exists.)declare_id!andAnchor.tomlviaanchor keys list && anchor keys syncbefore any audit submission.Test plan
cargo checkfrom repo root (Rust 1.85, Solana 2.0.21, Anchor 0.31.1).cargo clippy --all-targets -- -D warnings.pnpm -r typecheckto verify SDK types still resolve against the new IDL.terminology-lint.shshould pass (norescue/RescueReceiptintroduced — all canonical v4.0 terms).Summary by CodeRabbit
Release Notes
New Features
Chores