The following test demonstrates the vulnerability by placing a regular bid and capturing the emitted events. It proves that AuctionSettled is emitted while the auction's on-chain state remains active and unsettled.
pragma solidity 0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";
import {BidBeastsNFTMarket} from "../src/BidBeastsNFTMarketPlace.sol";
import {BidBeasts} from "../src/BidBeasts_NFT_ERC721.sol";
* @title VulnerabilityTest444
* @notice Test to verify the alleged vulnerability about misleading AuctionSettled event emission
* @dev This test examines whether AuctionSettled event is incorrectly emitted during regular bidding
*/
contract VulnerabilityTest444 is Test {
BidBeastsNFTMarket market;
BidBeasts nft;
address public constant OWNER = address(0x1);
address public constant SELLER = address(0x2);
address public constant BIDDER_1 = address(0x3);
address public constant BIDDER_2 = address(0x4);
uint256 public constant STARTING_BALANCE = 100 ether;
uint256 public constant TOKEN_ID = 0;
uint256 public constant MIN_PRICE = 1 ether;
uint256 public constant BUY_NOW_PRICE = 5 ether;
event AuctionSettled(uint256 tokenId, address winner, address seller, uint256 price);
event BidPlaced(uint256 tokenId, address bidder, uint256 amount);
function setUp() public {
vm.prank(OWNER);
nft = new BidBeasts();
market = new BidBeastsNFTMarket(address(nft));
vm.stopPrank();
vm.deal(SELLER, STARTING_BALANCE);
vm.deal(BIDDER_1, STARTING_BALANCE);
vm.deal(BIDDER_2, STARTING_BALANCE);
_mintAndListNFT();
}
function _mintAndListNFT() internal {
vm.prank(OWNER);
nft.mint(SELLER);
vm.startPrank(SELLER);
nft.approve(address(market), TOKEN_ID);
market.listNFT(TOKEN_ID, MIN_PRICE, BUY_NOW_PRICE);
vm.stopPrank();
}
* @notice Test to verify if AuctionSettled event is incorrectly emitted during regular bidding
* @dev This test checks the vulnerability described in report 444
*/
function test_MisleadingAuctionSettledEventDuringRegularBidding() public {
console.log("=== Testing Misleading AuctionSettled Event Vulnerability ===");
uint256 bidAmount = MIN_PRICE + 0.1 ether;
console.log("Placing regular bid (not buy-now) with amount:", bidAmount);
console.log("Expected: Only BidPlaced event should be emitted");
console.log("Vulnerability: AuctionSettled event is also emitted incorrectly");
vm.recordLogs();
vm.prank(BIDDER_1);
market.placeBid{value: bidAmount}(TOKEN_ID);
Vm.Log[] memory logs = vm.getRecordedLogs();
bool auctionSettledEmitted = false;
bool bidPlacedEmitted = false;
for (uint i = 0; i < logs.length; i++) {
if (logs[i].topics[0] == keccak256("AuctionSettled(uint256,address,address,uint256)")) {
auctionSettledEmitted = true;
console.log("VULNERABILITY CONFIRMED: AuctionSettled event was emitted during regular bidding");
if (logs[i].topics.length >= 4) {
uint256 eventTokenId = uint256(logs[i].topics[1]);
address eventWinner = address(uint160(uint256(logs[i].topics[2])));
address eventSeller = address(uint160(uint256(logs[i].topics[3])));
uint256 eventPrice = abi.decode(logs[i].data, (uint256));
console.log("Event details - TokenId:", eventTokenId);
console.log("Event details - Winner:", eventWinner);
console.log("Event details - Seller:", eventSeller);
console.log("Event details - Price:", eventPrice);
}
}
if (logs[i].topics[0] == keccak256("BidPlaced(uint256,address,uint256)")) {
bidPlacedEmitted = true;
console.log("Correct event: BidPlaced was also emitted");
}
}
BidBeastsNFTMarket.Listing memory listing = market.getListing(TOKEN_ID);
assertTrue(listing.listed, "Auction should still be active/listed");
address nftOwner = nft.ownerOf(TOKEN_ID);
assertEq(nftOwner, address(market), "NFT should still be in marketplace, not transferred to bidder");
assertEq(BIDDER_1.balance, STARTING_BALANCE - bidAmount, "Bidder's balance should be deducted");
console.log("=== Vulnerability Analysis ===");
console.log("AuctionSettled emitted during regular bid:", auctionSettledEmitted);
console.log("BidPlaced emitted correctly:", bidPlacedEmitted);
console.log("Auction still active:", listing.listed);
console.log("NFT still in marketplace:", nftOwner == address(market));
if (auctionSettledEmitted && listing.listed) {
console.log("VULNERABILITY CONFIRMED: AuctionSettled event emitted but auction is still active");
console.log("This creates misleading information for off-chain systems");
}
assertTrue(auctionSettledEmitted, "Vulnerability: AuctionSettled should not be emitted during regular bidding");
}
* @notice Test to verify correct behavior when buy-now is used
* @dev This test shows when AuctionSettled should legitimately be emitted
*/
function test_CorrectAuctionSettledEventDuringBuyNow() public {
console.log("=== Testing Correct AuctionSettled Event During Buy-Now ===");
vm.recordLogs();
vm.prank(BIDDER_1);
market.placeBid{value: BUY_NOW_PRICE}(TOKEN_ID);
Vm.Log[] memory logs = vm.getRecordedLogs();
bool auctionSettledEmitted = false;
for (uint i = 0; i < logs.length; i++) {
if (logs[i].topics[0] == keccak256("AuctionSettled(uint256,address,address,uint256)")) {
auctionSettledEmitted = true;
console.log("Correct: AuctionSettled emitted during buy-now purchase");
}
}
BidBeastsNFTMarket.Listing memory listing = market.getListing(TOKEN_ID);
assertFalse(listing.listed, "Auction should be settled (not listed)");
address nftOwner = nft.ownerOf(TOKEN_ID);
assertEq(nftOwner, BIDDER_1, "NFT should be transferred to buyer");
console.log("Buy-now correctly settled auction:", !listing.listed);
console.log("NFT correctly transferred to buyer:", nftOwner == BIDDER_1);
assertTrue(auctionSettledEmitted, "AuctionSettled should be emitted during buy-now");
}
* @notice Test multiple regular bids to see if vulnerability persists
* @dev This test checks if the misleading event is emitted on subsequent bids
*/
function test_MultipleRegularBidsEmitMisleadingEvents() public {
console.log("=== Testing Multiple Regular Bids ===");
uint256 firstBid = MIN_PRICE + 0.1 ether;
vm.recordLogs();
vm.prank(BIDDER_1);
market.placeBid{value: firstBid}(TOKEN_ID);
Vm.Log[] memory logs1 = vm.getRecordedLogs();
bool firstBidAuctionSettled = false;
for (uint i = 0; i < logs1.length; i++) {
if (logs1[i].topics[0] == keccak256("AuctionSettled(uint256,address,address,uint256)")) {
firstBidAuctionSettled = true;
}
}
uint256 secondBid = (firstBid * 105) / 100;
vm.recordLogs();
vm.prank(BIDDER_2);
market.placeBid{value: secondBid}(TOKEN_ID);
Vm.Log[] memory logs2 = vm.getRecordedLogs();
bool secondBidAuctionSettled = false;
for (uint i = 0; i < logs2.length; i++) {
if (logs2[i].topics[0] == keccak256("AuctionSettled(uint256,address,address,uint256)")) {
secondBidAuctionSettled = true;
}
}
console.log("First bid emitted AuctionSettled:", firstBidAuctionSettled);
console.log("Second bid emitted AuctionSettled:", secondBidAuctionSettled);
BidBeastsNFTMarket.Listing memory listing = market.getListing(TOKEN_ID);
assertTrue(listing.listed, "Auction should still be active after multiple bids");
assertTrue(firstBidAuctionSettled, "First bid should have incorrectly emitted AuctionSettled");
assertTrue(secondBidAuctionSettled, "Second bid should have incorrectly emitted AuctionSettled");
}
}