Core Contracts

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

Race Condition on `RAACHousePriceOracle::lastHouseId` Leading to Incorrect Oracle Fulfillment

Summary

The RAACHousePriceOracle contract relies on the lastHouseId variable to track which house ID is being updated by an oracle response. However, this approach introduces a race condition because multiple requests can be made before a response is fulfilled. As a result, when _processResponse is called, the lastHouseId may no longer correspond to the original request, leading to incorrect house price updates.

Vulnerability Details

The issue stems from the following implementation:

uint256 public lastHouseId;
function _beforeFulfill(string[] calldata args) internal override {
lastHouseId = args[0].stringToUint();
}
function _processResponse(bytes memory response) internal override {
uint256 price = abi.decode(response, (uint256));
housePrices.setHousePrice(lastHouseId, price);
emit HousePriceUpdated(lastHouseId, price);
}

The lastHouseId is updated in _beforeFulfill, which is called when the request is sent. Since _processResponse is an asynchronous function, multiple requests can be sent before the first response is fulfilled, leading to lastHouseId being overwritten. As a result, when _processResponse executes, it may update a different house ID than originally intended.

Impact

This vulnerability can lead to incorrect price updates, causing house prices to be assigned to the wrong house ID. As a result, historical data integrity may be lost, and the contract may fail to correctly map oracle responses, making it unreliable for accurate house price tracking.

Tools Used

Manual review

Recommendations

Use requestId Instead of lastHouseId Instead of storing the house ID in a global variable, map it to the request ID:

// BaseChainlinkFunctionsOracle
mapping(bytes32 => uint256) public requestToHouseId;
function sendRequest(...) external onlyOwner {
bytes32 requestId = _sendRequest(req.encodeCBOR(), subscriptionId, callbackGasLimit, donId);
requestToHouseId[requestId] = args[0].stringToUint();
}

Modify the fulfillment function to use requestId:

// RAACHousePriceOracle
function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override {
if (err.length == 0 && response.length > 0) {
_processResponse(requestId, response);
}
}
function _processResponse(bytes32 requestId, bytes memory response) internal override {
uint256 price = abi.decode(response, (uint256));
uint256 houseId = requestToHouseId[requestId];
housePrices.setHousePrice(houseId, price);
emit HousePriceUpdated(houseId, price);
}
Updates

Lead Judging Commences

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

Oracle Race Condition in RAACHousePriceOracle causes price misassignment between NFTs

inallhonesty Lead Judge 4 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.