feat(liquidation): reserve pool backstop for bad debt vaults (#11) - #1298
Merged
Ceejaytech25 merged 2 commits intoSep 28, 2026
Merged
Ceejaytech25 merged 2 commits into
Ceejaytech25 merged 2 commits into
Conversation
|
@barry01-hash Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1152
Refs internal issue #11 (reserve pool backstop for bad debt). Upstream
ceejaylaboratory#11is not this issue, so this PR intentionally does not close it.Summary
Adds a reserve pool that absorbs bad debt, so an under-collateralised vault cannot sit on the books indefinitely. Before this, a vault whose collateral was worth less than its debt had no exit:
liquidateandpartial_liquidateboth liquidate for a liquidator's profit, and on a vault with no profitable collateral left there is nothing to pay them, so the position just lingered.liquidate_bad_debtprices the vault through the oracle, requirescollateral_value < debt_value, pays the debt in full from the reserve, takes the remaining collateral into the reserve, and closes the vault at zero.Design decisions worth reviewing
Permissionless, and with no
liquidatorargument, following the signature in the issue. The caller is not paid and gains nothing here, so requiring authorisation would only add friction to protocol maintenance everyone benefits from. A bad vault should never be holdable open by the absence of a liquidator.Two balances, not one.
reserve_debtis denominated in debt-token units because that is what has to be spent to clear a debt;reserve_collateralis denominated in collateral-token units because that is what gets seized. A single number would mix two different tokens, and denominating in oracle value would bake a price into a balance that has to be spent at a later, unknown price.Funding is permissionless too. The contract has no admin concept and adding one would have meant changing
initialize's signature and touching every existing caller, sofund_reserveauthorises the funder's own contribution instead. Anyone can recapitalise the backstop, which is the property you want from a reserve.The vault record is zeroed, not deleted. "Close vault record" is implemented as
debt = 0, collateral = 0rather than removing the storage entry, matching whatliquidatealready does. Deleting it would make a cleared vault panic onget_health_factorand break the existing guarantee that a cleared vault stays queryable —test_zero_debt_after_full_liquidation_is_safedepends on that.The event reports the shortfall in oracle value units (
debt_value - collateral_value), because the reserve debit is in token units and a reserve operator needs the value loss to size future funding:Refactor
get_health_factorwas pricing the vault inline; the pricing now lives in one privatevault_valueshelper shared withliquidate_bad_debt, so there is a single implementation of the oracle comparison. The arithmetic and its ordering are unchanged, and all 18 pre-existing tests pass untouched.Tests
11 new tests, 29 total, all passing:
test_liquidate_bad_debt_clears_vault_from_reserve— 1 collateral against 2 debt; vault closed to zero, reserve debited 2, 1 collateral unit absorbed.test_liquidate_bad_debt_is_driven_by_the_oracle— a vault at exactly par (HF 1.0) is not bad debt, then a collateral price halving makes it bad debt. This is the case raw token counts would get wrong.test_reserve_absorbs_collateral_from_several_vaults— balances accumulate across two vaults.test_liquidate_bad_debt_requires_a_funded_reserve— reverts withreserve pool cannot cover bad debtrather than clearing half a position.test_liquidate_bad_debt_rejects_vault_at_par,..._rejects_healthy_vault,..._rejects_vault_without_debt— all revert withvault is not under-collateralized.test_liquidate_bad_debt_reports_the_shortfall— asserts the full event tuple.test_liquidate_bad_debt_is_permissionless— no authorisation required.test_fund_reserve_accumulates,test_fund_reserve_rejects_zero.Verification
Limitations, deliberate and worth stating
fund_reserverecords a balance rather than transferring tokens, and the seized collateral is booked toreserve_collateralrather than held. Wiring the reserve to real token transfers is a separate piece of work; until then the reserve is a ledger, not a treasury.Interaction with #1295
liquidate_bad_debtonly triggers below HF 1.0, so it is orthogonal to the partial-liquidation work in #1295. It is worth noting that it is also the correct path for the deeply-underwater vaults whose pro-rata partial liquidation I flagged in #1295 as making the owner worse off — this gives the protocol a way to close those out properly.Note on CI
As with #1295 and #1297,
rust.ymldoes not build or testliquidation, so the 29 green tests are local evidence only. I have a one-line matrix change ready to add that crate to CI; say the word and I will send it as a separate PR so these three stay independently reviewable.