Skip to content

fix: allow fully wiped junior LP exits - #181

Merged
dcccrypto merged 1 commit into
dcccrypto:mainfrom
Bayyan16:fix/junior-zero-payout-withdraw
Jun 19, 2026
Merged

fix: allow fully wiped junior LP exits#181
dcccrypto merged 1 commit into
dcccrypto:mainfrom
Bayyan16:fix/junior-zero-payout-withdraw

Conversation

@Bayyan16

@Bayyan16 Bayyan16 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #180.

This PR fixes the fully-wiped junior LP exit path where a junior LP position with zero remaining effective collateral could not be closed because the withdraw flow rejected withdrawal_amount == 0 before LP burn and cleanup logic could run.

The withdraw path now only permits a zero-payout withdrawal when all of the following are true:

  • tranches are enabled
  • the position is junior
  • pool.effective_junior_balance() == 0
  • withdrawal_amount == 0

For that specific terminal state, the SPL token transfer is skipped, and HWM enforcement is bypassed because the zero-payout cleanup cannot reduce TVL. The LP burn and deposit/tranche cleanup path can still execute.

Why

A fully-wiped junior LP can legitimately have no remaining collateral claim after absorbing losses.

Previously, the generic ZeroAmount guard rejected the withdrawal before the LP burn and cleanup path. After allowing the zero-payout path, HWM enforcement also needs to avoid blocking this specific cleanup scenario because no collateral leaves the pool.

This PR keeps zero-payout withdrawals rejected for normal/non-terminal cases, while allowing cleanup of a fully-wiped junior LP position.

Changes

  • Added a fully_wiped_junior_exit condition in process_withdraw.
  • Kept StakeError::ZeroAmount for all zero-payout withdrawals except fully-wiped junior exits.
  • Bypassed HWM enforcement only for fully-wiped junior zero-payout cleanup.
  • Skipped the SPL token transfer when withdrawal_amount == 0.
  • Preserved the existing burn/accounting cleanup path after the transfer section.
  • Added a regression test for the fully-wiped junior LP zero-payout exit case, including HWM-blocked setup coverage.

How to test

  1. Build the SBF program:
cargo build-sbf
  1. Run the targeted regression test:
cargo test poc_fully_wiped_junior_lp_cannot_exit_due_zero_payout_guard -- --nocapture
  1. Run the v17 insurance E2E test:
cargo test --test v17_stake_insurance_e2e -- --nocapture
  1. Run the full test suite:
cargo test

Checklist

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

process_withdraw gains a fully_wiped_junior_exit flag that bypasses the ZeroAmount error when a junior tranche depositor holds LP against a fully-depleted junior balance. The vault-to-user SPL token transfer is now conditional on withdrawal_amount > 0. A regression integration test verifies the zero-payout path is accepted.

Fully-wiped junior LP zero-payout exit

Layer / File(s) Summary
ZeroAmount bypass and conditional transfer in process_withdraw
src/processor.rs
Introduces fully_wiped_junior_exit computed from tranche_enabled && is_junior && effective_junior_balance() == 0 && withdrawal_amount == 0, uses it to skip the ZeroAmount error, and wraps the vault-to-user invoke_signed SPL transfer in a withdrawal_amount > 0 guard so no zero-amount transfer CPI is issued.
Regression test
tests/integration.rs
Adds poc_fully_wiped_junior_lp_cannot_exit_due_zero_payout_guard: configures a pool where the full junior deposit is flushed as loss, asserts effective_junior_balance() is zero, confirms calc_junior_collateral_for_withdraw yields zero, and asserts the zero-payout exit condition is treated as valid.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • dcccrypto/percolator-stake#98: Modifies the same process_withdraw junior-withdrawal collateral path to use effective_junior_balance(), which is the root value this PR checks for zero.
  • dcccrypto/percolator-stake#103: Also touches process_withdraw junior full-wipe handling, specifically zeroing junior_balance when all junior LP is burned — directly adjacent to the cleanup path this PR now allows to run.
  • dcccrypto/percolator-stake#172: Modifies the same withdrawal code path to clear and re-initialize the deposit record on full withdrawal, which is the cleanup state this PR's fix now unblocks for zero-payout junior exits.

Poem

🐇 The junior was wiped, its balance was nil,
Yet the processor blocked it — what a steep hill!
A guard was inserted, a flag raised up tall:
"If fully wiped, skip the transfer, don't stall."
Now the worthless LP burns clean and free,
Hopping past ZeroAmount, as it ought to be! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: allow fully wiped junior LP exits' is concise and directly describes the main change in the PR.
Linked Issues check ✅ Passed The PR directly addresses issue #180 by implementing the exact fix suggested: introducing a fully_wiped_junior_exit condition, rejecting zero-payout withdrawals except for that case, skipping the SPL transfer when needed, and preserving cleanup logic. A regression test is also included.
Out of Scope Changes check ✅ Passed All changes in processor.rs and integration.rs are directly related to fixing the fully-wiped junior LP exit issue described in #180, with no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description comprehensively addresses the issue with detailed context, changes made, testing instructions, and a complete checklist.

✏️ 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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/integration.rs (1)

888-889: 💤 Low value

Consider renaming the test to reflect the fix rather than the bug.

The current name poc_fully_wiped_junior_lp_cannot_exit_due_zero_payout_guard reads as though it tests the broken behavior. Since the assertion confirms the fix works (zero-payout exit is allowed), a name like test_fully_wiped_junior_lp_can_exit_with_zero_payout would better convey intent.

🤖 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 `@tests/integration.rs` around lines 888 - 889, The test function name
`poc_fully_wiped_junior_lp_cannot_exit_due_zero_payout_guard` is misleading
because it suggests the test validates that the junior LP cannot exit, when the
test actually verifies that the junior LP can exit with zero payout (the fix).
Rename this test function to
`test_fully_wiped_junior_lp_can_exit_with_zero_payout` or similar to accurately
reflect that it validates the fixed behavior where exiting with zero payout is
allowed.
🤖 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 `@tests/integration.rs`:
- Around line 888-889: The test function name
`poc_fully_wiped_junior_lp_cannot_exit_due_zero_payout_guard` is misleading
because it suggests the test validates that the junior LP cannot exit, when the
test actually verifies that the junior LP can exit with zero payout (the fix).
Rename this test function to
`test_fully_wiped_junior_lp_can_exit_with_zero_payout` or similar to accurately
reflect that it validates the fixed behavior where exiting with zero payout is
allowed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53109fb2-0b1f-477d-b658-ca4a16d0d95f

📥 Commits

Reviewing files that changed from the base of the PR and between 666d283 and b8d9440.

📒 Files selected for processing (2)
  • src/processor.rs
  • tests/integration.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8d9440993

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/processor.rs
Comment on lines +955 to +958
let fully_wiped_junior_exit = pool.tranche_enabled()
&& is_junior
&& pool.effective_junior_balance() == 0
&& withdrawal_amount == 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bypass HWM for zero-payout junior cleanup

When HWM is enabled and prior withdrawals plus a later insurance flush leave the pool already below the current HWM floor, this new exception only bypasses ZeroAmount; the withdraw then falls through to the HWM check below, where post_tvl == current_tvl and the zero-payout cleanup is still rejected. Because a fully wiped junior exit transfers no collateral and cannot further reduce TVL, HWM should not block this path or the worthless junior LP can remain unburnable in exactly the cleanup scenario this change is meant to allow.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in the latest push.

The zero-payout fully-wiped junior exit now bypasses HWM enforcement because no collateral leaves the pool and the cleanup cannot reduce TVL. I also added regression coverage for an HWM-blocked setup to ensure this terminal cleanup path can still proceed while normal zero-payout withdrawals remain rejected.

@Bayyan16
Bayyan16 force-pushed the fix/junior-zero-payout-withdraw branch from b8d9440 to 5c50b87 Compare June 19, 2026 10:55
@dcccrypto
dcccrypto merged commit eb423b3 into dcccrypto:main Jun 19, 2026
1 check passed
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.

[MEDIUM] Fully-wiped junior LP positions cannot exit because zero-collateral withdrawals are rejected

2 participants