Summary
The liquidation mechanism in the Stability Pool contract can fail due to incorrect ordering of state updates and debt calculations. The user's scaled debt is calculated before updating the lending pool state, leading to potential discrepancies between the approved amount and the actual debt that needs to be repaid.
Vulnerability Details
The issue occurs in two parts:
In StabilityPool.sol's liquidateBorrower()
:
function liquidateBorrower(address userAddress) external {
_update();
uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
lendingPool.updateState();
lendingPool.finalizeLiquidation(userAddress);
}
In LendingPool.sol's finalizeLiquidation()
:
function finalizeLiquidation(address userAddress) external {
ReserveLibrary.updateReserveState(reserve, rateData);
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
}
The vulnerability arises because:
StabilityPool calculates and approves debt based on old state
LendingPool updates state, changing the actual debt amount
The approved amount becomes insufficient for the actual debt
Impact
Liquidations will fail when debt amounts change due to state updates
Tools Used
Recommendations
function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
lendingPool.updateState();
uint256 userDebt = lendingPool.getUserDebt(userAddress);
uint256 scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
if (userDebt == 0) revert InvalidAmount();
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
if (!approveSuccess) revert ApprovalFailed();
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}