Bid Beasts

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

Function at `BidBeastsNFTMarket::placeBid` has unbounded auction time extension logic that can cause the  auction to never end.

Root + Impact

Unbounded auction time extension logic that can cause the auctions to be indefinitely prolonged, creating a Denial-of-Service by preventing settlement and locking funds.

Description

  • In the designed auction system, the auction end time is initially set to 15 minutes after the first bid. For each subsequent bid, if the remaining time is less than 15 minutes, the auction is extended to 15 minutes past the most recent bid.

    Under normal conditions, this mechanism prevents last-second “sniping” by ensuring that bidders always have enough time to respond.

    However, the logic also allows an attacker (or determined participants) to continuously place bids near the end of the auction, thereby indefinitely extending the auction duration. This can result in the auction never reaching a settlement point, creating a Denial-of-Service condition for sellers who wish to finalize and receive funds, and for bidders who want closure.

if (previousBidAmount == 0) { // extend a bid if no bid made
requiredAmount = listing.minPrice;
require(msg.value >= requiredAmount, "First bid must be > min price")
@> listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION; // bid extended 15min
emit AuctionExtended(tokenId, listing.auctionEnd);
} else { // extend a bid if there is already a bid made
requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE); // @audit fuzz test to make sure this is always going higher 📚
require(msg.value >= requiredAmount, "Bid not high enough");
uint256 timeLeft = 0;
if (listing.auctionEnd > block.timestamp) {
timeLeft = listing.auctionEnd - block.timestamp; // time left for auction to end
}
if (timeLeft < S_AUCTION_EXTENSION_DURATION) { // extend if timeleft is less than 15 mins
@> listing.auctionEnd = listing.auctionEnd + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
}
}

Risk

Likelihood:

  • Attackers or competitive bidders can deliberately wait until the last moments to place bids, causing the auction to be extended indefinitely.

  • This will always occur in competitive bidding wars, where multiple participants keep outbidding each other close to the deadline.

Impact:

  • Impact 1: Funds remain locked in escrow for an extended or indefinite period, delaying payout to the seller.

  • Impact 2: Bidders face uncertainty and potential frustration due to auctions not settling within expected timelines, degrading platform trust.

  • Impact 3: This can grief the seller by bidding with small increments just to keep the auction open without real intent to win.

Proof of Concept

This test loop demonstrates that the auction can be kept alive indefinitely by placing repeated bids from different participants just before expiration.

function test_auctionsNeverEnds() public {
_mintNFT();
_listNFT();
vm.startPrank(BIDDER_1);
market.placeBid{value: MIN_PRICE}(TOKEN_ID);
vm.stopPrank();
for (uint i = 5; i < 10; i++) {
vm.startPrank(address(uint160(i)));
uint256 highestBidAmount = market.getHighestBid(TOKEN_ID).amount;
uint256 bidAmount = (highestBidAmount / 100) * (100 + MIN_BID_INCREMENT_PERCENTAGE);
uint256 auctionEnd = market.getListing(TOKEN_ID).auctionEnd;
vm.warp(auctionEnd - 1 minutes);
vm.deal(address(uint160(i)), bidAmount);
market.placeBid{value: bidAmount}(TOKEN_ID);
vm.stopPrank();
assert(auctionEnd > block.timestamp);
}
market.settleAuction(TOKEN_ID);
}

Recommended Mitigation

The settleAuction function should enforce the 3 days auction deadline so as anyone can settle the auction in 3 days no matter what.

function settleAuction(uint256 tokenId) external isListed(tokenId) {
Listing storage listing = listings[tokenId];
require(listing.auctionEnd > 0, "Auction has not started (no bids)");
- require(block.timestamp >= listing.auctionEnd, "Auction has not ended");
+ require(block.timestamp >= 3 days, "Auction has not ended");
require(bids[tokenId].amount >= listing.minPrice, "Highest bid did not meet min price");
_executeSale(tokenId);
}
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeast Marketplace: Auction Duration Miscalculation

BidBeast marketplace contains a flaw in its auction timing mechanism. This causes the contract to miscalculate the actual end time of an auction, resulting in auctions that either conclude prematurely or run longer than specified.

Support

FAQs

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