Summary
The StabilityPool.liquidateBorrower() function covers user's debt. However, it calculates user debt incorrectly and uses inflated value for user debt.
Vulnerability Details
The StabilityPool.liquidateBorrower() function calculates the amount of user debt by invoking lendingPool.getUserDebt(userAddress).
The lendingPool.getUserDebt(userAddress) function returns normalized amount of debt, by multiplying user.scaledDebtBalance by reserve.usageIndex. However, StabilityPool.liquidateBorrower() function mutiplies this value by reserve.usageIndex again.
As result, userDebt will be scaledAmount * reserve.usageIndex ^ 2 and be inflated. The stability pool will use inflated assets to cover user's debt.
function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
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.updateState();
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}
function getUserDebt(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
>> return user.scaledDebtBalance.rayMul(reserve.usageIndex);
}
Impact
The stability pool uses inflated assets to liquidate user debt.
Tools Used
Manual Review
Recommendations
Remove multiplication by reserve.usageIndex
function liquidateBorrower(address userAddress) external onlyManagerOrOwner nonReentrant whenNotPaused {
_update();
// Get the user's debt from the LendingPool.
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();
+ if (crvUSDBalance < userDebt) revert InsufficientBalance();
// Approve the LendingPool to transfer the debt amount
- bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
+ bool approveSuccess = crvUSDToken.approve(address(lendingPool), userDebt);
if (!approveSuccess) revert ApprovalFailed();
// Update lending pool state before liquidation
lendingPool.updateState();
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);
- emit BorrowerLiquidated(userAddress, scaledUserDebt);
+ emit BorrowerLiquidated(userAddress, userDebt);
}