Core Contracts

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

Price Oracle Race Condition in RAAC NFT Minting

Summary

The protocol's strength lies in its ability to maintain precise price consistency between on-chain NFTs and real-world assets. However, I've identified a timing vulnerability in the price verification system.

When minting RAAC NFTs, the protocol relies on the RAACHousePrices oracle to provide accurate valuations. I expects perfect consistency between the initial check and final mint, but notice how the oracle's architecture allows price updates to occur mid-transaction. This creates a dangerous race condition.

The RAACHousePriceOracle fetches pricing data and updates the RAACHousePrices contract. Meanwhile, the RAACNFT contract's minting function checks these prices at two critical moments, during initial verification and final minting. The mistake is assuming these price checks will always match.

Looking at the RAACNFT.sol implementation, we see it interfaces with RAACHousePrices.sol for valuation data. The prices should remain stable during minting. This means that if a house is valued at 500,000 USDC when minting begins, it must maintain that exact value until the mint completes.

This timing gap enables a arbitrage where users could:

  1. Initiate minting when oracle reports lower prices

  2. Complete minting after true market prices have increased

  3. Instantly capture the price differential through lending or secondary sales

Vulnerability Details

The assumption that price checks would be atomic within a single transaction makes sense from a traditional finance perspective where asset prices lock during a trade execution.

However, in the RAAC ecosystem:

  1. The oracle updates prices asynchronously

  2. Multiple contracts (RAACNFT, LendingPool, StabilityPool) read these prices

  3. No mechanism exists to temporarily lock prices during critical operations

// RAACNFT.sol - Minting Function
function mint(uint256 _tokenId, uint256 _amount) public override {
// CRITICAL POINT 1: Initial price check
// This price can change before transaction completes
uint256 price = raac_hp.tokenToHousePrice(_tokenId);
if(price == 0) { revert RAACNFT__HousePrice(); }
// CRITICAL POINT 2: Price validation
// Uses potentially stale price value
if(price > _amount) { revert RAACNFT__InsufficientFundsMint(); }
// CRITICAL POINT 3: Token transfer occurs with potentially outdated price
token.safeTransferFrom(msg.sender, address(this), _amount);
// CRITICAL POINT 4: NFT minting happens without re-checking price
_safeMint(msg.sender, _tokenId);
// CRITICAL POINT 5: Refund calculation uses original price
// Could be incorrect if price changed
if (_amount > price) {
uint256 refundAmount = _amount - price;
token.safeTransfer(msg.sender, refundAmount);
}
}
// RAACHousePrices.sol - Price Update Function
function setHousePrice(
uint256 _tokenId,
uint256 _amount
) external onlyOracle {
// VULNERABLE POINT: Can be called at any time
// Even between price check and mint completion
tokenToHousePrice[_tokenId] = _amount;
lastUpdateTimestamp = block.timestamp;
emit PriceUpdated(_tokenId, _amount);
}

Theflow

  1. User calls mint() → Checks price

  2. Oracle updates price via setHousePrice()

  3. Original mint transaction completes with old price

  4. Result: NFT minted at incorrect valuation

This creates a race condition between price updates and minting operations.

The core issue lies in how the RAACNFT contract handles price verification during minting. Notice how the RAACHousePrices oracle can update values at any moment, creating a dangerous gap between initial price checks and final minting. This means that a property valued at 500,000 USDC when minting begins could have a completely different value by the time the mint completes.

Let's see how this occurs in practice. When a user initiates an NFT mint, the contract performs an initial price check through RAACHousePrices. The oracle, operating independently, can update this price while the mint transaction is still pending. The final verification then passes despite the price discrepancy, fundamentally breaking the protocol's promise of maintaining precise real estate valuations.

This vulnerability ripples through the entire RAAC ecosystem. The LendingPool relies on these NFT valuations for collateral, the StabilityPool uses them for risk calculations, and even the governance system's voting power could be affected through the veRAACToken mechanism. A knowledgable actor could exploit this timing gap to mint NFTs at outdated prices, immediately leveraging them in the protocol's DeFi integrations.

Impact

  1. Start a mint transaction when prices are low

  2. Have the price update mid-transaction

  3. Complete the mint at the original lower price despite the new market value

Recommendations

sequenceDiagram
participant User
participant RAACNFT
participant RAACHousePrices
participant Oracle
Note over RAACNFT,RAACHousePrices: Price Check Phase
User->>RAACNFT: mint(_tokenId, _amount)
RAACNFT->>RAACHousePrices: tokenToHousePrice(_tokenId)
RAACHousePrices-->>RAACNFT: price = 500,000 USDC
Note over Oracle,RAACHousePrices: Price Update Phase (Race Condition!)
Oracle->>RAACHousePrices: setHousePrice(_tokenId, 550,000)
RAACHousePrices->>RAACHousePrices: Update price
Note over RAACNFT,User: Minting Phase (Uses Stale Price)
RAACNFT->>User: _safeMint(msg.sender, _tokenId)

Contract Relationships

IRAACNFT (Interface)
RAACNFT (Implementation)
Uses
IRAACHousePrices (Interface)
RAACHousePrices (Implementation)

Needs to implement price locking at the RAACHousePrices contract level

contract RAACHousePrices {
mapping(uint256 => PriceState) public priceStates;
struct PriceState {
uint256 price;
bool locked;
uint256 lockExpiry;
}
function lockPriceForMint(uint256 tokenId) external returns (uint256) {
priceStates[tokenId].locked = true;
priceStates[tokenId].lockExpiry = block.timestamp + 15 minutes;
return priceStates[tokenId].price;
}
}
sequenceDiagram
participant User
participant RAACNFT
participant RAACHousePrices
participant Oracle
Note over RAACNFT,RAACHousePrices: Contract Interaction Flow
User->>RAACNFT: mint(_tokenId, _amount)
RAACNFT->>RAACHousePrices: tokenToHousePrice(_tokenId)
RAACHousePrices-->>RAACNFT: returns price
Note over Oracle,RAACHousePrices: Race Condition Window
Oracle->>RAACHousePrices: setHousePrice(_tokenId, newAmount)
RAACNFT->>RAACNFT: Continue mint with old price

Contract Relationships

// RAACNFT.sol depends on
interface IRAACNFT {
function mint(uint256 _tokenId, uint256 _amount) external;
function getHousePrice(uint256 _tokenId) external view returns (uint256);
}
// RAACHousePrices.sol depends on
interface IRAACHousePrices {
function tokenToHousePrice(uint256 _tokenId) external view returns (uint256);
function setHousePrice(uint256 _tokenId, uint256 _amount) external;
}
  1. Price Locking: Preventing oracle updates during minting

  2. Price Verification: Ensuring price consistency through double-checking

Updates

Lead Judging Commences

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

Oracle Race Condition in RAACHousePriceOracle causes price misassignment between NFTs

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

Oracle Race Condition in RAACHousePriceOracle causes price misassignment between NFTs

Support

FAQs

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

Give us feedback!