Bid Beasts

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

Premature auction settlement before 3-day deadline

Missing enforcement of the 3-day auction duration allows early settlement, undermining bidding fairness and seller revenue

Description

  • The contract is intended to have a maximum auction duration of 3 days, as mentioned in the contest details:

    The contract also supports:

    • Auction deadline of exactly 3 days.

    • (Rest of the details...)

  • Weirdly enough, there's no such logic in the contract that enforces this 3-day auction deadline. The only time-related logic present is the auction extension duration of 15 minutes (checkout BidBeastsNFTMarketPlace::S_AUCTION_EXTENSION_DURATION).

    uint256 constant public S_AUCTION_EXTENSION_DURATION = 15 minutes;

  • Subsequent bids placed within the final 15 minutes of the auction extend the duration by an additional 15 minutes, potentially allowing the auction to continue indefinitely as long as new bids are placed within this window and the seller does not invoke takeHighestBid to settle early.

    // Lines 145 to 167 in placeBid function
    function placeBid(uint256 tokenId) external payable isListed(tokenId) {
    // ...
    if (previousBidAmount == 0) {
    requiredAmount = listing.minPrice;
    require(msg.value > requiredAmount, "First bid must be > min price");
    @> listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION;
    emit AuctionExtended(tokenId, listing.auctionEnd);
    } else {
    requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
    require(msg.value >= requiredAmount, "Bid not high enough");
    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);
    }
    }
    }

  • This deviation from the specified 3-day duration creates an issue where auctions may end prematurely after just 15 minutes if no further bids are placed, allowing a bidder to settle the auction via settleAuction at a potentially low price. This scenario undermines price discovery and user trust in the platform.

    /**
    * @notice Settles the auction after it has ended. Can be called by anyone.
    */
    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(bids[tokenId].amount >= listing.minPrice, "Highest bid did not meet min price");
    _executeSale(tokenId);
    }

Risk

Likelihood: Medium

  • Every auction starts with a 15-minute duration and may end prematurely if bidding is sparse, deviating from the 3-day requirement.

Impact: High/Medium

  • Economic Loss: Sellers get significantly less price discovery (15 minutes vs 3 days)

  • Market Failure: Auctions can't serve their purpose of finding a fair market value

  • Specification Violation: Contract doesn't meet its own stated requirements, a complete deviation from documented behaviour

  • User Trust: Undermines confidence in the platform

Proof of Concept

  • Add the following test test_PrematureAuctionSettlement in the test file:

    function test_PrematureAuctionSettlement() public {
    // Minting and listing the NFT through modifiers
    _mintNFT();
    _listNFT();
    // An early bidder bids for it
    vm.prank(BIDDER_1);
    market.placeBid{value: BID_AMOUNT}(TOKEN_ID);
    // Checking auction end time
    BidBeastsNFTMarket.Listing memory listing = market.getListing(TOKEN_ID);
    console.log("Initial Auction end time:", listing.auctionEnd); // 1 (block.timestamp) + 900 (15 minutes) = 901
    // Fast forward time by 15 minutes
    vm.warp(block.timestamp + 15 minutes);
    // BIDDER_1 settles the auction, as others missed the narrow window
    vm.prank(BIDDER_1);
    market.settleAuction(TOKEN_ID);
    // Checking ownership of the NFT
    assertEq(
    nft.ownerOf(TOKEN_ID),
    BIDDER_1,
    "NFT is still in contract"
    );
    }

  • Run the above test using the command:

    forge test --mt test_PrematureAuctionSettlement -vv

Recommended Mitigation

There are two ways to mitigate this issue. The protocol can choose any one of them, as it's a matter of preference how they want their auctions to be run:

  1. Enforce the 3-day auction deadline, while preventing sniping (Recommended): It's better to implement a hybrid model, set a fixed 3-day deadline on the first bid, then allow 15-minute extensions only for bids placed in the final 15 minutes of the current deadline. This ensures auctions last at least 3 days (preventing premature settlement) and extends only as needed for fairness, potentially going beyond 3 days in competitive cases, and thus preventing sniping.

    contract BidBeastsNFTMarket is Ownable(msg.sender) {
    // ...
    uint256 constant public S_AUCTION_EXTENSION_DURATION = 15 minutes;
    + uint256 constant public S_AUCTION_DEADLINE = 3 days;
    // ...
    function placeBid(uint256 tokenId) external payable isListed(tokenId) {
    // ...
    if (previousBidAmount == 0) {
    requiredAmount = listing.minPrice;
    require(msg.value > requiredAmount, "First bid must be > min price");
    - listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION;
    + listing.auctionEnd = block.timestamp + S_AUCTION_DEADLINE;
    emit AuctionExtended(tokenId, listing.auctionEnd);
    } else {
    requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
    require(msg.value >= requiredAmount, "Bid not high enough");
    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);
    }
    }
    }
    }

  2. Update the documentation to reflect the actual behaviour: If the protocol prefers to keep the auction duration as it is, they should update their documentation and contest details to accurately represent the 15-minute auction extension logic. This ensures that users are well-informed about the auction mechanics and can adjust their bidding strategies accordingly.

Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month 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.