Summary
The StabilityPool#liquidateBorrower()
allows both the protocol owner and assigned managers to perform liquidations. However, there is no requirement for managers to have sufficient funds before calling this function, leading to potential trust issues and mismanagement.
Vulnerability Details
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);
}
The function allows any assigned manager to trigger a liquidation without checking if they actually contributed funds to the StabilityPool
.
This means managers could be appointed without any financial stake in the protocol, yet still control liquidations.
But in fact, a manager has own allocation.
function addManager(address manager, uint256 allocation) external onlyOwner validAmount(allocation) {
if (managers[manager]) revert ManagerAlreadyExists();
managers[manager] = true;
@> managerAllocation[manager] = allocation;
totalAllocation += allocation;
managerList.push(manager);
emit ManagerAdded(manager, allocation);
}
The protocol relies on trust that managers act in the best interest of the system.
Impact
A manager without personal funds at stake may not act responsibly when liquidating users.
Tools Used
manual
Recommendations
Require managers to validate allocation before liquidating.
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();
+ if (managers[msg.sender] && managerAllocation[manager] < userDebt) {
+ revert("Not Enough Allocation");
+ }
uint256 crvUSDBalance = crvUSDToken.balanceOf(address(this));
if (crvUSDBalance < scaledUserDebt) revert InsufficientBalance();
// Approve the LendingPool to transfer the debt amount
bool approveSuccess = crvUSDToken.approve(address(lendingPool), scaledUserDebt);
if (!approveSuccess) revert ApprovalFailed();
// Update lending pool state before liquidation
lendingPool.updateState();
// Call finalizeLiquidation on LendingPool
lendingPool.finalizeLiquidation(userAddress);
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}