Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Permanent Lock of NFT and Bid Funds When Minimum Price Not Met

Permanent Lock of NFT and Bid Funds When Minimum Price Not Met

#Risk

Severity: Critical

  • Likelihood: High (common auction scenario).

  • Impact: Complete asset loss + protocol unusable for affected listings.


#Description

The auction settlement logic fails to handle the case where the highest bid is below the seller’s minimum price.

The function settleAuction() — the only path to finalize an auction — will always revert in this case, leaving both the NFT and the bidder’s funds permanently locked inside the contract.

Because no alternative resolution function exists, neither the seller nor the bidder can recover their assets, creating an unrecoverable deadlock.


#Root Cause

In settleAuction(), a strict require enforces that the highest bid must be greater than or equal to the minimum price.

Root Cause in the codebase with @> marks to highlight the relevant section:

// src/BidBeastsNFTMarketPlace.sol
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"); // @> problematic line
_executeSale(tokenId);
}
  • State Mutation Constraint:
    Since settleAuction() is the only function available after an auction timer expires, and it reverts when minPrice is not met, the protocol provides no escape path to refund funds or return the NFT.


Impact Summary

  • Seller: Permanently loses their NFT.

  • Bidder: Permanently loses their Ether.

  • Protocol: Auction remains unresolved, undermining core functionality.


#PoC

  1. Seller A lists NFT #42 with minPrice = 1 ETH.

  2. Bidder B places a bid of 0.8 ETH.

  3. Auction ends after 15 minutes.

  4. Anyone calls settleAuction(42).

  5. Transaction reverts: 0.8 ETH < 1 ETH.

Result:

  • settleAuction() is blocked.

  • unlistNFT() cannot be called because a bid exists.

  • takeHighestBid() cannot be called because bid < minPrice.
    ➡️ Both assets remain locked in the contract forever.


#Recommended Mitigation

Introduce a function to handle unsuccessful auctions by refunding the bidder and returning the NFT to the seller.

+ /**
+ * @notice Cancels an auction that ended without meeting the minimum price.
+ * @dev Returns NFT to seller and refunds the highest bidder.
+ */
+ function cancelUnsuccessfulAuction(uint256 tokenId) external isListed(tokenId) {
+ Listing storage listing = listings[tokenId];
+ Bid memory bid = bids[tokenId];
+
+ require(listing.auctionEnd > 0, "Auction has not started");
+ require(block.timestamp >= listing.auctionEnd, "Auction has not ended");
+ require(bid.amount < listing.minPrice, "Auction was successful, use settleAuction");
+
+ listing.listed = false;
+ delete bids[tokenId];
+
+ BBERC721.transferFrom(address(this), listing.seller, tokenId);
+ _payout(bid.bidder, bid.amount);
+
+ emit AuctionCancelled(tokenId, bid.bidder, bid.amount);
+ }

Recommended Test Case

Add a unit test to ensure correct behavior:

  • Scenario:

    • Seller lists NFT with minPrice = 1 ETH.

    • Bidder places bid of 0.8 ETH.

    • Auction expires.

    • Call cancelUnsuccessfulAuction().

  • Expected Outcome:

    • NFT returned to seller.

    • Bidder refunded.

    • AuctionCancelled event emitted.

Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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