Summary
The closeLiquidation function forces users to repay their entire debt to avoid liquidation, preventing partial repayments that could restore position health and save the collateral.
Vulnerability Details
The function requires debt to be completely repaid (below DUST_THRESHOLD) rather than allowing partial repayments that could bring the health factor back above the liquidation threshold:
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();
}
Impact
Users who could partially repay to restore health factor lose their entire collateral
Forces unnecessary full repayments when partial ones would suffice
Reduces protocol capital efficiency
This may lead to more liquidations than necessary
Tools Used
Manual review
Recommendations
Allow partial repayments during the grace period and add repayment parameter to closeLiquidation
function closeLiquidation(uint256 repayAmount) external nonReentrant whenNotPaused {
address userAddress = msg.sender;
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
ReserveLibrary.updateReserveState(reserve, rateData);
_repay(repayAmount, userAddress);
uint256 healthFactor = calculateHealthFactor(userAddress);
if (healthFactor >= healthFactorLiquidationThreshold) {
isUnderLiquidation[userAddress] = false;
liquidationStartTime[userAddress] = 0;
emit LiquidationClosed(userAddress);
} else {
revert HealthFactorNotRestored();
}
}