Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Real estate NFTs are stuck after liquidation inside stability pool

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();
// 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();
// 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);
}

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();
// update state
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;
// Transfer NFTs to Stability Pool
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.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Liquidated RAACNFTs are sent to the StabilityPool by LendingPool::finalizeLiquidation where they get stuck

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.