feat(m6): claim_lp_proceeds Merkle verification + SOL transfer#20
feat(m6): claim_lp_proceeds Merkle verification + SOL transfer#20graveyieldprotocol wants to merge 5 commits into
Conversation
Wires the second half of the salvage settlement flow: original LP holders prove inclusion in the snapshot recorded by salvage_pool and withdraw their pro-rata share from lp_holder_pool_vault. New `merkle.rs` module implements SHA-256 sorted-pair verification (OpenZeppelin / Uniswap convention) with 7 host unit tests covering single-leaf, two-leaf, four-leaf trees, sort invariance, empty proofs, and tampering. claim_lp_proceeds handler replaces m3's two TODOs: - Merkle proof check (was `!proof.is_empty()` stub) - SOL transfer from lp_holder_pool_vault to lp_holder (was TODO comment) PR scope per Option B (2 PRs by code path): this is the claim_lp_proceeds side. The matching salvage_pool execution path (Raydium V4 + Jupiter + 40/40/20 distribution) is PR #19. The two PRs have NO file overlap — m5 touches instructions/salvage_pool.rs and cpi/, m6 touches instructions/claim_lp_proceeds.rs and merkle.rs. Both modify lib.rs in different sections so the rebase-at-merge is clean. No new error codes — InvalidClaimProof (7010) and ClaimAlreadyProcessed (7011) already cover the m6 surface. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Two locally-reproduced fixes (cargo clippy --all-targets -- -D warnings
goes from 1 error to 0; 7/7 merkle.rs unit tests pass):
1. `Cargo.toml`: add `solana-sha256-hasher = "2"` dep. Anchor 0.32
dropped the `solana_program::hash` re-export — the curated
re-export list only includes account_info, clock, msg, entrypoint,
program_error, pubkey, system_program, system_instruction. SHA-256
hashing lives in the standalone solana-sha256-hasher crate, which
is already a transitive dep but needs to be declared to be imported.
2. `merkle.rs`: change `use anchor_lang::solana_program::hash::hash;`
to `use solana_sha256_hasher::hash;` matching the new home.
All remaining changes are rustfmt drift (3 files) auto-applied via
`cargo fmt --all`. Verified clean locally:
- cargo fmt --check ✓
- cargo clippy --all-targets -- -D warnings ✓
- cargo test --lib --package grave-vault: 8/8 tests pass
(7 merkle::tests::* + 1 test_id from lib.rs scaffold)
Anchor build (BPF compile) deferred to CI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two BPF-target-only failures that host cargo check / clippy don't surface: 1. salvage_pool.rs: Box-wrap 11 large account fields. `cargo-build-sbf` reported the `SalvagePool::try_accounts` function overflowing its 4096-byte BPF frame by 128 bytes (4224 estimated). `protocol_config`, `eligibility_cert`, `pool_registry`, `salvage_receipt`, four TokenAccounts, three Mints are now `Box<Account<...>>` — moves deserialized data off the stack onto the heap. Canonical Anchor pattern for large Accounts structs. 2. .github/workflows/ci.yml: Pin platform-tools v1.54 before `anchor build`. Solana 3.0.10 bundles platform-tools v1.51 which ships cargo 1.84 — too old for `edition2024` manifests pulled transitively by Anchor 0.32.1's SPL deps (blake3 0.12, hashbrown 0.17, digest 0.11, crypto-common 0.2). cargo-build-sbf 3.0.10's --tools-version flag is silently ignored, and the workspace `[metadata.solana] tools-version = "v1.54"` pin isn't honored. Workaround: replace the cached platform-tools directory contents with v1.54 (cargo 1.89) before anchor build runs. The cache key stays `v1.51` because cargo-build-sbf 3.0.10 hardcodes it. Locally reproduced both failures with the matching toolchain stack (Solana 3.0.10 + platform-tools v1.51 + anchor 0.32.1). After both fixes: - `cargo-build-sbf` finishes in 3.79s clean - `cargo clippy --all-targets -- -D warnings` clean - `cargo fmt --check` clean PR #20 (m6) gets the same ci.yml change as a parallel fixup. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Same ci.yml change as PR #19's fixup #2: Solana 3.0.10's bundled platform-tools v1.51 ships cargo 1.84 which can't parse `edition2024` manifests pulled transitively by Anchor 0.32.1's SPL deps. Replace the cached platform-tools directory with v1.54 (cargo 1.89) before anchor build invokes cargo-build-sbf. m6's Rust code itself is BPF-clean — `cargo-build-sbf` succeeds locally once platform-tools v1.54 is active. The only change here is the CI workflow step. If PR #19 lands first with the same ci.yml change, this PR's rebase resolves trivially (identical content). If PR #20 lands first, PR #19 rebases against this. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Locally reproduced: `anchor build` passes the BPF compile stage (cargo-build-sbf finishes clean with platform-tools v1.54 in place from fixup #2) but fails the IDL-generation stage: info: syncing channel updates for nightly-x86_64-unknown-linux-gnu error: could not download file from 'https://static.rust-lang.org/...' Error: Building IDL failed. Anchor 0.32.1's `anchor idl build` still invokes rustup to install a nightly toolchain, which the workflow's `dtolnay/rust-toolchain@stable` step doesn't pre-install. The CI runner can reach static.rust-lang.org in principle, but rustup's auto-install path needs an explicit toolchain declared. Fix: pass `--no-idl` to skip IDL generation. The on-chain program builds and verifies correctly without IDL; IDL is only required for TypeScript client type generation, which is a separate workstream (can land later as a CI step that installs nightly before invoking `anchor idl build`). Verified locally with anchor-cli 0.32.1 + platform-tools v1.54 + Solana 3.0.10: anchor build --no-idl → Finished `release` profile in 5.67s (clean) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Step-level CI timing on fixup #3 reveals the actual failure: Step 7 'Install Anchor CLI' completed in 0s conclusion=success Step 8 'Pin platform-tools v1.54' completed in 48s conclusion=success Step 9 'Anchor build' completed in 1s conclusion=failure `cargo binstall --no-confirm --version 0.32.1 anchor-cli` silently no-ops on anchor-cli 0.32.x — it exits 0 without installing `anchor` on PATH, then `anchor build` exits immediately (1s) because the binary doesn't exist. This is the same silent-no-op pattern I have in failure-pattern memory; I had closed PR #18 thinking cargo-binstall was working (based on PR #17's intermittent success), but it's actually flaky/broken for 0.32.x consistently. Fix: replace cargo binstall with `cargo install --locked --version 0.32.1 anchor-cli` + an `anchor --version` assertion. Source compile takes ~5-7 min on a cold cache but is cached by Swatinem/rust-cache@v2, so steady-state CI time is unchanged. The version assertion fails the install step itself on any future regression instead of deferring to the build step where the symptom is opaque (0s install + 1s build failure is harder to diagnose than a clean install-step failure). Combined with fixup #2 (platform-tools v1.54) and fixup #3 (--no-idl to skip nightly-Rust IDL generation), this should clear anchor build. Locally verified all three together produce a clean `anchor build --no-idl` in 5.67s on m5 and 5.02s on m6. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Wires the second half of GraveVault's settlement flow.
claim_lp_proceedsnow actually executes a claim instead of stubbing the proof + transfer:(lp_holder, lp_balance_at_snapshot)againstpool_registry.lp_snapshot_merkle_root(sealed bysalvage_poolat salvage time)lp_balance_at_snapshot > 0ANDlp_total_supply_at_snapshot > 0claimed + amount <= totallp_holder_pool_vaulttolp_holderviasystem_program::transfer(PDA-signs with its own seeds viainvoke_signed)ClaimRecordPDA (canonical double-claim defense — second claim by same(pool, holder)pair fails at theinitconstraint before lamports move)LpClaimProcessedPR scope (Option B, code-path split)
This PR is the claim_lp_proceeds side. The matching
salvage_poolexecution path (Raydium V4 + Jupiter + 40/40/20 distribution) is in PR #19 (m5). The two PRs have no file overlap:cpi/(new),instructions/salvage_pool.rs,errors.rs,constants.rsmerkle.rs(new),instructions/claim_lp_proceeds.rslib.rsandCHANGELOG.mdin non-overlapping sections (m5 addspub mod cpi;, m6 addspub mod merkle;)Whichever PR lands second will need a clean rebase; Git's three-way merge handles it.
What's in this PR
New file —
programs/grave-vault/src/merkle.rs:compute_leaf(holder, balance) -> [u8; 32]=sha256(pubkey || balance_le_u64)verify_proof(root, leaf, proof) -> bool= sorted-pair walk (OZ / Uniswap convention)Modified —
programs/grave-vault/src/instructions/claim_lp_proceeds.rs:require!(!params.merkle_proof.is_empty(), …)stub with a realmerkle::verify_proof(...)call againstpool_registry.lp_snapshot_merkle_rootTODO(GraveVault m6): SOL transfercomment with a realsystem_program::transferCPI signed bylp_holder_pool_vault's own seeds viainvoke_signedModified —
programs/grave-vault/src/lib.rs:+ pub mod merkle;Modified —
CHANGELOG.md:Error codes
No new error codes.
InvalidClaimProof(7010) andClaimAlreadyProcessed(7011) — both already on main — cover the m6 surface.docs/error_codes.mdunchanged.Test plan
cargo fmt --checkgreencargo clippy --all-targets -- -D warningsgreencargo test --lib --package grave-vault merkle— 7 unit tests passanchor buildgreenterminology-lintgreen (pre-checked locally: PASS)Unverified (transparent)
sha256(pubkey || balance_le_u64)) is documented inmerkle.rs— the off-chain builder MUST match it byte-for-byte. Indexer work is m9 (separate workstream).anchor build— deferred to CI per the validated parallel-track cadence.Honest disclaimers
Same ship-now-verify-after cadence as m5. Anything CodeRabbit or CI flags will be addressed as fix-up commits on this PR. The CHANGELOG enumerates what's not yet proven.
🤖 Generated with Claude Code