Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Valid

Documentation states 3-Day Auction Duration but Code Does Not Enforce It, Auctions May Remain Open Indefinitely

Documentation states 3-Day Auction Duration but Code Does Not Enforce It, Auctions May Remain Open Indefinitely

Description

  • The project documentation states each auction lasts 3 days, the smart contract should create listings with an explicit deadline and should prevent bidding after that deadline. This ensures predictable auction lifecycle, fair competition, and correct settlement timing.

  • In this codebase the documented 3-day auction duration is not enforced or used anywhere in the marketplace contract. There is no recorded per-listing deadline or global auction timer enforced by the bidding and settlement logic, allowing auctions to be left open indefinitely or to be settled at arbitrary times. This mismatch between docs and implementation creates a functional integrity bug.

// Root cause (illustrative) - the Listing struct and listing flow do not record or use a deadline
struct Listing {
uint256 tokenId;
address seller;
uint256 minPrice;
uint256 buyNowPrice;
// @> no deadline or expiry field recorded here
}
// When a listing is created, no deadline is set
function listNft(uint256 tokenId, uint256 minPrice, uint256 buyNowPrice) external {
listings[tokenId] = Listing(tokenId, msg.sender, minPrice, buyNowPrice);
// @> no assignment like listings[tokenId].deadline = block.timestamp + AUCTION_DURATION
}

Risk

Likelihood

  • Every time an NFT is listed the contract creates a listing without an enforced expiry, so auctions will routinely remain open beyond the documented 3 days.

Impact

  • Auctions can accept bids forever or until someone manually triggers settlement. This breaks user expectations and marketplace invariants and can allow unfair timing behavior from participants.

Proof of Concept

Below is a concise test-style PoC that demonstrates a listing continues to accept bids after 3 days.
to get best results, reduce fees to 0.* ether.

function test_listing_accepts_bids_with_continuous_extensions() public {
// Seller lists the NFT
_mintNFT(SELLER);
_listNFT(SELLER, TOKEN_ID);
uint256 startTime = block.timestamp;
// First bid starts the auction
vm.prank(BIDDER_1);
market.placeBid{value: MIN_PRICE + 0.1 ether}(TOKEN_ID);
// Precompute number of iterations: 4 days / 14 minutes = 411
uint256 iterations = 411;
address currentBidder = BIDDER_1;
for (uint256 i = 0; i < iterations; i++) {
// Alternate between BIDDER_1 and BIDDER_2
currentBidder = (i % 2 == 0) ? BIDDER_2 : BIDDER_1;
// Get current highest bid
uint256 currentBid = market.getHighestBid(TOKEN_ID).amount;
// Compute next bid = currentBid * 105% + 1 wei
uint256 nextBid = (currentBid * 105) / 100 + 1;
// Advance time by 14 minutes
vm.warp(block.timestamp + 14 minutes);
// Place new bid
vm.prank(currentBidder);
market.placeBid{value: nextBid}(TOKEN_ID);
}
// At this point, more than 4 days have passed since the first bid.
// Auction is still open because bids kept coming in under the 15 min threshold.
// According to docs, it should have ended after 3 days.
uint256 elapsed = block.timestamp - startTime;
// Assert at least 3 days have passed
assertGt(elapsed, 3 days);
// Assert about 4 days have passed (allow some margin for rounding)
assertApproxEqAbs(elapsed, 4 days, 30 minutes);
// Final extra bid to confirm acceptance
uint256 finalBid = (market.getHighestBid(TOKEN_ID).amount * 105) / 100 + 1;
vm.warp(block.timestamp + 14 minutes);
vm.prank(BIDDER_1);
market.placeBid{value: finalBid}(TOKEN_ID);
// Expectation if 3-day enforcement was implemented: revert
// Actual: bid accepted and highestBidder updated
assertEq(market.getHighestBid(TOKEN_ID).bidder, BIDDER_1);
}

This PoC proves the absence of a time check in the bidding flow. If a deadline field were present and enforced, placeBid would revert when block.timestamp > 3 days.

Recommended Mitigation

Add an explicit auction duration constant, record a deadline (or endTime) when creating a listing, enforce block.timestamp <= deadline in the bidding path, and only emit AuctionSettled or allow settlement after block.timestamp > deadline. Use checks-effects-interactions and unit tests to verify the lifecycle.

+ // new constant (declared at contract level)
+ uint256 public constant AUCTION_DURATION = 3 days;
struct Listing {
uint256 tokenId;
address seller;
uint256 minPrice;
uint256 buyNowPrice;
+ uint256 endTime; // new field to track deadline
}
function listNft(uint256 tokenId, uint256 minPrice, uint256 buyNowPrice) external {
- listings[tokenId] = Listing(tokenId, msg.sender, minPrice, buyNowPrice);
+ listings[tokenId] = Listing({
+ tokenId: tokenId,
+ seller: msg.sender,
+ minPrice: minPrice,
+ buyNowPrice: buyNowPrice,
+ endTime: block.timestamp + AUCTION_DURATION // set deadline at listing time
+ });
emit NftListed(tokenId, msg.sender, minPrice, buyNowPrice);
}
function placeBid(uint256 tokenId) external payable {
Listing storage listing = listings[tokenId];
+ // enforce auction is still active
+ require(block.timestamp <= listing.endTime, "Auction ended");
// existing bid logic follows...
}
function settleAuction(uint256 tokenId) external {
Listing storage listing = listings[tokenId];
+ // ensure auction has finished
+ require(block.timestamp > listing.endTime, "Auction not ended");
+
+ // settlement: transfer NFT, distribute funds, emit AuctionSettled
// existing settlement logic here...
}
Updates

Lead Judging Commences

cryptoghost Lead Judge
2 months ago
cryptoghost Lead Judge 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeasts Marketplace: Improper Documentation

Documentation for BidBeasts Marketplace is incomplete or inaccurate, potentially leading to misconfigurations or security misunderstandings.

Support

FAQs

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

Give us feedback!