Bid Beasts

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

False “sale settled” event emitted during bidding.

False “sale settled” event emitted during bidding.

Description

  • The contract should emit AuctionSettled only when a sale actually settles—that is, when _executeSale transfers the NFT to the winner and distributes the proceeds (either via takeHighestBid, settleAuction, or the buy‑now branch inside placeBid that immediately finalizes and returns).

  • placeBid emits AuctionSettled during normal bidding, before any settlement occurs and while the NFT remains escrowed in the marketplace. This creates a false positive sale event that can mislead off‑chain indexers, analytics, or automation that react to settlement events.

function placeBid(uint256 tokenId) external payable isListed(tokenId) {
Listing storage listing = listings[tokenId];
address previousBidder = bids[tokenId].bidder;
uint256 previousBidAmount = bids[tokenId].amount;
// ... [checks and buy-now branch above] ...
require(msg.sender != previousBidder, "Already highest bidder");
@> emit AuctionSettled(tokenId, msg.sender, listing.seller, msg.value); // ❌ emitted during bidding (no sale)
// --- Regular bidding logic continues ---
uint256 requiredAmount;
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 {
// ...
}
bids[tokenId] = Bid(msg.sender, msg.value);
if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmount);
}
emit BidPlaced(tokenId, msg.sender, msg.value);
}

Risk

Likelihood:

  • This occurs on every non–buy‑now bid placed via placeBid, since the erroneous emit is reached in the normal bidding path.

  • Typical auction flows involve many bids; thus, multiple spurious settlement events are produced throughout the auction’s lifetime.

Impact:

  • Off‑chain consumers (indexers, bots, subgraphs, accounting systems) that rely on AuctionSettled to trigger asset handover, accounting, or notifications will record false settlements, causing operational errors or premature actions.

  • Data integrity & monitoring are compromised: analytics, revenue reporting, and alerting pipelines may reflect incorrect sale counts and volumes; incident response tools may misfire on bogus “settlements.”

Proof of Concept

A minimal Foundry test that shows AuctionSettled is emitted during a regular bid, while no settlement happens (NFT remains owned by the marketplace and listing stays active).

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "forge-std/Test.sol";
import "../src/BidBeasts_NFT_ERC721.sol";
import "../src/BidBeastsNFTMarketPlace.sol";
contract FalseSettlementEventTest is Test {
BidBeasts nft;
BidBeastsNFTMarket market;
address seller = address(0xA11CE);
address bidder1 = address(0xB1D01);
uint256 tokenId;
// Mirror the event signature from the marketplace
event AuctionSettled(uint256 tokenId, address winner, address seller, uint256 price);
function setUp() public {
// Deploy NFT & Market
nft = new BidBeasts();
market = new BidBeastsNFTMarket(address(nft));
// Mint to seller (onlyOwner is the test contract, so mint directly)
tokenId = nft.mint(seller);
// Seller approves & lists at min price = 0.01 ether, no buy-now
vm.startPrank(seller);
nft.approve(address(market), tokenId);
market.listNFT(tokenId, 0.01 ether, 0);
vm.stopPrank();
// Fund bidder
vm.deal(bidder1, 10 ether);
}
function test_AuctionSettledEmittedDuringBid_NoActualSettlement() public {
// Expect a (false) AuctionSettled event on a normal bid
vm.expectEmit(true, true, true, true);
emit AuctionSettled(tokenId, bidder1, seller, 0.02 ether);
// Place a valid first bid (> min price)
vm.prank(bidder1);
market.placeBid{value: 0.02 ether}(tokenId);
// ASSERT: No settlement actually happened
// 1) NFT still escrowed by marketplace
assertEq(nft.ownerOf(tokenId), address(market), "NFT should remain in escrow");
// 2) Listing still active (not marked as settled/unlisted)
BidBeastsNFTMarket.Listing memory L = market.getListing(tokenId);
assertTrue(L.listed, "Listing should remain active after a normal bid");
// 3) Highest bid recorded properly (normal bidding path)
BidBeastsNFTMarket.Bid memory B = market.getHighestBid(tokenId);
assertEq(B.bidder, bidder1);
assertEq(B.amount, 0.02 ether);
}
}

Recommended Mitigation

Emit AuctionSettled only from the true settlement path(s) (i.e., _executeSale and the buy‑now branch that calls it).

function placeBid(uint256 tokenId) external payable isListed(tokenId) {
Listing storage listing = listings[tokenId];
address previousBidder = bids[tokenId].bidder;
uint256 previousBidAmount = bids[tokenId].amount;
// ... checks and buy-now branch ...
- require(msg.sender != previousBidder, "Already highest bidder");
- emit AuctionSettled(tokenId, msg.sender, listing.seller, msg.value); // ❌ remove
+ require(msg.sender != previousBidder, "Already highest bidder");
// proceed with regular bidding logic...
}
Updates

Lead Judging Commences

cryptoghost Lead Judge 24 days ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeasts Marketplace: Incorrect Event Emission

placeBid emits AuctionSettled even though the auction hasn’t ended, causing misleading event logs.

Support

FAQs

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