Core Contracts

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

After Liquidating a user NFTs are transferred into the StabilityPool which though lacks functionality to withdraw them, effectively locking them in the contract

Description

During the liquidation scenario of a user within LendingPool::finalizeLiquidation the users NFT are getting transferred into the StabilityPool which though lacks functionality to forward or approve those NFTs effectively locking them within the contract.

Vulnerable Code

LendingPool::finalizeLiquidation:

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;
// 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;
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) = IDebtToken(reserve.reserveDebtTokenAddress).burn(userAddress, userDebt, reserve.usageIndex);
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountBurned); // amountScaled
user.scaledDebtBalance -= amountBurned;
reserve.totalUsage = newTotalSupply;
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit LiquidationFinalized(stabilityPool, userAddress, userDebt, getUserCollateralValue(userAddress));
}

Looking at the highlighted line above we clearly see, that NFTs are being transferred into the StabilityPool. Looking now at the interface of it:

IStabilityPool:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface IStabilityPool {
function deposit(uint256 amount) external;
function withdraw(uint256 deCRVUSDAmount) external;
function liquidateBorrower(address userAddress) external;
/* ========== VIEW FUNCTIONS ========== */
function getExchangeRate() external view returns (uint256);
function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) external view returns (uint256);
function calculateRcrvUSDAmount(uint256 deCRVUSDAmount) external view returns (uint256);
function calculateRaacRewards(address user) external view returns (uint256);
function getPendingRewards(address user) external view returns (uint256);
function getTotalDeposits() external view returns (uint256);
function getUserDeposit(address user) external view returns (uint256);
function balanceOf(address user) external view returns (uint256);
/* ========== MANAGER FUNCTIONS ========== */
function getManagerAllocation(address manager) external view returns (uint256);
function getTotalAllocation() external view returns (uint256);
function getManager(address manager) external view returns (bool);
function getManagers() external view returns (address[] memory);
/* ========== ADMIN FUNCTIONS ========== */
function addManager(address manager, uint256 allocation) external;
function removeManager(address manager) external;
function updateAllocation(address manager, uint256 newAllocation) external;
function setRAACMinter(address _raacMinter) external;
function depositRAACFromPool(uint256 amount) external;
function pause() external;
function unpause() external;
/* ========== EVENTS ========== */
//// ***SNIP*** ////
}

we can see that there is no functionality implemented to withdraw/transfer these liquidated NFTs.

Impact

Locking those NFTs within the StabilityPool basically makes liquidating users unprofitable, since the liquidation mechanic directly relies on selling the liquidated NFTs. Therefore the spent crvUSD during liquidation are under these circumstances a direct loss to the protocol, breaking solvency.

By default his justifies a severity of High.

Tools Used

Manual Review

Recommended Fix

Integrate functionality into the StabilityPool to be able to approve/send those NFTs.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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.