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.