Core Contracts

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

Auction: Miner-Exploitable Timestamp

Summary

The Auction contract calculates the price of ZENO tokens using block.timestamp, which miners can manipulate within a small window (typically ±30 seconds). This allows miners to influence the auction price to their advantage, enabling them to buy ZENO tokens at artificially low/high prices and profit unfairly.

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/zeno/Auction.sol#L69

Vulnerability Details

Exploit Scenario

  • Auction Setup: An auction runs from startTime to endTime, decaying the price from startingPrice to reservePrice.

  • Miner Participation: A miner submits a transaction to buy ZENO tokens.

  • Timestamp Manipulation: The miner mines the block and sets block.timestamp to a value that skews the price calculation.

Code Proof:

In Auction.sol, the price calculation relies on block.timestamp:

function getPrice() public view returns (uint256) {
if (block.timestamp < state.startTime) return state.startingPrice;
if (block.timestamp >= state.endTime) return state.reservePrice;
return state.startingPrice - (
(state.startingPrice - state.reservePrice) *
(block.timestamp - state.startTime) /
(state.endTime - state.startTime)
);
}

Attack Simulation:

  • Auction Parameters:

startTime: 12:00 PM

endTime: 1:00 PM

startingPrice: 100 USDC

reservePrice: 50 USDC

  • Fair Price at 12:30 PM:

Price = 100 - (100-50) * (30 minutes / 60 minutes) = 75 USDC
  • Miner Manipulation:
    The miner sets block.timestamp to 12:25 PM (5 minutes earlier).

Manipulated Price = 100 - (100-50) * (25 minutes / 60 minutes) ≈ 79.16 USDC
  • Result: Miner buys ZENO at 75 USDC (fair) but tricks the contract into charging 79.16 USDC, pocketing the difference.

Impact

  1. Unfair Profits: Miners extract value at the expense of honest participants.

  2. Protocol Drainage: Repeated exploitation could drain funds from the auction pool.

Tools Used

Manual reviews, static analysis

Recommendations

Replace block.timestamp with a decentralized oracle (e.g., Chainlink) to ensure tamper-proof timestamps.

Step 1: Integrate Chainlink’s Timestamp Oracle

// Auction.sol
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract Auction is IAuction, Ownable {
AggregatorV3Interface internal timeOracle;
constructor(...) {
timeOracle = AggregatorV3Interface(CHAINLINK_TIMESTAMP_ORACLE_ADDRESS);
}
function getOracleTimestamp() internal view returns (uint256) {
(, , , uint256 timestamp, ) = timeOracle.latestRoundData();
return timestamp;
}
}

Step 2: Modify Price Calculation to Use Oracle Time

function getPrice() public view returns (uint256) {
uint256 currentTime = getOracleTimestamp(); // Use oracle timestamp
if (currentTime < state.startTime) return state.startingPrice;
if (currentTime >= state.endTime) return state.reservePrice;
return state.startingPrice - (
(state.startingPrice - state.reservePrice) *
(currentTime - state.startTime) /
(state.endTime - state.startTime)
);
}

Step 3: Add Sanity Checks

Ensure the oracle timestamp is within an acceptable deviation from block.timestamp:

function getOracleTimestamp() internal view returns (uint256) {
(, , , uint256 timestamp, ) = timeOracle.latestRoundData();
require(
timestamp >= block.timestamp - 30 seconds &&
timestamp <= block.timestamp + 30 seconds,
"Oracle timestamp skewed"
);
return timestamp;
}

Why This Fix Works
- Decentralized Time Source: Chainlink oracles provide tamper-proof timestamps.

Manipulation Resistance: Miners cannot influence oracle-reported timestamps.

Graceful Degradation: If the oracle fails, the require statement halts the contract, preventing exploitation.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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