Core Contracts

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

Incorrect start time boundary check in the modifier `Auction::whenActive`

Summary

The whenActive modifier in the Auction contract uses a strict greater than (>) comparison when checking if the current block timestamp is after the auction startTime. This creates a condition where the auction cannot be participated in exactly at the intended start time.

Vulnerability Details

The use of strict comparison > instead of >= creates a one-block period after the intended start time where the auction cannot be accessed. The contract's pricing function (getPrice()) is designed to handle transactions at exactly the start time. This suggests that the contract was designed to work at the start time, but the modifier accidentally prevents it.

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

Impact

Users cannot participate in the auction exactly at the start time due to this unnecessary one-block delay in auction participation causing missed opportunities for early participants (for example: those who use a bot/automatic system to access the auctions).

Add Foundry to the project following this procedure: .

In the test folder create a file named Auction.t.sol and copy / paste this code:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import {Auction} from "../contracts/zeno/Auction.sol";
import {ZENO} from "../contracts/zeno/ZENO.sol";
import {MockUSDC} from "../contracts/mocks/core/tokens/MockUSDC.sol";
contract AuctionTest is Test {
Auction public auction;
ZENO public zeno;
MockUSDC public mockUSDC;
uint256 public MATURITY_DATE = 86400 * 365;
address public businessAddress = makeAddr("businessAddress");
uint256 public startTime = 1733730100; //Feb 6th 2025
uint256 public endTime = 1738832238; //Dec 9th, 2024
uint256 public startingPrice = 100; //USDC
uint256 public reservePrice = 10; //USDC
uint256 public totalZENOAllocated = 10; // 10 Bonds allocated for the auction
address public initialOwner = address(this);
function setUp() public {
mockUSDC = new MockUSDC(1000000); //mint 1000000 to address(this)
zeno = new ZENO(address(mockUSDC), MATURITY_DATE, "ZEN", "ZENO", address(this));
auction = new Auction(
address(zeno),
address(mockUSDC),
businessAddress,
startTime,
endTime,
startingPrice,
reservePrice,
totalZENOAllocated,
initialOwner
);
console2.log("mockUSDC: ", address(mockUSDC));
console2.log("zenoAddress: ", address(zeno));
console2.log("auction: ", address(auction));
zeno.transferOwnership(address(auction)); //transfer zeno ownership to auction
}
function test_BidCantStartOnStartTime() public {
vm.warp(1733730100); //startTime
address buyer = makeAddr("buyer");
mockUSDC.approve(address(this), type(uint256).max);
mockUSDC.transferFrom(address(this), buyer, 100);
uint256 buyerBalance = mockUSDC.balanceOf(buyer);
assertEq(buyerBalance, 100);
vm.startPrank(buyer);
mockUSDC.approve(address(auction), type(uint256).max);
vm.expectRevert();
auction.buy(1);
vm.stopPrank();
assertEq(zeno.balanceOf(buyer), 0);
}
}

Run: forge test --match-test test_BidCantStartOnStartTime -vvvv

Ran 1 test for test/Auction.t.sol:AuctionTest
[PASS] test_BidCantStartOnStartTime() (gas: 108967)
Logs:
mockUSDC: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
zenoAddress: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
auction: 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
Traces:
[108967] AuctionTest::test_BidCantStartOnStartTime()
├─ [0] VM::warp(1733730100 [1.733e9])
│ └─ ← [Return]
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02]
├─ [0] VM::label(buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02], "buyer")
│ └─ ← [Return]
├─ [24735] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::approve(AuctionTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ ├─ emit Approval(owner: AuctionTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], spender: AuctionTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], value: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ └─ ← [Return] true
├─ [30421] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::transferFrom(AuctionTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02], 100)
│ ├─ emit Transfer(from: AuctionTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], to: buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02], value: 100)
│ └─ ← [Return] true
├─ [559] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::balanceOf(buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02]) [staticcall]
│ └─ ← [Return] 100
├─ [0] VM::assertEq(100, 100) [staticcall]
│ └─ ← [Return]
├─ [0] VM::startPrank(buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02])
│ └─ ← [Return]
├─ [24735] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::approve(0xF62849F9A0B5Bf2913b396098F7c7019b51A820a, 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ ├─ emit Approval(owner: buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02], spender: 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a, value: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
│ └─ ← [Return] true
├─ [0] VM::expectRevert(custom error 0xf4844814)
│ └─ ← [Return]
├─ [2520] 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a::buy(1)
│ └─ ← [Revert] revert: Auction not started
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [2637] 0x2e234DAe75C793f67A35089C9d99245E1C58470b::balanceOf(buyer: [0x0fF93eDfa7FB7Ad5E962E4C0EdB9207C03a0fe02]) [staticcall]
│ └─ ← [Return] 0
├─ [0] VM::assertEq(0, 0) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.48ms (269.92µs CPU time)

The test shows that the user cant bid in the auction because the auction state is "not started".

Tools Used

Manual review

Recommendations

Modify the whenActive modifier to use >= for the start time check:

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 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.