[WIP] feat: Partial Liquidation#374
Open
vlad-woof-software wants to merge 84 commits into
Open
Conversation
…sible to reach targetHF to avoid underflow error
- Fix 1: correct numerator to (targetHF × debt − totalCV_CF) / (LP × targetHF − CF) instead of wrong LF-weighted totalCollaterizedValue2 - Fix 2: seizeAmount = rawCollateralUSD / price; seizedValue = rawCollateralUSD × LP (consistent with full-seizure path semantics) - Fix 3: replace guard condition with denominator-positivity check + available balance check; remove pre-loop that computed totalCollaterizedValue2; simplify LiquidationData struct from 10 fields to 4
- Fix 4 incorrect finalIsLiquidatable assertions (true→false): after absorb account always exits liquidation zone regardless of path taken - Add 3 new tests: * formula accuracy: verifies rawCollateralUSD, remaining balance > 0, HF ≈ targetHF * multi-collateral mixed: full COMP seizure then partial WETH seizure * full liquidation fallback: all collateral exhausted, debt zeroed by reserves
…am and stale NatSpec
…into wip-partial
| uint256 seizedAmount; | ||
| uint256 seizedValue; | ||
|
|
||
| for (uint8 i; i < numAssets; ++i) { |
…t 6 decimals base token)
…terals to cover the debt, but full siezure becomes
Comment on lines
+145
to
+151
| if (assetConfig.borrowCollateralFactor != 0 && assetConfig.liquidateCollateralFactor == 0) { | ||
| revert CometMainInterface.BorrowCFTooLarge(); | ||
| } else if (assetConfig.borrowCollateralFactor != 0 && assetConfig.liquidateCollateralFactor != 0) { | ||
| // Ensure collateral factors are within range | ||
| if (assetConfig.borrowCollateralFactor > assetConfig.liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge(); | ||
| if (assetConfig.liquidateCollateralFactor > MAX_COLLATERAL_FACTOR) revert CometMainInterface.LiquidateCFTooLarge(); | ||
| } |
Comment on lines
+147
to
+151
| } else if (assetConfig.borrowCollateralFactor != 0 && assetConfig.liquidateCollateralFactor != 0) { | ||
| // Ensure collateral factors are within range | ||
| if (assetConfig.borrowCollateralFactor > assetConfig.liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge(); | ||
| if (assetConfig.liquidateCollateralFactor > MAX_COLLATERAL_FACTOR) revert CometMainInterface.LiquidateCFTooLarge(); | ||
| } |
Comment on lines
+160
to
+165
| if (borrowCollateralFactor != 0 && liquidateCollateralFactor == 0) { | ||
| revert CometMainInterface.BorrowCFTooLarge(); | ||
| } else if (borrowCollateralFactor != 0 && liquidateCollateralFactor != 0) { | ||
| // Be nice and check descaled values are still within range | ||
| if (borrowCollateralFactor >= liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge(); | ||
| } |
Comment on lines
+162
to
+165
| } else if (borrowCollateralFactor != 0 && liquidateCollateralFactor != 0) { | ||
| // Be nice and check descaled values are still within range | ||
| if (borrowCollateralFactor >= liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge(); | ||
| } |
| */ | ||
| function pauseCollateralAssetSupply(uint24 assetIndex, bool paused) override external onlyGovernorOrPauseGuardian isValidAssetIndex(assetIndex) { | ||
| if ((collateralsSupplyPauseFlags & (uint24(1) << assetIndex) != 0) == paused) revert CollateralAssetOffsetStatusAlreadySet(collateralsSupplyPauseFlags, assetIndex, paused); | ||
| if (!paused && isCollateralDeactivated(assetIndex)) revert CollateralIsDeactivated(assetIndex); |
| */ | ||
| function pauseCollateralAssetTransfer(uint24 assetIndex, bool paused) override external onlyGovernorOrPauseGuardian isValidAssetIndex(assetIndex) { | ||
| if ((collateralsTransferPauseFlags & (uint24(1) << assetIndex) != 0) == paused) revert CollateralAssetOffsetStatusAlreadySet(collateralsTransferPauseFlags, assetIndex, paused); | ||
| if (!paused && isCollateralDeactivated(assetIndex)) revert CollateralIsDeactivated(assetIndex); |
| for (uint8 i; i < numAssets; ) { | ||
| if (isInAsset(assetsIn, i, _reserved)) { | ||
| asset = getAssetInfo(i); | ||
| if (fetchedCollateralPrices.length == 0) collateralPrices[i] = getPrice(asset.priceFeed); |
Comment on lines
+1269
to
+1295
| if (liquidation && asset.liquidateCollateralFactor == 0) { | ||
| unchecked { ++i; } | ||
| continue; | ||
| } else { | ||
| // Block ALL borrow-side actions when the borrower still holds deactivated collateral. | ||
| // This revert is intentionally broad: it prevents borrowing, withdrawing other | ||
| // collateral, and transferring — even if the remaining active collateral would | ||
| // pass the collateralization check on its own. The purpose is to force the | ||
| // borrower to withdraw the deactivated collateral FIRST before doing anything | ||
| // else (see the deactivation lifecycle comment on isCollateralDeactivated). | ||
| // | ||
| // If the borrower cannot withdraw the deactivated collateral without becoming | ||
| // under-collateralized, they are stuck and must wait for liquidation. | ||
| if (isCollateralDeactivated(asset.offset)) revert TokenIsDeactivated(asset.asset); | ||
|
|
||
| // Skip assets with borrowCollateralFactor == 0 — they provide no | ||
| // borrowing power, so mulFactor(value, 0) would add nothing to liquidity. | ||
| // More critically, this avoids calling getPrice() for their price feed: | ||
| // if a non-contributing asset's oracle reverts (stale, broken, decommissioned), | ||
| // it would otherwise block the entire collateralization check, paralyzing | ||
| // borrows and transfers for every account that holds that asset — even though | ||
| // the asset has zero influence on their borrow capacity. | ||
| if (asset.borrowCollateralFactor == 0) { | ||
| unchecked { ++i; } | ||
| continue; | ||
| } | ||
| } |
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.
Overview
This PR introduces partial liquidation support to the Comet protocol.
Instead of always seizing all collateral from an underwater account,
the liquidator now brings the account's health factor up to a configured
targetHealthFactorthreshold - seizing only as much collateral as needed.This reduces unnecessary value extraction from borrowers and better aligns
with the protocol RFC.
What has been done
Configuration
targetHealthFactormoved intoCometConfiguration.Configurationstruct - no longer a separate contract/interface, but afirst-class config field
targetHealthFactorinitialized inCometandScrollCometconstructors from configtargetHealthFactor()added toCometMainInterfaceConfiguratorupdated to exposesetTargetHealthFactormanagementCore logic
CometWithExtendedAssetList.absorbInternal: iterates collateral assets and eitherpartially seizes one asset (stopping at
targetHF) or fully seizes it and moves to the next∆ = (targetHF × debt − totalCV) × FACTOR_SCALE / (LP × targetHF − CF)targetHF == 0Tests
test/partial-liquidation-test.tscovering basic scenarios: single-collateral partial seizure,multi-collateral mixed seizure, full-liquidation fallback
targetHealthFactorconfig field and updatedmakeProtocolsetupWhat is still pending (reason for WIP)
baseBorrowMinboundary, dust positions, rounding behaviour)baseBorrowMincheck: if remaining debt after partial seizure falls belowbaseBorrowMin, the protocol should fall through tofull liquidation instead
LP × targetHF > CFfor all assets (prevents invalid configuration that would cause revert ordivision by zero at runtime)
console.logdebug calls from contracts