Bid Beasts

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

Inconsistent Bid Requirements Create Buy-Now Bypass Vulnerability

Inconsistent Bid Requirements Create Buy-Now Bypass Vulnerability

Description

  • The auction system should consistently enforce that regular bids must exceed the minimum price while buy-now purchases can be made at exactly the buy-now price.

  • The contract has inconsistent bid validation logic where regular bids require msg.value > minPrice but buy-now purchases allow msg.value >= buyNowPrice, creating a scenario where users cannot bid at the minimum price but can purchase at that exact price when minPrice == buyNowPrice.

In src/BidBeastsNFTMarketPlace.sol:

function placeBid(uint256 tokenId) external payable isListed(tokenId) {
// Buy-now logic allows exact price match
@> if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) { //Uses >= allowing exact match
// ... buy-now execution
return;
}
// Regular bidding logic requires exceeding minimum
if (previousBidAmount == 0) {
requiredAmount = listing.minPrice;
@> require(msg.value > requiredAmount, "First bid must be > min price"); //Uses > requiring excess
//existing code
}
//existing code
}

Risk

Likelihood:

  • Sellers commonly set minPrice == buyNowPrice to create fixed-price sales rather than auctions.

  • Users attempting to bid at the minimum price will encounter unexpected reverts while buy-now purchases succeed.

  • This inconsistency occurs every time a seller lists an NFT with equal minimum and buy-now prices

Impact:

  • Users cannot participate in regular bidding when minPrice == buyNowPrice, forcing them into immediate buy-now purchases.

  • Breaks the intended auction mechanism by preventing competitive bidding at the minimum price threshold.

Proof of Concept

First we need to make a quick fix in test/BidBeastsMarketPlaceTest.t.sol:BidBeastsNFTMarketTest::setUp()

function setUp() public {
// Deploy contracts
- vm.prank(OWNER);
+ vm.startPrank(OWNER);
nft = new BidBeasts();
market = new BidBeastsNFTMarket(address(nft));
rejector = new RejectEther();
vm.stopPrank();
// Fund users
vm.deal(SELLER, STARTING_BALANCE);
vm.deal(BIDDER_1, STARTING_BALANCE);
vm.deal(BIDDER_2, STARTING_BALANCE);
}

Please add the following test to test/BidBeastsMarketPlaceTest.t.sol:

function testBugNowPriceEqualsMinPrice() public {
/* ---------------------------- Seller lists NFT ---------------------------- */
_mintNFT();
vm.startPrank(SELLER);
nft.approve(address(market), TOKEN_ID);
market.listNFT(TOKEN_ID, MIN_PRICE, MIN_PRICE);
vm.stopPrank();
/* ---------------------------- BIDDER_1 bids ---------------------------- */
vm.prank(BIDDER_1);
market.placeBid{value: MIN_PRICE}(TOKEN_ID);
address NFTowner = nft.ownerOf(TOKEN_ID);
assertEq(NFTowner, BIDDER_1);
}
function testBidderBidsExactlyMinPrice() public {
_mintNFT();
_listNFT();
vm.prank(BIDDER_1);
vm.expectRevert("First bid must be > min price");
market.placeBid{value: MIN_PRICE}(TOKEN_ID);
}

Then run forge test --mt testBugNowPriceEqualsMinPrice and forge test --mt testBidderBidsExactlyMinPrice.

Output:

Ran 1 test for test/BidBeastsMarketPlaceTest.t.sol:BidBeastsNFTMarketTest
[PASS] testBugNowPriceEqualsMinPrice() (gas: 286081)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.31ms (139.61µs CPU time)
Ran 1 test for test/BidBeastsMarketPlaceTest.t.sol:BidBeastsNFTMarketTest
[PASS] testBidderBidsExactlyMinPrice() (gas: 221672)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.23ms (686.99µs CPU time

Recommended Mitigation

Change the regular bidding logic to use >= for consistency with buy-now logic:

function placeBid(uint256 tokenId) external payable isListed(tokenId) {
// ... existing code ...
if (previousBidAmount == 0) {
requiredAmount = listing.minPrice;
- require(msg.value > requiredAmount, "First bid must be > min price");
+ require(msg.value >= requiredAmount, "First bid must be >= min price");
listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
}
// ... rest of function ...
}
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeasts Marketplace: First Bid > Instead of >=

First bid validation uses > instead of >=, preventing valid starting bids.

Support

FAQs

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