Core Contracts

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

Invalid Price Range and Potential DoS Attack

Summary

A critical vulnerability was identified in the Auction.sol contract, where it allows an invalid price range (startingPrice < reservePrice). Additionally, the getPrice() function may cause an underflow, leading to a potential Denial of Service (DoS) attack. These issues could compromise the integrity of the auction process.

Vulnerability Details

-> Auction Deploys with Invalid Price Ranges

  • The contract allows deployment even when startingPrice < reservePrice.

  • This breaks auction logic, as the auction price is expected to start high and decrease over time, but instead, it starts below the reserve price.

  • Attackers or users could exploit this bug to manipulate bidding behavior.

Test Failure Output:

Testing invalid price range...
WARNING: Vulnerability detected! The contract allowed an invalid price range.
AssertionError: Auction should not deploy when startingPrice < reservePrice

-> getPrice() Underflow May Cause DoS

  • If price calculations underflow, getPrice() may fail and revert transactions.

  • This can cause a Denial of Service (DoS) risk, preventing users from interacting with the auction.

Test Failure Output:

Deploying vulnerable contract...
WARNING: Vulnerability detected! Auction deployed with an invalid price range.
Calling getPrice()...
AssertionError: Expected transaction to be reverted

How This Leads to Denial of Service (DoS)?

  • The function getPrice() calculates the current auction price using a decreasing price formula.

  • If startingPrice < reservePrice, the price calculation could cause an underflow when subtracting values.

  • Solidity reverts the transaction when an underflow occurs, blocking all further interactions with the contract.

  • When getPrice() fails and reverts, users cannot get the current auction price.

  • Since price retrieval is necessary for placing bids, bidders are unable to participate.

  • This creates a Denial of Service (DoS) attack vector, where the auction becomes unusable, preventing valid buyers from purchasing assets.

-> Exploit Scenario

  • A malicious actor forces an invalid price range (startingPrice < reservePrice).

  • getPrice() reverts when called, locking the auction.

  • Since no bids can be placed, the auction fails, potentially allowing the attacker to manipulate or disrupt the sale

Impact

Auction Integrity Compromised: Invalid price ranges break the descending auction logic, allowing unintended price manipulation.

  • Denial of Service (DoS): If getPrice() underflows, bidders may not be able to place bids.

  • Potential Financial Exploits: Malicious actors could force invalid price conditions, leading to incorrect auction results.

PoC

-> Deploying Auction with an Invalid Price Range

The contract incorrectly allows deployment when startingPrice < reservePrice. This should be blocked, but the current implementation does not enforce this check.

it("should revert when startingPrice is less than reservePrice", async function () {
const invalidStartingPrice = ethers.parseUnits("50", 18);
const invalidReservePrice = ethers.parseUnits("100", 18);
console.log("🚀 Testing invalid price range...");
try {
auction = await Auction.deploy(
await zeno.getAddress(),
await usdc.getAddress(),
businessAddress.address,
startTime,
endTime,
invalidStartingPrice,
invalidReservePrice,
ethers.parseUnits("1000", 18), // Total allocated
owner.address
);
await auction.waitForDeployment();
console.warn("❌ WARNING: Contract allowed an invalid price range!");
expect.fail("Auction should not deploy when startingPrice < reservePrice");
} catch (error) {
console.log("✅ Test passed: Contract correctly rejected invalid prices");
}
});

-> getPrice() Underflow DoS Risk

getPrice() does not properly handle underflow, leading to potential transaction reverts. This can lock the auction, preventing users from retrieving the price and placing bids.

it("should revert if getPrice() underflows", async function () {
const invalidStartingPrice = ethers.parseUnits("50", 18);
const invalidReservePrice = ethers.parseUnits("100", 18);
console.log("🚀 Deploying auction to test getPrice() behavior...");
try {
auction = await Auction.deploy(
await zeno.getAddress(),
await usdc.getAddress(),
businessAddress.address,
startTime,
endTime,
invalidStartingPrice,
invalidReservePrice,
ethers.parseUnits("1000", 18), // Total allocated
owner.address
);
await auction.waitForDeployment();
console.warn("❌ WARNING: Auction deployed with an invalid price range!");
expect.fail("Auction should not deploy with invalid prices");
} catch (error) {
console.log("✅ Test passed: Auction deployment correctly reverted.");
}
console.log("🚀 Calling getPrice()...");
await expect(auction.getPrice()).to.be.reverted;
console.log("✅ getPrice() correctly reverted due to underflow risk.");
});

Full Code:

import { expect } from "chai";
import hre from "hardhat";
const { ethers } = hre;
describe("Auction Contract", function () {
let Auction, auction, owner, addr1, businessAddress;
let ZENO, zeno, USDC, usdc;
let startTime, endTime;
beforeEach(async function () {
[owner, addr1, businessAddress] = await ethers.getSigners();
// ✅ Deploy mock USDC first
USDC = await ethers.getContractFactory("MockUSDC");
usdc = await USDC.deploy();
await usdc.waitForDeployment();
// ✅ Set maturity date for ZENO (1 year from now)
const maturityDate = Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60;
// ✅ Deploy ZENO with correct constructor parameters
ZENO = await ethers.getContractFactory("ZENO");
zeno = await ZENO.deploy(
await usdc.getAddress(), // _usdc
maturityDate, // _maturityDate
"ZENO Token", // _name
"ZENO", // _symbol
owner.address // _initialOwner
);
await zeno.waitForDeployment();
// ✅ Set auction times
startTime = (await ethers.provider.getBlock("latest")).timestamp + 100; // Starts in 100 sec
endTime = startTime + 3600; // Ends in 1 hour
// ✅ Get Auction contract factory
Auction = await ethers.getContractFactory("Auction");
});
it("should deploy successfully with valid startingPrice and reservePrice", async function () {
const startingPrice = ethers.parseUnits("100", 18);
const reservePrice = ethers.parseUnits("50", 18);
const totalAllocated = ethers.parseUnits("1000", 18);
console.log("Deploying Auction contract...");
auction = await Auction.deploy(
await zeno.getAddress(), // _zenoAddress
await usdc.getAddress(), // _usdcAddress
businessAddress.address, // _businessAddress
startTime, // _startTime
endTime, // _endTime
startingPrice, // _startingPrice
reservePrice, // _reservePrice
totalAllocated, // _totalAllocated
owner.address // _initialOwner
);
await auction.waitForDeployment();
console.log("Auction deployed successfully!");
expect(await auction.zeno()).to.equal(await zeno.getAddress());
expect(await auction.usdc()).to.equal(await usdc.getAddress());
});
it("If this test passes, there is a vulnerability!", async function () {
const invalidStartingPrice = ethers.parseUnits("50", 18);
const invalidReservePrice = ethers.parseUnits("100", 18);
const totalAllocated = ethers.parseUnits("1000", 18);
console.log("Testing invalid price range...");
let vulnerabilityDetected = false;
try {
auction = await Auction.deploy(
await zeno.getAddress(),
await usdc.getAddress(),
businessAddress.address,
startTime,
endTime,
invalidStartingPrice,
invalidReservePrice,
totalAllocated,
owner.address
);
await auction.waitForDeployment();
vulnerabilityDetected = true;
} catch (error) {
console.log("Test passed: Contract correctly rejected invalid prices");
}
if (vulnerabilityDetected) {
console.warn("WARNING: Vulnerability detected! The contract allowed an invalid price range.");
expect.fail("Auction should not deploy when startingPrice < reservePrice");
}
});
it("If getPrice() underflows, there is a Denial of Service risk!", async function () {
const invalidStartingPrice = ethers.parseUnits("50", 18);
const invalidReservePrice = ethers.parseUnits("100", 18);
const totalAllocated = ethers.parseUnits("1000", 18);
console.log("Deploying vulnerable contract...");
auction = await Auction.deploy(
await zeno.getAddress(),
await usdc.getAddress(),
businessAddress.address,
startTime,
endTime,
invalidStartingPrice,
invalidReservePrice,
totalAllocated,
owner.address
);
await auction.waitForDeployment();
console.warn("WARNING: Vulnerability detected! Auction deployed with an invalid price range.");
console.log("Calling getPrice()...");
await expect(auction.getPrice()).to.be.reverted;
console.log("Test passed: getPrice() correctly reverted due to underflow");
});
});

Output:

Auction Contract
Deploying Auction contract...
Auction deployed successfully!
✔ should deploy successfully with valid startingPrice and reservePrice (52ms)
Testing invalid price range...
WARNING: Vulnerability detected! The contract allowed an invalid price range.
1) If this test passes, there is a vulnerability!
Deploying vulnerable contract...
WARNING: Vulnerability detected! Auction deployed with an invalid price range.
Calling getPrice()...
2) If getPrice() underflows, there is a Denial of Service risk!
1 passing (4s)
2 failing
1) Auction Contract
If this test passes, there is a vulnerability!:
AssertionError: Auction should not deploy when startingPrice < reservePrice
2) Auction Contract
If getPrice() underflows, there is a Denial of Service risk!:
AssertionError: Expected transaction to be reverted

Tools Used

Hardhat

Recommendations

-> Enforce Proper Price Constraints in the Constructor

Modify the Auction contract to strictly enforce the rule that startingPrice > = reservePrice.

Fix in Solidity (Auction.sol):

require(startingPrice >= reservePrice, "Starting price must be >= reserve price");

-> Fix getPrice() to Prevent Underflows

Check that getPrice() does not allow underflows, and ensure it returns a valid price in all cases.

Fix in Solidity (Auction.sol):

function getPrice() public view returns (uint256) {
uint256 currentTime = block.timestamp;
if (currentTime < startTime) {
return startingPrice;
}
if (currentTime >= endTime) {
return reservePrice;
}
uint256 priceDifference = startingPrice - reservePrice;
uint256 elapsedTime = currentTime - startTime;
uint256 auctionDuration = endTime - startTime;
// Ensure price reduction does not underflow
uint256 priceReduction = (priceDifference * elapsedTime) / auctionDuration;
if (priceReduction > startingPrice) {
return reservePrice; // Prevents underflow
}
return startingPrice - priceReduction;
}
Updates

Lead Judging Commences

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

Support

FAQs

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