Skip to content

fix(stake): verify wrapper_vault SPL Token ownership before raw mint read in FlushToInsurance - #227

Merged
dcccrypto merged 1 commit into
dcccrypto:mainfrom
Ayomisco:fix/stake-n5-wrapper-vault-owner-check
Jun 23, 2026
Merged

fix(stake): verify wrapper_vault SPL Token ownership before raw mint read in FlushToInsurance#227
dcccrypto merged 1 commit into
dcccrypto:mainfrom
Ayomisco:fix/stake-n5-wrapper-vault-owner-check

Conversation

@Ayomisco

@Ayomisco Ayomisco commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #226

Change

Adds wrapper_vault.owner == spl_token::id() check in process_flush_to_insurance before the raw data[0..32] mint read, matching the guard already present in pre_accrue_mode1, process_accrue_fees, and process_return_insurance.

Why

A non-token account with forged bytes at offset [0..32] matching pool.collateral_mint passes the existing mint check. The SPL Token program's CPI validates the account on its side, but an explicit owner check here is consistent with every other vault-touching handler in the file and eliminates the inconsistency.

Test plan

  • Existing test suite passes (cargo test)
  • Passing a non-SPL-Token-owned account as wrapper_vault now returns StakeError::InvalidAccount instead of propagating to the CPI

Summary by CodeRabbit

  • Bug Fixes
    • Added validation to verify token account ownership before processing, preventing potential errors when handling invalid accounts.

…ead in FlushToInsurance

process_flush_to_insurance reads wrapper_vault data[0..32] as mint without first
confirming wrapper_vault.owner == spl_token::id(). A crafted account owned by any
program with forged bytes at offset [0..32] matching the collateral mint passes the
check. Every other vault-touching handler (pre_accrue_mode1, process_accrue_fees,
process_return_insurance) already performs this owner check. This adds the missing
guard immediately before the raw data borrow.
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b150702d-47b4-4052-b9d2-e59bd0124b5e

📥 Commits

Reviewing files that changed from the base of the PR and between 5f6ffa7 and 00ceba8.

📒 Files selected for processing (1)
  • src/processor.rs

📝 Walkthrough

Walkthrough

In process_flush_to_insurance within src/processor.rs, a seven-line early guard is added that checks whether wrapper_vault.owner equals crate::spl_token::id() before borrowing raw account data and comparing mint bytes. Non-token accounts now fail immediately with StakeError::InvalidAccount.

Changes

SPL Token owner guard in process_flush_to_insurance

Layer / File(s) Summary
Early SPL Token owner check for wrapper_vault
src/processor.rs
Before the raw [0..32] mint-byte read, a guard now verifies wrapper_vault.owner == spl_token::id() and returns StakeError::InvalidAccount on mismatch, matching the ownership checks already present in other vault-touching handlers.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related issues

Possibly related PRs

  • dcccrypto/percolator-stake#99: Adds SPL token-program ownership verification in a different handler (process_admin_withdraw_insurance) in the same src/processor.rs file, following the same defensive pattern.
  • dcccrypto/percolator-stake#208: Strengthens SPL Token program owner checks before raw token-account byte reads in deposit/withdraw paths of src/processor.rs, directly analogous to this fix.

Poem

🐇 Hoppy the rabbit checked the vault one day,
"Is this SPL Token? Let me check, I say!"
If the owner's wrong, we bounce right out,
No forged bytes sneaking past my snout.
InvalidAccount! Off you go!
Only real token vaults allowed below. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main security fix: adding SPL Token ownership verification for wrapper_vault before the raw mint read in process_flush_to_insurance.
Description check ✅ Passed The description provides clear context about the fix, why it's needed, and includes a test plan. However, it lacks the 'How to test' section from the template with specific testing instructions.
Linked Issues check ✅ Passed The PR directly addresses issue #226 by adding the missing SPL Token ownership check before the raw mint read, matching the implementation in other vault-touching handlers.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #226: adding the wrapper_vault SPL Token ownership verification in process_flush_to_insurance. No out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dcccrypto
dcccrypto merged commit 4ef7798 into dcccrypto:main Jun 23, 2026
1 check passed
dcccrypto added a commit that referenced this pull request Jun 23, 2026
)

Implements the N-2 timelock that #243 prepared (TIMELOCK_SLOTS). The capital-LOCK
vector in #242 is a cooldown_slots INCREASE: a compromised admin could set
cooldown = MAX_COOLDOWN_SLOTS (~1 year) in a single UpdateConfig tx and lock LP
withdrawals instantly. That increase now requires a two-phase, time-delayed commit.

- ProposeCooldownIncrease (tag 7, reclaimed) — admin records the new (larger) value +
  proposal slot. Rejects non-increases (those use UpdateConfig).
- CommitCooldownIncrease (tag 8) — applies the pending value ONLY after TIMELOCK_SLOTS
  (~48h) have elapsed, giving LP holders a guaranteed exit window.
- CancelCooldownIncrease (tag 9) — admin withdraws a pending proposal.
- UpdateConfig now REJECTS a cooldown INCREASE (CooldownIncreaseRequiresTimelock);
  decreases (LP-friendly) and deposit_cap stay immediate.

State: pending_cooldown_slots + cooldown_proposed_at_slot stored in the previously-free
_reserved[10..26] — no struct-size change, no version bump (fresh-start cutover).
3 new errors (25/26/27). FlushToInsurance / SetMarketResolved intentionally stay
immediate (PDA-custody + #227/#229 guards / one-way operational — see N-2 comment).

Verification:
- timelock_window_elapsed extracted as a pure helper; 3 unit tests + 2 accessor tests +
  collision-with-neighbors test; instruction round-trip tests; tombstone tests updated.
- Kani proof kani_timelock_window_elapsed_matches_spec: VERIFIED (293 properties, 2/2
  cover properties satisfied — non-vacuous, both elapsed/not-elapsed reachable, overflow
  is Err not panic).
- 138 lib tests + full integration suite (incl. LiteSVM e2e) pass, 0 failures;
  cargo build-sbf clean. NOT deployed (cutover-gated).

Closes #242.

Co-authored-by: dcccrypto <dcccrypto@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[M] FlushToInsurance: wrapper_vault SPL Token ownership not verified before raw mint read

2 participants