Core Contracts

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

Auction start delay prevents users from buying `ZENO` at the exact initial price

Description

The Auction::whenActive() modifier incorrectly checks if the auction has started by using the > operator instead of >=. This means that the auction is not considered active at the exact starting time, leading to a one-second delay before users can participate in the auction, causing the actual starting price to be lower than expected. As a result, users cannot buy ZENO tokens at the exact initial price because the price starts to decrease immediately after the auction begins.

Context

Impact

Low. This issue prevents users from buying ZENO tokens at the exact initial price, but it does not lead to any loss of funds.

Likelihood

Low. The issue only affects the initial price of the auction, making it unlikely to be noticed unless the auction duration is extremely short.

Proof of Concept

To execute this proof of concept integrate foundry by running the following commands in your terminal, in the project's root directory:

# Create required directories
mkdir out lib
# Add `forge-std` module to `lib`
git submodule add https://github.com/foundry-rs/forge-std lib/forge-std
# Create foundry.toml
touch foundry.toml

Next, configure foundry by adding the following settings to foundry.toml:

[profile.default]
src = "contracts"
out = "out"
lib = "lib"

After that, create a foundry/ directory inside the test/ directory. Inside foundry/, create the following file:

  • ZenoModule.t.sol

And then paste the following code to ZenoModule.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "../../lib/forge-std/src/Test.sol";
import {Auction, IAuction} from "../../contracts/zeno/Auction.sol";
import {AuctionFactory} from "../../contracts/zeno/AuctionFactory.sol";
import {ZENO} from "../../contracts/zeno/ZENO.sol";
import {ZENOFactory} from "../../contracts/zeno/ZENOFactory.sol";
import {ERC20Mock} from "./mocks/ERC20Mock.sol";
contract ZenoModuleTest is Test {
address public immutable OWNER = makeAddr("OWNER");
address public immutable BUSINESS = makeAddr("BUSINESS");
address public immutable ALICE = makeAddr("ALICE");
address public immutable BOB = makeAddr("BOB");
AuctionFactory public auctionFactory;
ZENOFactory public zenoFactory;
Auction public auction;
ZENO public zeno;
ERC20Mock public usdc;
function setUp() public virtual {
// Deploy the contracts.
vm.startPrank(OWNER);
auctionFactory = new AuctionFactory(OWNER);
zenoFactory = new ZENOFactory(OWNER);
usdc = new ERC20Mock("USDC", "USDC", 6);
// Owner create a new zeno contract.
zenoFactory.createZENOContract(address(usdc), block.timestamp + 365 days);
// Get the instance of the new zeno contract.
zeno = zenoFactory.getZENO(0);
// Owner creates an auction for the zeno contract.
auctionFactory.createAuction(
address(zeno), address(usdc), BUSINESS, block.timestamp, block.timestamp + 10 minutes, 1e6, 0.5e6, 100_000e6
);
// Get the instance of the auction contract.
auction = auctionFactory.getAuction(0);
// Owner transfers the ownership of the zeno contract to the auction contract.
zenoFactory.transferZenoOwnership(0, address(auction));
vm.stopPrank();
}
function test_poc_invalid_initial_price() public {
// Store the timestamp in which the auction was created.
uint256 auctionStart = block.timestamp;
// Get the current price right after the auction starts.
uint256 initialPrice = auction.getPrice();
// Assert that the current price is equal to the initial price.
IAuction.AuctionDetails memory details = auction.getDetails();
assertEq(initialPrice, details.startingPrice);
// Give 50k USDC to Alice for her to buy 50k ZENO.
deal(address(usdc), ALICE, 50_000e6);
// Alice approves the auction contract to spend her USDC.
vm.startPrank(ALICE);
usdc.approve(address(auction), 50_000e6);
// Alice tries to buy ZENO bonds at the initial price,
// but the transaction reverts because the auction has not started,
// which means that the auction does not start at the actual
// starting price, but at slighly smaller one.
vm.expectRevert("Auction not started");
auction.buy(50_000);
// Increase the timestamp in one second.
vm.warp(auctionStart + 1);
// Get the current price one second after the auction starts.
uint256 finalPrice = auction.getPrice();
// Assert that the final price is lower than the starting price.
assertTrue(initialPrice > finalPrice);
// Alice can now buy the ZENO bonds.
auction.buy(50_000);
vm.stopPrank();
// Log the expected initial price.
console.log("Expected price : ", initialPrice);
console.log("Actual price : ", finalPrice);
}
}

Recommendations

Modify the Auction::whenActive() modifier to consider the first second of the auction as active

modifier whenActive() {
- require(block.timestamp > state.startTime, "Auction not started");
+ require(block.timestamp >= state.startTime, "Auction not started");
require(block.timestamp < state.endTime, "Auction ended");
_;
}
Updates

Lead Judging Commences

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