Conversation
- Partial liquidation sequences across multi-collateral positions - Selective collateral seizure in multi-asset positions - Atomic DEX liquidation failure on insufficient vault liquidity - Oracle-deviation circuit breaker (slippage > dexOracleDeviationBps reverts) - Stability and insurance fee accrual over 1 year with continuous compounding - Bad debt handling: zombie position after complete collateral seizure
…to taras/173-liquidation-edge-cases
|
|
||
| createAndStorePool(signer: MAINNET_PROTOCOL_ACCOUNT, defaultTokenIdentifier: MAINNET_MOET_TOKEN_ID, beFailed: false) | ||
|
|
||
| // Setup pool with real mainnet token prices |
There was a problem hiding this comment.
real mainnet token prices
Kind of a nitpick but I wouldn't say the prices are "real" when we're hard-coding them in a mock oracle 😅
| // Setup pool with real mainnet token prices | |
| // Setup pool with plausible mainnet token prices |
| // DEX Liquidity Constraints | ||
| // | ||
| // Scenario: The DEX vault holds only 50% of the debt tokens needed to repay | ||
| // the liquidation. A batch DEX liquidation fails atomically, leaving the | ||
| // position unchanged and still unhealthy. After topping up the DEX vault, | ||
| // the same liquidation parameters succeed. | ||
| // | ||
| // Position: 200 FLOW @ $1.00 (CF=0.80), borrow 130 USDF | ||
| // health = 200*1.0*0.80 / 130 = 160/130 ≈ 1.2308 | ||
| // FLOW crash: $1.00 -> $0.75 | ||
| // health = 200*0.75*0.80 / 130 = 120/130 ≈ 0.9231 (unhealthy) | ||
| // Liquidation params: seize 55 FLOW, repay 46 USDF | ||
| // DEX priceRatio (FLOW->USDF) = 0.75 | ||
| // seize 55 < repay/ratio = 46/0.75 = 61.33 (passes DEX check) | ||
| // post-health = (200-55)*0.75*0.80 / (130-46) = 87/84 ≈ 1.036 (within 1.05 target) | ||
| // Scenario 1: DEX vault funded with 23 USDF (50% of 46 needed) -> liquidation reverts | ||
| // Scenario 2: top up to 53 USDF (>=46) -> liquidation succeeds |
There was a problem hiding this comment.
I'm not sure where the list of test cases from the issue came from, but I suspect this test case was proposed under the assumption that we have automated liquidation. When automated liquidation is implemented, there will be a code path in FlowALP which swaps using a DEX. When that is true, it will be useful to test that code path while simulating various conditions for the DEX, including liquidity constraints. However, in this test case:
- The interaction with FlowALP is an invocation of the
manualLiquidationfunction, where we pass in debt repayment funds - We happen to get the repayment funds by swapping through a (mock) DEX, but FlowALP doesn't see any of that -- it just gets the funds. Conceptually it's behaviour can't be different depending on where the funds originated.
- All the DEX interactions before
manualLiquidationare using mocks and test-only code. There is no additional production code beyondmanualLiquidationthat we are validating.
Essentially, I don't think this test case is applicable with the current liquidation logic. Let me know what you think.
| } | ||
|
|
||
| // ============================================================================= | ||
| // Stability and Insurance Fee Accrual — 1 year before liquidation |
There was a problem hiding this comment.
What's the idea behind this test case? What kinds of interactions between liquidation and fee collection are we trying to validate?
| } | ||
|
|
||
| // ============================================================================= | ||
| // Liquidation Slippage Constraints |
There was a problem hiding this comment.
This test seems to mostly duplicate the coverage area of the tests matching testManualLiquidation_dexOraclePriceDivergence.* in cadence/tests/liquidation_phase1_test.cdc.
| } | ||
|
|
||
| // ============================================================================= | ||
| // Bad Debt Handling |
There was a problem hiding this comment.
This seems to mostly duplicate the coverage area of testManualLiquidation_reduceHealth in cadence/tests/liquidation_phase1_test.cdc
Closes: #173
Note: should be reviewed after merging #172
Some of proposed test in task weren't implemented because Flow doesn't have gas-price-based ordering at all.
Summary
Adds mainnet fork tests for liquidation edge cases: partial sequences, multi-collateral seizure, DEX liquidity failures, oracle-deviation circuit breaker, fee accrual over time, and bad debt handling.
Tests added (fork_liquidation_edge_cases.cdc)
testPartialLiquidationSequencesFive positions with distinct collateral types (FLOW, USDF, USDC, WETH, WBTC), each borrowing MOET at health ≈ 1.1. After a FLOW price crash, only the FLOW position becomes unhealthy (health 0.95). A liquidator partially restores it across three sequential calls (seize 10 / repay 20 MOET each), stepping health through 0.967 → 0.985 → 1.005. A fourth call and a second liquidator's attempt both revert once the position is healthy. Verifies that positions backed by unaffected collateral are untouched.
testLiquidateMultiCollateralChooseUSDCA single position holds FLOW + USDC + WETH as collateral with USDF debt (health ≈ 1.109). After a FLOW crash ($1.00 → $0.75), the position drops to health ≈ 0.935. The liquidator selectively seizes USDC (seize 40 USDC, repay 55 USDF) while FLOW and WETH balances remain untouched. Validates that a liquidator can choose the optimal collateral without disturbing other assets.
testDexLiquidityConstraintsA MockDexSwapper vault is seeded with only 50% of the required repayment tokens (23 USDF instead of 46). The batch DEX liquidation reverts atomically, leaving position state unchanged. After topping up the DEX vault to 53 USDF, the identical parameters succeed. Verifies that insufficient DEX liquidity causes a clean, state-preserving failure rather than a partial execution.
testLiquidationSlippageConstraintsGovernance tightens dexOracleDeviationBps from the default 300 bps to 200 bps. Two manual liquidation attempts use the same seize/repay amounts but different DEX price ratios:
Scenario 1 (priceRatio 0.7275, deviation ≈ 309 bps > 200 bps max) → reverts, position unchanged.
Scenario 2 (priceRatio 0.7425, deviation ≈ 101 bps < 200 bps max) → succeeds, post-health ≈ 1.036.
Validates the oracle-deviation guard that prevents liquidators from extracting value at stale DEX prices.
testStabilityAndInsuranceFeeAccrualSets a 10% annual fixed interest rate on USDF with 10% stability fee and 10% insurance fee. An LP deposits 5000 USDF; a borrower takes 130 USDF against 200 FLOW. After Test.moveTime(by: ONE_YEAR), debt compounds to ≈ 143.67 USDF (continuous compounding: 130 × e^0.10). A liquidator then partially restores health. Fee collection is verified:
Stability fund receives ≈ 0.705 USDF (debitBalance 67 × (e^0.10 − 1) × 10%)
Insurance fund receives ≈ 0.705 MOET (swapped 1:1 via MockDex)
LP earns ≈ 416.435 USDF credit income (5000 × (e^0.08 − 1) — FixedRate applies creditRate to the full LP deposit, not just outstanding debt)