diff --git a/packages/contracts/foundry_test/CdpManager.Liquidation.t.sol b/packages/contracts/foundry_test/CdpManager.Liquidation.t.sol index 6a0a22015..ae4c8ef7e 100644 --- a/packages/contracts/foundry_test/CdpManager.Liquidation.t.sol +++ b/packages/contracts/foundry_test/CdpManager.Liquidation.t.sol @@ -847,6 +847,35 @@ contract CdpManagerLiquidationTest is eBTCBaseInvariants { ); } + function test_ZeroSurplus_WithFullLiq_ForICRLessThanLICR(uint256 ICR) public { + ICR = bound(ICR, cdpManager.MCR() + 1, cdpManager.CCR()); + + // ensure there is more than one CDP + _singleCdpSetup(users[0], 156e16); + (address user, bytes32 userCdpid) = _singleCdpSetup(users[0], ICR); + + // price drop to trigger liquidation + uint256 _originalPrice = priceFeedMock.fetchPrice(); + uint256 _newPrice = (_originalPrice * (cdpManager.LICR() - 1234567890123)) / ICR; + priceFeedMock.setPrice(_newPrice); + uint256 _currentICR = cdpManager.getICR(userCdpid, _newPrice); + assertTrue(cdpManager.getSyncedICR(userCdpid, _newPrice) < cdpManager.LICR()); + + // prepare liquidation + address _liquidator = users[users.length - 1]; + deal(address(eBTCToken), _liquidator, cdpManager.getCdpDebt(userCdpid)); // sugardaddy liquidator + + // ensure there is no surplus for full liquidation if bad debt generated + uint256 _surplusBalBefore = collSurplusPool.getSurplusCollShares(user); + uint256 _redistributedIndexBefore = cdpManager.systemDebtRedistributionIndex(); + vm.prank(_liquidator); + cdpManager.liquidate(userCdpid); + uint256 _surplusBalAfter = collSurplusPool.getSurplusCollShares(user); + uint256 _redistributedIndexAfter = cdpManager.systemDebtRedistributionIndex(); + assertTrue(_surplusBalBefore == _surplusBalAfter); + assertTrue(_redistributedIndexAfter > _redistributedIndexBefore); + } + function testFullLiquidation() public { // Set up a test case where the CDP is fully liquidated, with ICR below MCR or TCR in recovery mode // Call _liquidateIndividualCdpSetupCDP with the appropriate arguments diff --git a/packages/contracts/test/CdpManager_SimpleLiquidation_Test.js b/packages/contracts/test/CdpManager_SimpleLiquidation_Test.js index ab7a7e8c9..4ce9baced 100644 --- a/packages/contracts/test/CdpManager_SimpleLiquidation_Test.js +++ b/packages/contracts/test/CdpManager_SimpleLiquidation_Test.js @@ -1043,4 +1043,30 @@ contract('CdpManager - Simple Liquidation with external liquidators', async acco assert.isTrue(_batch5.length == 3 && _batch5[2] == _aliceCdpId && _batch5[1] == _bobCdpId && _batch5[0] == _carolCdpId); }) + it("Full liquidation should leave zero collateral surplus if bad debt generated", async () => { + let {tx: opAliceTx} = await openCdp({ ICR: toBN(dec(149, 16)), extraEBTCAmount: toBN(minDebt.toString()).add(toBN(1)), extraParams: { from: alice } }) + await openCdp({ extraEBTCAmount: toBN('951439999999999990'), extraParams: { from: bob, value: toBN('18311039310716208939') } }) + let _aliceCdpId = await sortedCdps.cdpOfOwnerByIndex(alice, 0); + let _bobCdpId = await sortedCdps.cdpOfOwnerByIndex(bob, 0); + let _oldPrice = await priceFeed.getPrice(); + console.log('cdpCollateral=' + (await cdpManager.getSyncedCdpCollShares(_bobCdpId))); + console.log('cdpDebt=' + (await cdpManager.getSyncedCdpDebt(_bobCdpId))); + + // shuffle a bit for the share rate to trigger liquidation since ICR < LICR + await collToken.setEthPerShare(toBN("827268193736210321")); + let _icr = await cdpManager.getSyncedICR(_bobCdpId, _oldPrice); + assert.isTrue(_icr.lt(LICR)); + let _tcr = await cdpManager.getSyncedTCR(_oldPrice); + assert.isTrue(_tcr.lt(CCR)); + + // full liquidation should leave bad debt and zero collateral for this CDP + let _surplusBalBefore = await collSurplusPool.getSurplusCollShares(bob); + let _redistributedIndexBefore = await cdpManager.systemDebtRedistributionIndex(); + await cdpManager.liquidate(_bobCdpId, {from: alice}); + let _surplusBalAfter = await collSurplusPool.getSurplusCollShares(bob); + let _redistributedIndexAfter = await cdpManager.systemDebtRedistributionIndex(); + assert.isTrue(_surplusBalBefore.eq(_surplusBalAfter)); + assert.isTrue(_redistributedIndexAfter.gt(_redistributedIndexBefore)); + }) + }) \ No newline at end of file