Core Contracts

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

Incorrect Price Precision Validation in RAACNFT::mint when retrieving price using chainlink functions can lead to allowing users mint NFT for a fraction of intended cost

Summary

RAACNFT::mint function does not validate that the house price retrieved from raac_hp.tokenToHousePrice(_tokenId) is denominated in 18 decimals. Since Chainlink Functions can only return values as arbitrary uint256 from the source configured by RAAC, the absence of this validation can lead to incorrect minting prices, potentially allowing users to mint NFTs at unintended lower prices.

Vulnerability Details

RAACNFT::mint contains the following:

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 from user
token.safeTransferFrom(msg.sender, address(this), _amount);
// mint tokenId to user
_safeMint(msg.sender, _tokenId);
// If user approved more than necessary, refund the difference
if (_amount > price) {
uint256 refundAmount = _amount - price;
token.safeTransfer(msg.sender, refundAmount);
}
emit NFTMinted(msg.sender, _tokenId, price);
}

raac_hp.tokenToHousePrice(_tokenId) is a mapping from RAACHousePrices.sol that stores the latest prices set by the chainlink oracles. See the relevant function below:

function setHousePrice(
uint256 _tokenId,
uint256 _amount
) external onlyOracle {
tokenToHousePrice[_tokenId] = _amount;
lastUpdateTimestamp = block.timestamp;
emit PriceUpdated(_tokenId, _amount);
}

RAAC uses chainlink functions to retrieve the latest house prices from the source they have configured and updates the tokenToHousePrice mapping as seen above. This can be seen in RAACHousePriceOracle.sol. The key function that does this is below:

function _processResponse(bytes memory response) internal override {
uint256 price = abi.decode(response, (uint256));
housePrices.setHousePrice(lastHouseId, price);
emit HousePriceUpdated(lastHouseId, price);
}

RAACHousePriceOracle::_processResponse gets the response the RAAC source and calls RAACHousePrices::setHousePrice as described. There is no validation ensuring that price is denominated in 18 decimals. Unlike Chainlink Price Feeds, which have predefined decimal places for each asset, Chainlink Functions can return uint256 values that may have any arbitrary decimal precision depending on how the data source formats its response. With chainlink price feeds, whenever prices are received from the oracle, standard practice is to handle the decimal amounts and convert them to the required decimals. With chainlink functions, since prices are retrieved from RAAC source, it should follow the same behaviour to configure the decimals and prevent human error.

If the returned price has fewer decimals than expected which is very likely as human error can allow for a lack of precision. If such a situation occurs, the _amount check:

if(price > _amount) { revert RAACNFT__InsufficientFundsMint(); }

may allow the user to underpay for the NFT due to the incorrect price scale.

Proof Of Code (POC)

This test was run in RAACNFT.test.js in the "Minting" describe block

it("doesnotrevertwhenhousepricedoesnothave18decimals", async () => {
await housePrices.setHousePrice(TOKEN_ID, ethers.parseEther("0.1"));
await expect(
raacNFT.connect(user1).mint(TOKEN_ID, ethers.parseEther("0.1"))
)
.to.emit(raacNFT, "NFTMinted")
.withArgs(user1.address, TOKEN_ID, ethers.parseEther("0.1"));
expect(await raacNFT.ownerOf(TOKEN_ID)).to.equal(user1.address);
expect(await crvUSD.balanceOf(raacNFT.getAddress())).to.equal(
ethers.parseEther("0.1")
);
});

Impact

Financial Loss: Users may exploit incorrect pricing to mint NFTs for significantly less than intended.
NFT Devaluation: The house price is a core property of the NFT. If improperly set, the NFT may be rendered useless.
Potential Arbitrage: Users could mint NFTs at incorrect prices and resell them for profit.

Tools Used

Manual Review, Hardhat

Recommendations

Validate the Price Precision: Introduce a MINIMUM_PRICE_DECIMALS constant to enforce 18-decimal precision, ensuring that any incorrect price values are rejected.

uint256 constant MINIMUM_PRICE_DECIMALS = 1e18;

Modify the mint function to include this check:

if (price < MINIMUM_PRICE_DECIMALS) {
revert RAACNFT__InvalidHousePrice();
}

Normalize Price Based on Expected Decimals: Instead of assuming the price has 18 decimals, explicitly retrieve the expected decimal format from the Chainlink Functions response and normalize the price before comparison.

If the function is expected to return a uint256 with X decimals, manually scale it to 18 decimals before use:

uint8 expectedDecimals = 18; // Set this based on the expected Chainlink Functions response
uint256 normalizedPrice = price * (10 ** (18 - expectedDecimals));
if (normalizedPrice > _amount) {
revert RAACNFT__InsufficientFundsMint();
}

This ensures that prices retrieved from Chainlink Functions are correctly scaled before use.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[INVALID] API response validation

We need to assume that the trusted admin is able to properly configure the API that will provide values. As long as the response isn't empty (and there's a check for that) then anything else relies on the accuracy of the data the API provides.

Support

FAQs

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