Core Contracts

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

Permanent Fund Lock in RAACNFT Contract Due to Missing Fund Distribution Logic

Summary

The RAACNFT contract permanently locks user funds (crvUSD) when minting NFTs instead of distributing them to the property seller or the protocol treasury. This occurs because the contract transfers the payment to its own address (address(this)) without any mechanism to later distribute or withdraw these funds.

This is especially concerning because the NFT can be used as collateral to borrow from the LendingPool. If a users NFT collateral value drops in price and he gets liquidated, the NFT gets transferred to the StabilityPool but there is no way to access the capital from the NFT contract => leaving the LendingPool undercollaterized (NFTs Get Permanently Locked in Stability Pool After Liquidation").

Vulnerability Details

In the mint() function of the RAACNFT contract, when a user mints an NFT representing a property, they must send the required amount of crvUSD tokens. The current implementation has these key issues:

function mint(uint256 _tokenId, uint256 _amount) public override {
// ...
token.safeTransferFrom(msg.sender, address(this), _amount);
// ...
_safeMint(msg.sender, _tokenId);
// ...
}
  1. The funds are transferred to address(this) (the contract itself)

  2. There is no function to withdraw or distribute these funds

  3. No treasury or property seller address is specified to receive the payments

  4. No mechanism exists to access the locked funds

PoC

This is confirmed by the test case which shows the funds remaining in the contract:

In order to run the test you need to:

  1. Run foundryup to get the latest version of Foundry

  2. Install hardhat-foundry: npm install --save-dev @nomicfoundation/hardhat-foundry

  3. Import it in your Hardhat config: require("@nomicfoundation/hardhat-foundry");

  4. Make sure you've set the BASE_RPC_URL in the .env file or comment out the forking option in the hardhat config.

  5. Run npx hardhat init-foundry

  6. There is one file in the test folder that will throw an error during compilation so rename the file in test/unit/libraries/ReserveLibraryMock.sol to => ReserveLibraryMock.sol_broken so it doesn't get compiled anymore (we don't need it anyways).

  7. Create a new folder test/foundry

  8. Paste the below code into a new test file i.e.: FoundryTest.t.sol

  9. Run the test: forge test --mc FoundryTest -vvvv

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {Test} from "forge-std/Test.sol";
import {RAACNFT} from "../../contracts/core/tokens/RAACNFT.sol";
import {crvUSDToken} from "../../contracts/mocks/core/tokens/crvUSDToken.sol";
import {RAACHousePrices} from "../../contracts/core/primitives/RAACHousePrices.sol";
contract FoundryTest is Test {
RAACNFT public raacNFT;
crvUSDToken public crvUSD;
RAACHousePrices public housePrices;
address public owner;
address public user1;
address public user2;
uint256 constant INITIAL_BATCH_SIZE = 3;
uint256 constant HOUSE_PRICE = 100 ether;
uint256 constant TOKEN_ID = 1;
function setUp() public {
// Setup accounts
owner = makeAddr("owner");
user1 = makeAddr("user1");
user2 = makeAddr("user2");
vm.startPrank(owner);
// Deploy crvUSD token
crvUSD = new crvUSDToken(owner);
crvUSD.setMinter(owner);
crvUSD.mint(user1, 1000 ether);
// Deploy house prices oracle
housePrices = new RAACHousePrices(owner);
housePrices.setOracle(owner);
// Set initial house prices
housePrices.setHousePrice(TOKEN_ID, HOUSE_PRICE);
housePrices.setHousePrice(TOKEN_ID + 1, HOUSE_PRICE);
// Deploy RAACNFT
raacNFT = new RAACNFT(address(crvUSD), address(housePrices), owner);
vm.stopPrank();
}
function test_MintPermanentlyLocksFunds() public {
uint256 amount = HOUSE_PRICE;
vm.startPrank(user1);
crvUSD.approve(address(raacNFT), amount);
raacNFT.mint(TOKEN_ID, amount);
assertEq(raacNFT.balanceOf(user1), 1);
// ! crvUSD is locked in the NFT contract now
assertEq(crvUSD.balanceOf(address(raacNFT)), HOUSE_PRICE);
}
}

Impact

  • All funds sent to mint NFTs become permanently locked in the contract

  • Property sellers cannot receive payments for their properties

  • Loss of funds for the protocol and the property owners / users

Tools Used

  • Foundry

  • Manual Review

Recommendations

The funds should either go to the property owners directly or to the protocol treasury for later distribution or covering the debt after a user gets liquidated. The protocol needs to decide how it wants to handle this but the funds shouldn't be stuck in the contract.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACNFT collects payment for NFT minting but lacks withdrawal functionality, permanently locking all tokens in the contract

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACNFT collects payment for NFT minting but lacks withdrawal functionality, permanently locking all tokens in the contract

Support

FAQs

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