Summary
The function LendingPool::closeLiquidation()
does not take health factor into account, which can cause the borrower unable to close liquidation even though the collateral value increased
Vulnerability Details
The liquidation for a debt position can be initiated when health factor is lower than the threshold through function LendingPool::initiateLiquidation()
. After the liquidation is initiated, there is a grace period in which the borrower can close the liquidation.
The problem arises when there is scenario that the position collateral value decreases, which can initiate a liquidation. But when the collateral value goes up again, the borrower is unable to close the liquidation. This is because the function LendingPool::closeLiquidation()
does not check health factor but only check position's debt.
function initiateLiquidation(address userAddress) external nonReentrant whenNotPaused {
if (isUnderLiquidation[userAddress]) revert UserAlreadyUnderLiquidation();
ReserveLibrary.updateReserveState(reserve, rateData);
UserData storage user = userData[userAddress];
@> uint256 healthFactor = calculateHealthFactor(userAddress);
@> if (healthFactor >= healthFactorLiquidationThreshold) revert HealthFactorTooLow();
isUnderLiquidation[userAddress] = true;
liquidationStartTime[userAddress] = block.timestamp;
emit LiquidationInitiated(msg.sender, userAddress);
}
function closeLiquidation() external nonReentrant whenNotPaused {
address userAddress = msg.sender;
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
ReserveLibrary.updateReserveState(reserve, rateData);
if (block.timestamp > liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodExpired();
}
UserData storage user = userData[userAddress];
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
@> if (userDebt > DUST_THRESHOLD) revert DebtNotZero();
isUnderLiquidation[userAddress] = false;
liquidationStartTime[userAddress] = 0;
emit LiquidationClosed(userAddress);
}
PoC
Add the test to file test/unit/core/pools/LendingPool/LendingPool.test.js
describe("Liquidation", function () {
...
it('Borrowers are unable to close liquidation even collateral value increased', async function() {
await raacHousePrices.setHousePrice(1, ethers.parseEther("90"));
await lendingPool.connect(user2).initiateLiquidation(user1.address)
await raacHousePrices.setHousePrice(1, ethers.parseEther("500"));
await lendingPool.connect(user1).closeLiquidation();
})
Run the test and console shows:
LendingPool
Liquidation
1) Borrowers are unable to close liquidation even collateral value increased
0 passing (2s)
1 failing
1) LendingPool
Liquidation
Borrowers are unable to close liquidation even collateral value increased:
Error: VM Exception while processing transaction: reverted with custom error 'DebtNotZero()'
at LendingPool.closeLiquidation (contracts/core/pools/LendingPool/LendingPool.sol:533)
Impact
Tools Used
Manual
Recommendations
Also add a health check in function LendingPool::closeLiquidation()