Bid Beasts

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

Auction-time griefing: additive extension lets bidders grow time buffer (> extension) and keep auction open indefinitely

Root + Impact

Description

  • Normal behavior: Anti-sniping should maintain a sliding window (e.g., “always at least 15 minutes left”) by setting auctionEnd = block.timestamp + extension when bids arrive near the end.

Issue: The code adds the extension to the existing deadline:

  • (A bid with timeLeft = 10m increases the buffer to 25m, not 15m. Repeated bids placed before the window repeatedly inflate the buffer, enabling time-dilation griefing (keeping the auction open much longer than intended).)

// if (timeLeft < S_AUCTION_EXTENSION_DURATION) {
listing.auctionEnd = listing.auctionEnd + S_AUCTION_EXTENSION_DURATION; // additive

Risk

Likelihood:

  • Occurs whenever bids come in while timeLeft < extension. Common near the end of competitive auctions.

Impact:

  • Operational griefing / soft DoS: Attackers (or strategic bidders) can prolong closing, delaying settlement & cash flows.

Seller fatigue / UX issues: Auction end becomes unpredictable and significantly longer than the configured window.

Proof of Concept

The test starts an auction (end = now + 15m), warps time so 10m remain, then submits a new bid. Because the code adds 15m to the existing auctionEnd (auctionEnd += 15m) instead of resetting to now + 15m, the new time left becomes ~25m (10m remaining + 15m added). Repeating this near the end lets bidders inflate the buffer repeatedly and keep the auction open far longer than intended.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {Test} from "forge-std/Test.sol";
import {BidBeastsNFTMarket} from "../src/BidBeastsNFTMarketPlace.sol";
import {BidBeasts} from "../src/BidBeasts_NFT_ERC721.sol";
contract AuctionExtensionGriefing_PoC is Test {
BidBeastsNFTMarket market;
BidBeasts nft;
address OWNER = address(0xA1);
address SELLER = address(0xB2);
address B1 = address(0xC3);
address B2 = address(0xD4);
uint256 constant TOKEN_ID = 0;
uint256 constant MIN = 1 ether;
function setUp() public {
vm.startPrank(OWNER);
nft = new BidBeasts();
market = new BidBeastsNFTMarket(address(nft));
vm.stopPrank();
vm.deal(SELLER, 100 ether);
vm.deal(B1, 100 ether);
vm.deal(B2, 100 ether);
vm.prank(OWNER);
nft.mint(SELLER);
vm.startPrank(SELLER);
nft.approve(address(market), TOKEN_ID);
market.listNFT(TOKEN_ID, MIN, 0); // no buy-now
vm.stopPrank();
// First bid starts auction & sets end = now + 15m
vm.prank(B1);
market.placeBid{value: MIN}(TOKEN_ID);
}
function test_AdditiveExtensionInflatesBuffer() public {
(, , , uint256 end1, ) = market.getListing(TOKEN_ID);
// Warp so that only 10 minutes remain
vm.warp(end1 - 10 minutes);
// Second bid arrives with 10m left -> additive code sets end += 15m
vm.prank(B2);
market.placeBid{value: (MIN * 105) / 100}(TOKEN_ID);
(, , , uint256 end2, ) = market.getListing(TOKEN_ID);
// With additive logic, new time-left ≈ 25m (10m remaining + 15m added)
assertApproxEqAbs(end2, end1 + 15 minutes, 2);
// A series of such bids can keep inflating the buffer, delaying closure far beyond 15m.
}
}

Recommended Mitigation

Optional hardening:

  • Add a max end cap, e.g., require(listing.auctionEnd <= start + MAX_DURATION, "auction max duration exceeded");

  • Emit an event when the cap is reached to keep indexers in sync.

This keeps anti-sniping behavior while preventing compounding extensions that enable long-tail griefing.

--- a/src/BidBeastsNFTMarketPlace.sol
+++ b/src/BidBeastsNFTMarketPlace.sol
@@ function placeBid(uint256 tokenId) external payable isListed(tokenId) {
- uint256 timeLeft = 0;
- if (listing.auctionEnd > block.timestamp) {
- timeLeft = listing.auctionEnd - block.timestamp;
- }
- if (timeLeft < S_AUCTION_EXTENSION_DURATION) {
- listing.auctionEnd = listing.auctionEnd + S_AUCTION_EXTENSION_DURATION;
- emit AuctionExtended(tokenId, listing.auctionEnd);
- }
+ // Sliding-window anti-sniping: ensure at least extension remains,
+ // without compounding beyond the configured window.
+ uint256 end = listing.auctionEnd;
+ uint256 ext = S_AUCTION_EXTENSION_DURATION;
+ if (end <= block.timestamp || end - block.timestamp < ext) {
+ listing.auctionEnd = block.timestamp + ext; // reset window
+ emit AuctionExtended(tokenId, listing.auctionEnd);
+ }
Updates

Lead Judging Commences

cryptoghost Lead Judge 26 days 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.