Summary
Attackers can initiate multiple liquidations on an account without cost, making it impossible for users to recover.
Vulnerability Details
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);
}
Impact
Tools Used
Recommendations
Require liquidators to stake a deposit that is lost if the liquidation fails.
Implement a minimum delay between liquidation attempts.
mapping(address => uint256) public lastLiquidationAttempt;
uint256 public constant MIN\_LIQUIDATION\_DELAY = 1 hours;
uint256 public constant LIQUIDATION\_BOND = 1 ether;
function initiateLiquidation(address userAddress) external payable nonReentrant whenNotPaused {
require(msg.value >= LIQUIDATION\_BOND, "Insufficient liquidation bond");
require(block.timestamp > lastLiquidationAttempt\[userAddress] + MIN\_LIQUIDATION\_DELAY, "Too soon");
lastLiquidationAttempt[userAddress] = block.timestamp;
if (!isUserEligibleForLiquidation(userAddress)) {
payable(msg.sender).transfer(msg.value / 2);
revert UserNotEligibleForLiquidation();
}
isUnderLiquidation[userAddress] = true;
liquidationStartTime[userAddress] = block.timestamp;
}