Summary
Liquidating a user in LendingPool.sol
just makes his collateral/NFT unusable, as it is transferred to a StabilityPool.sol
which has no NFT handling mechanisms.
Vulnerability Details
Users in LendingPool
can borrow
the asset token with their NFTs as collateral. When a user is under liquidation and his grace period has ended, StabilityPool
owners or managers can call the liquidateBorrower
function to complete the liquidation process:
StabilityPool.sol
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);
}
This function calls finalizeLiquidation
in LendingPool
which transfers the liquidated user's NFTs to StabilityPool
:
LendingPool.sol
function finalizeLiquidation(address userAddress) external nonReentrant onlyStabilityPool {
if (!isUnderLiquidation[userAddress]) revert NotUnderLiquidation();
ReserveLibrary.updateReserveState(reserve, rateData);
if (block.timestamp <= liquidationStartTime[userAddress] + liquidationGracePeriod) {
revert GracePeriodNotExpired();
}
UserData storage user = userData[userAddress];
uint256 userDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex);
isUnderLiquidation[userAddress] = false;
liquidationStartTime[userAddress] = 0;
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
user.depositedNFTs[tokenId] = false;
@> raacNFT.transferFrom(address(this), stabilityPool, tokenId);
}
delete user.nftTokenIds;
.
.
emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
}
However, StabilityPool
doesn't have any NFT handling mechanisms. There is no way to withdraw/transfer these NFTs and they are stuck in the StabilityPool
contract forever, essentially getting burned.
Impact
The liquidated NFTs are stuck forever in the StabilityPool
and owners which pay for their liquidation have no way to move them.
Tools Used
Manual review
Recommendations
Create a withdraw function for the NFTs which will transfer them to the desired address.