Bid Beasts

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

Buy-Now not auto-settled when highest bid ≥ buyNowPrice (surprising auction semantics)

Root + Impact

  • Root: The code only triggers Buy-Now when the incoming msg.value ≥ buyNowPrice. If an existing highest bid already ≥ buyNowPrice, the auction does not settle automatically.

  • Impact: Auctions can remain open even though the buy-now threshold is met, enabling griefing, race conditions, and user confusion; revenue timing and fairness are affected.


Description

  • In a buy-now auction, once the current price reaches the buy-now threshold, the sale should finalize immediately: the NFT transfers to the top bidder and the seller receives proceeds.

  • Here, buy-now logic runs only when the current bid call provides msg.value ≥ buyNowPrice. If a previous bid already exceeded buyNowPrice, the auction stays open until someone calls takeHighestBid/settleAuction, contrary to buy-now expectations.

// In placeBid
if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
// @> Only settles when the incoming bid meets buy-now.
// ... executes sale ...
}
// @> There is no check here for: previousBidAmount >= listing.buyNowPrice

Risk

Likelihood:

  • Reason 1 // As bids climb, it’s common for some bid to cross the buy-now threshold before a final buy-now “click.”

  • Reason 2 // Multiple users interact concurrently; the condition “previous ≥ buyNow” will frequently arise.


Impact:

  • Impact 1 // Auction remains open after buy-now is effectively reached, enabling sniping or strategic delays.

  • Impact 2 // UX/reputation hit: buyers expect immediate settlement upon reaching buy-now; analytics and bots may misinterpret state.


Proof of Concept

// Sequence:
// 1) buyNowPrice = 10 ether
// 2) Bidder A bids 10.1 ether (>= buy-now) -> NO auto-settle (prev >= buy-now not handled)
// 3) Auction remains open; Bidder B can still act, or seller must manually call takeHighestBid()
// Unexpectedly, the "buy-now" condition did not finalize the sale at step 2.

Recommended Mitigation

  • Auto-settle whenever either the existing highest bid or the incoming bid meets/exceeds buyNowPrice:

// Pseudopatch inside placeBid:
uint256 prev = bids[tokenId].amount;
if (listing.buyNowPrice > 0 && (prev >= listing.buyNowPrice || msg.value >= listing.buyNowPrice)) {
uint256 salePrice = listing.buyNowPrice;
uint256 overpay = msg.value >= salePrice ? (msg.value - salePrice) : 0;
address previousBidder = bids[tokenId].bidder;
uint256 previousBidAmt = prev;
// winner is the caller; clamp to salePrice
bids[tokenId] = Bid(msg.sender, salePrice);
listings[tokenId].listed = false;
if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmt);
}
_executeSale(tokenId);
if (overpay > 0) _payout(msg.sender, overpay);
return;
}
Updates

Lead Judging Commences

cryptoghost Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!