Core Contracts

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

NFT Double-Minting Vulnerability in RAACNFT Contract Enabling Collateral Exploitation and DoS

Summary

The RAACNFT contract allows for the multiple minting of the same tokenId that allows an attacker to mint the same NFT tokenId multiple times.

As a result, the LendingPool contract, which utilizes these NFTs as collateral, is susceptible to two attacks:

Collateral Exploitation: An attacker can deposit duplicate instances of an NFT as collateral to extract more funds.

DoS: If a legitimate user deposits a specific tokenId into the LendingPool, an attacker can re-mint the same tokenId, effectively causing a state conflict. This results in the LendingPool losing its legitimate ownership of the token, which will later cause withdrawals of that token collateral to fail.

Vulnerability Details

The mint function in the RAACNFT contract does not check if a given _tokenId has already been minted. This allows an attacker to call the mint function multiple times with the same _tokenId, resulting in multiple NFTs with the same ID being minted to the same or different addresses.

function mint(uint256 _tokenId, uint256 _amount) public override {
uint256 price = raac_hp.tokenToHousePrice(_tokenId);
if (price == 0) { revert RAACNFT__HousePrice(); }
if (price > _amount) { revert RAACNFT__InsufficientFundsMint(); }
// Transfer ERC20 from user to contract - requires pre-approval
token.safeTransferFrom(msg.sender, address(this), _amount);
// Mint tokenId to user without checking if it already exists
_safeMint(msg.sender, _tokenId);
// Refund excess funds if necessary
if (_amount > price) {
uint256 refundAmount = _amount - price;
token.safeTransfer(msg.sender, refundAmount);
}
emit NFTMinted(msg.sender, _tokenId, price);
}

The LendingPool contract accepts NFT deposits and verifies ownership by calling raacNFT.ownerOf(tokenId). Because an attacker can mint an NFT multiple times under different addresses, they can create multiple “legitimate” appearances of the same NFT. Each duplicate can be deposited as collateral. The LendingPool logic subsequently allows borrowing or depositing reserve assets (such as crvUSD) against each instance, even though they all represent the same underlying asset.

function depositNFT(uint256 tokenId) external nonReentrant whenNotPaused {
// update state
ReserveLibrary.updateReserveState(reserve, rateData);
if (raacNFT.ownerOf(tokenId) != msg.sender) revert NotOwnerOfNFT();
UserData storage user = userData[msg.sender];
if (user.depositedNFTs[tokenId]) revert NFTAlreadyDeposited();
@>> user.nftTokenIds.push(tokenId);
@>> user.depositedNFTs[tokenId] = true;
@>> raacNFT.safeTransferFrom(msg.sender, address(this), tokenId);
emit NFTDeposited(msg.sender, tokenId);
}

Attack Path

Step 1: The attacker calls the mint function for a specific tokenId (e.g., tokenId 42) with _amount equal to or greater than the price.
Step 2: Address A deposits the NFT (tokenId 42) into the LendingPool contract by calling depositNFT(42).
Step 3: The attacker then invokes the mint function again for the same tokenId 42 from a different controlled address (e.g., Address B), passing the required funds.
Step 4: Address B deposits the NFT (tokenId 42) into the LendingPool using depositNFT(42).
Step 5: Using the inflated collateral value, the attacker borrows or otherwise withdraws reserve assets (crvUSD) multiple times—once for each deposited duplicate NFT.

Outcome

By re-minting the NFT tokenId multiple times and depositing each version into the LendingPool, the attacker is able to withdraw more assets than what would be collateralized by a single NFT in the LendingPool.

DoS on Legitimate Collateral:

Step 1: A legitimate user deposits NFT tokenId 42 into the LendingPool, transferring it as collateral.
Step 2: An attacker re-mints tokenId 42 and transfers ownership away from the LendingPool (using the duplicate contract logic).

Outcome:The LendingPool, expecting to maintain custody of NFT tokenId 42 for collateral management, suddenly ceases to be its owner.

Subsequent withdrawal attempts by the legitimate user will fail, as the system's ownership tracking for tokenId 42 is compromised.

Impact

The LendingPool treats each instance of the re-minted NFT as a unique, valid collateral. This results in an artificially inflated total collateral value.

An attacker can repeatedly extract reserve assets (e.g., crvUSD) using the same NFT as collateral, leading to liquidation events or balance imbalances within the protocol.

If a legitimate user deposits an NFT (with a specific tokenId) to secure a loan or maintain collateral, an attacker can DOS that tokenId by re-minting it. By doing so, the LendingPool’s record of collateral ownership is invalidated. Consequently, when the legitimate user attempts to withdraw their collateral, the operation fails because the contract no longer holds the rightful NFT.

Tools Used

Manual review

Recommendations

Modify the mint function in the RAACNFT contract to check if the _tokenId already exists before minting a new NFT.

Updates

Lead Judging Commences

inallhonesty Lead Judge
9 months ago
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!