Bid Beasts

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

Buy Now Logic In `BidBeastsNFTMarket::placeBid` Allows Users To Bypass Auctions And Override Existing Bids

Buy Now Logic In BidBeastsNFTMarket::placeBid Allows Users To Bypass Auctions And Override Existing Bids

Description

  • The normal behavior of the marketplace, as documented, is a fixed 3-day auction where participants place bids and the highest bidder wins.

  • The specific issue is that the contract implements a hidden Buy Now mechanism. When listing.buyNowPrice > 0, any buyer can bypass the auction entirely by paying this price, immediately winning the NFT. This behavior is not documented and overrides the auction rules, breaking fairness for other bidders.

@> if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
uint256 salePrice = listing.buyNowPrice;
uint256 overpay = msg.value - salePrice;
// EFFECT: set winner bid to exact sale price (keep consistent)
@> bids[tokenId] = Bid(msg.sender, salePrice);
listing.listed = false;
if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmount);
}
// NOTE: using internal finalize to do transfer/payouts. _executeSale will assume bids[tokenId] is the final winner.
_executeSale(tokenId);
// Refund overpay (if any) to buyer
if (overpay > 0) {
_payout(msg.sender, overpay);
}
return;
}

Risk

Likelihood:

  • Occurs whenever an NFT is listed with a buyNowPrice greater than zero; any buyer can immediately purchase it and bypass the auction.

  • Every time multiple bidders participate, the hidden Buy Now can override their bids, triggering this issue systematically.

Impact:

  • Bidders lose NFTs they are actively bidding on, violating the expected 3-day auction rules and user trust.

  • Reputation and legal risk for the marketplace due to undocumented functionality and unfair behavior; potential for strategic abuse by sellers or confederates.

Proof of Concept

This test demonstrates that a buyer can bypass the ongoing auction by paying the Buy Now price, immediately winning the NFT and overriding any previous bids.

function test_buyNow_bypasses_auction() public {
address buyer = makeAddr("buyer");
vm.deal(buyer, 10 ether);
uint256 buyNowPrice = 1 ether;
uint256 firstBid = 0.5 ether;
vm.prank(OWNER);
uint256 tokenId = nft.mint(SELLER);
// Seller lists NFT with a Buy Now price
vm.startPrank(SELLER);
nft.approve(address(market), tokenId);
market.listNFT(tokenId, 0.3 ether, buyNowPrice);
vm.stopPrank();
// A bidder places a normal bid
vm.prank(BIDDER_1);
market.placeBid{value: firstBid}(tokenId);
// Buyer immediately calls the contract with buyNowPrice
vm.prank(buyer);
market.placeBid{value: buyNowPrice}(tokenId);
// NFT ownership should transfer to buyer
assertEq(nft.ownerOf(tokenId), buyer);
}

Recommended Mitigation

Remove the Buy Now logic entirely, enforce standard auction duration and rules

- if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
- uint256 salePrice = listing.buyNowPrice;
- uint256 overpay = msg.value - salePrice;
- bids[tokenId] = Bid(msg.sender, salePrice);
- listing.listed = false;
- if (previousBidder != address(0)) {
- _payout(previousBidder, previousBidAmount);
- }
- _executeSale(tokenId);
- if (overpay > 0) {
- _payout(msg.sender, overpay);
- }
- return;
- }
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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