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 3 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!