Core Contracts

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

`NFTLiquidator::placeBid()` allows liquidated NFTs to be bought as fraction of price regardless of their underlying debt, it can lead to major financial losses for protocol.

Summary

The RAAC protocol’s auction mechanism currently permits extremely low bids for liquidated NFTs, regardless of their underlying debt. This design flaw means NFTs can be acquired at a fraction of their debt value, potentially leading to significant financial loss for the protocol.

Vulnerability details

Protocols liquidate users primarily to manage and mitigate risk. When value of collateral falls below the borrowed amount, the position of the user becomes risky for the protocol to handle. Hence they are liquidated to keep the protocol safe from bad debt.

In RAAC NFTs are liquidated for the same reason and they are auctioned.
The purpose of auctions can be to cover up a certain portion of the debt which a particular NFT has. But here, liquidated NFTs can be acquired by paying extremely low bids.

The placeBid() function is reponsible for letting users bid on liquidated auctions.

/**
* @dev Allows users to place bids on liquidated NFTs
* @param tokenId The ID of the NFT being auctioned
*/
function placeBid(uint256 tokenId) external payable {
TokenData storage data = tokenData[tokenId];
if (block.timestamp >= data.auctionEndTime) revert AuctionHasEnded();
@> uint256 minBidAmount = data.highestBid + (data.highestBid * minBidIncreasePercentage / 100);
if (msg.value <= minBidAmount) revert BidTooLow(minBidAmount);
if (data.highestBidder != address(0)) {
payable(data.highestBidder).transfer(data.highestBid);
}
data.highestBid = msg.value;
data.highestBidder = msg.sender;
emit BidPlaced(tokenId, msg.sender, msg.value);
}

TokenData storage data = tokenData[tokenId]; fetches the details of the particular tokenId.
TokenData is a struct:

struct TokenData {
uint256 debt;
uint256 auctionEndTime;
uint256 highestBid;
address highestBidder;
}

When the NFTs are liquidated by liquidateNFT() function, it updates the TokenData struct associated with the particular tokenId.

function liquidateNFT(uint256 tokenId, uint256 debt) external {
//remaining function
@> tokenData[tokenId] = TokenData({
debt: debt,
auctionEndTime: block.timestamp + 3 days,
highestBid: 0,
highestBidder: address(0)
});
//remaining function
}

So we can see that for each tokenId liquidated, the TokenData struct contains it's underlying debt.

POC

Let us consider a token is being auctioned for the first time, and first bid is placed with 1 wei and analyse the placeBid() function:

data.highestBid = 0 (first bid)
minBidAmount = 0 + (0 * minBidIncreasePercentage / 100);
minBidAmount = 0;
if (msg.value <= minBidAmount) revert BidTooLow(minBidAmount);
if (1 <= 0) //if statement fails
//if any previous bids existed they are refunded, in this case it doesn't as it is the first bid.
data.highestBid = 1;
data.highestBidder = msg.sender;

So even if the actual debt associated with NFT was 10ETH(assumption) the NFT can be bought by paying a fraction of it. Which overtime would accrue bad debt to the protocol.

On the contrary if we look at buyBackNFT() function:

/**
* @dev Allows users to buy back liquidated NFTs at a premium
* @param tokenId The ID of the NFT to be bought back
*/
function buyBackNFT(uint256 tokenId) external payable {
TokenData storage data = tokenData[tokenId];
...//remaining function
@> uint256 price = data.debt * 11 / 10; // 110% of the debt
@> if (msg.value < price) revert InsufficientPayment(price);
...//remaining function
}

It ensures that user has to pay a 110% premium to buy back their NFT which is a good design choice.

Impact

The design flaw allows NFTs with significant debt to be acquired for a fraction of their value. This mispricing can lead to financial losses for the protocol.

Tools used

Manual review

Recommended mitigation

The protocol can implement a mechanism in which the base price of NFT i.e. minBidAmount takes in consideration of the debt value associated with the NFT. The base price can be set at a discounted price, which would help the protocol to cover up the bad debt partially.

E.g.

Considering discounted price of 20%
uint256 discountedDebt = (data.debt * 80)/100;
uint256 minBidAmount = data.highestBid + (discountedDebt * minBidIncreasePercentage / 100);

The actual formula for calculation minBidAmount while introducing a discount, can differ from the example. The above example is just a representation on how it** might be done.**

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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

Give us feedback!