function placeBid(uint256 tokenId) external payable isListed(tokenId) {
Listing storage listing = listings[tokenId];
address previousBidder = bids[tokenId].bidder;
uint256 previousBidAmount = bids[tokenId].amount;
require(listing.seller != msg.sender, "Seller cannot bid");
require(listing.auctionEnd == 0 || block.timestamp < listing.auctionEnd, "Auction ended");
if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
uint256 salePrice = listing.buyNowPrice;
uint256 overpay = msg.value - salePrice;
bids[tokenId] = Bid(msg.sender, salePrice);
listing.listed = false;
if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmount);
}
_executeSale(tokenId);
if (overpay > 0) {
_payout(msg.sender, overpay);
}
return;
}
require(msg.sender != previousBidder, "Already highest bidder");
emit AuctionSettled(tokenId, msg.sender, listing.seller, msg.value);
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 {
requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
require(msg.value >= requiredAmount, "Bid not high enough");
uint256 timeLeft = 0;
if (listing.auctionEnd > block.timestamp) {
timeLeft = listing.auctionEnd - block.timestamp;
}
if (timeLeft < S_AUCTION_EXTENSION_DURATION) {
listing.auctionEnd = listing.auctionEnd + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
}
}
bids[tokenId] = Bid(msg.sender, msg.value);
if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmount);
}
emit BidPlaced(tokenId, msg.sender, msg.value);
}
function testBidIncrementPrecisionLoss() public {
address seller = address(0x1);
address bidder1 = address(0x2);
address bidder2 = address(0x3);
vm.prank(owner);
nft.mint(seller, 1);
vm.startPrank(seller);
nft.approve(address(marketplace), 1);
marketplace.listNFT(1, 0.01 ether, 0);
vm.stopPrank();
uint256 firstBid = 1.99 ether;
vm.deal(bidder1, firstBid);
vm.prank(bidder1);
marketplace.placeBid{value: firstBid}(1);
uint256 actualRequired = (firstBid * 105) / 100;
uint256 flawedRequired = (firstBid / 100) * 105;
uint256 underbid = actualRequired - flawedRequired;
assertEq(underbid, 0.0105 ether);
vm.deal(bidder2, flawedRequired);
vm.prank(bidder2);
marketplace.placeBid{value: flawedRequired}(1);
assert(flawedRequired < actualRequired);
}
+ // Add safe math for ceiling division
+ function ceilDiv(uint256 a, uint256 b) private pure returns (uint256) {
+ return (a + b - 1) / b;
+ }
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
// ... same code until the calculation ...
} else {
- requiredAmount = (previousBidAmount / 100) * (100 + S_MIN_BID_INCREMENT_PERCENTAGE);
+ requiredAmount = ceilDiv(previousBidAmount * (100 + S_MIN_BID_INCREMENT_PERCENTAGE), 100);
require(msg.value >= requiredAmount, "Bid not high enough");
// ... rest of function ...
}