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 2 months 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.

Give us feedback!