Core Contracts

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

Liquidated `RAACNFT`s are stuck in `StabilityPool` forever

Summary

The StabilityPool is unable to handle NFTs, resulting in all liquidated RAACNFT collateral to be stuck in the contract unless and upgrade is done.

Vulnerability Details

Users deposit their RAACNFTs into the LendingPool as collateral to borrow against them. If the value of their collateral falls below a certain threshold, then the StabilityPool can liquidate these positions by paying the outstanding debt and receiving the RAACNFT collateral in return.

This happens in StabilityPool#liquidateBorrower via LendingPool#finalizeLiquidation:

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); // <-- Liquidation happens here
emit BorrowerLiquidated(userAddress, scaledUserDebt);
}

Looking at finalizeLiquidation(), we can see that it iterates over the borrowers posited collateral and transfers them to the StabilityPool using ERC721#transferFrom:

...
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); // <-- transfer happens here
}
...

Once the collateral has been transferred to the stability pool, there's no way to move them out of there again for further handling.
Given that other users deposit into the StabilityPool so that liquidations can happen in the first place, they should probably be rewarded for the risk, and likely, the collateral owned by the stability pool should be used for that.

Currently, this is not possible unless the StabilityPool is upgraded.

Impact

Unless there's an upgrade done later one, RAACNFTs will be stuck in StabilityPool forever and the protocol looses the opportunity to distribute the value to liquidity providers of the stability pool. Meaning, in case of liquidations, all liquidity provider funds are at risk.

Tools Used

Manual review.

Recommendations

There's a couple of things that should be done here:

  1. Similar to how LendingPool inherits ERC721Holder, StabilityPool should also inherit ERC721Holder, to ensure it signals other contracts that it can handle NFTs.

  2. In addition to 1) LendingPool should then also make use of ERC721#safeTransferFrom in favor of transferFrom to ensure it checks whether the receiving contract can actually handle ERC721 tokens.

Here's what this could look like:

+ import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
- contract StabilityPool is IStabilityPool, Initializable, ReentrancyGuard, OwnableUpgradeable, PausableUpgradeable {
+ contract StabilityPool is IStabilityPool, Initializable, ReentrancyGuard, OwnableUpgradeable, PausableUpgradeable, ERC721Holder {
// 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);
+ raacNFT.safeTransferFrom(address(this), stabilityPool, tokenId);
}

Relevant links

Updates

Lead Judging Commences

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

Give us feedback!