Core Contracts

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

NFTs transferred to stabilityPool during liquidation are unrecoverable

Summary

stabilityPool is not designed to handle NFTs ,
meaning any NFT transferred to stabilityPool as part of the current liquidation process are unrecoverable.

Vulnerability Details

During the liquidation process,
NFTs (collateral) of the user getting liquidated are transfered to the stabilityPool

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

The line in finalizeLiquidation uses transferFrom which assumes the receiving address can handle NFTs

raacNFT.transferFrom(address(this), stabilityPool, tokenId);

However StabilityPool is NOT designed to handle NFTs
The StabilityPool contract is designed to manage ERC-20 tokens
(like rToken, deToken, crvUSDToken, and raacToken).
There are no functions to manage ERC-721 NFTs.

This means that any NFT transferred to stabilityPool cannot be retrieved unless,
functionalities are added to facilitate NFT handling.

Also worth mentioning that using safeTransferFrom in finalizeLiquidation
without making the stabilityPool onERC721Received compatible
will cause the transaction to revert, preventing liquidation.

Impact

Locked Collateral:
NFTs transferred to stabilityPool become unrecoverable.

Impact : High
(The NFTs representing Real World Assets could be of significant value)

Likelihood : High
(Liquidations are a common occurance in lending protocols)

Tools Used

Manual Analysis

Recommendations

Make StabilityPool an NFT Receiver: Implement IERC721Receiver to accept NFTs.

Allow NFT Withdrawals: a function to withdraw stuck NFTs.

An example implementation below

contract StabilityPool is IERC721Receiver, Ownable {
event NFTReceived(address operator, address from, uint256 tokenId, bytes data);
event NFTWithdrawn(address tokenAddress, uint256 tokenId, address to);
// Mapping to track stored NFTs
struct NFT {
address nftAddress;
uint256 tokenId;
}
NFT[] public storedNFTs;
/**
* @dev Implements IERC721Receiver to accept ERC-721 NFTs.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external override returns (bytes4) {
storedNFTs.push(NFT(msg.sender, tokenId)); // Track received NFT
emit NFTReceived(operator, from, tokenId, data);
return this.onERC721Received.selector;
}
/**
* @dev Allows the owner to withdraw NFTs.
*/
function withdrawNFT(address nftAddress, uint256 tokenId, address to) external onlyOwner {
require(to != address(0), "Invalid address");
IERC721(nftAddress).safeTransferFrom(address(this), to, tokenId);
emit NFTWithdrawn(nftAddress, tokenId, to);
// Remove from tracking
for (uint i = 0; i < storedNFTs.length; i++) {
if (storedNFTs[i].nftAddress == nftAddress && storedNFTs[i].tokenId == tokenId) {
storedNFTs[i] = storedNFTs[storedNFTs.length - 1];
storedNFTs.pop();
break;
}
}
}
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!