Bid Beasts

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

Different bidders receive different defense times, which violates the fairness principle of the auction.

Root + Impact

Description

  • Inconsistent defense time: the less time left, the shorter the total time after extension

  • Unfair auction mechanism: different bidders get different defense time

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) {
// @NoticeDifferent bidders receive different defense times, which violates the fairness principle of the auction.
listing.auctionEnd = listing.auctionEnd + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
}
}

Risk

Likelihood:

  • The earlier the bidder places his bid, the longer the total remaining time will be, which violates the principle of fairness of the auction..

Impact:

  • Bidders cannot accurately predict the end time after the extension

  • The less time remaining, the shorter the total time after extension

Proof of Concept

function test_auctionTimeExtensionUnfairness() public {
_mintNFT();
_listNFT();
// First bid - starts auction
vm.prank(BIDDER_1);
market.placeBid{value: MIN_PRICE}(TOKEN_ID);
uint256 firstAuctionEnd = market.getListing(TOKEN_ID).auctionEnd;
console.log("First auction end time:", firstAuctionEnd);
console.log("Current time:", block.timestamp);
console.log("Initial auction duration:", firstAuctionEnd - block.timestamp);
// Wait 5 minutes (300 seconds)
vm.warp(block.timestamp + 300);
// Second bid with 5 minutes remaining
uint256 secondBidAmount = MIN_PRICE * 120 / 100;
vm.prank(BIDDER_2);
market.placeBid{value: secondBidAmount}(TOKEN_ID);
uint256 secondAuctionEnd = market.getListing(TOKEN_ID).auctionEnd;
console.log("Second auction end time:", secondAuctionEnd);
console.log("Time after second bid:", block.timestamp);
console.log("Remaining time after second bid:", secondAuctionEnd - block.timestamp);
// Wait another 10 minutes (600 seconds) - now only 5 minutes left
vm.warp(block.timestamp + 600);
// Third bid with only 5 minutes remaining
uint256 thirdBidAmount = secondBidAmount * 120 / 100;
vm.prank(BIDDER_1);
market.placeBid{value: thirdBidAmount}(TOKEN_ID);
uint256 thirdAuctionEnd = market.getListing(TOKEN_ID).auctionEnd;
console.log("Third auction end time:", thirdAuctionEnd);
console.log("Time after third bid:", block.timestamp);
console.log("Remaining time after third bid:", thirdAuctionEnd - block.timestamp);
// PROOF OF UNFAIRNESS:
// Second bidder got: secondAuctionEnd - (block.timestamp after second bid)
// Third bidder got: thirdAuctionEnd - (block.timestamp after third bid)
uint256 secondBidderDefenseTime = secondAuctionEnd - (block.timestamp - 600);
uint256 thirdBidderDefenseTime = thirdAuctionEnd - block.timestamp;
console.log("Second bidder defense time:", secondBidderDefenseTime);
console.log("Third bidder defense time:", thirdBidderDefenseTime);
// This proves the unfairness: earlier bidders get longer total defense time
assertTrue(secondBidderDefenseTime > thirdBidderDefenseTime,
"Earlier bidders should get longer defense time - this violates fairness!");
console.log("UNFAIRNESS PROVEN: Earlier bidders get longer total defense time");
}

Recommended Mitigation

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) {
// @Notice Whenever a bid is placed, the auction will be extended to the current time + 15 minutes.
listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
}
}
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeast Marketplace: Auction Duration Miscalculation

BidBeast marketplace contains a flaw in its auction timing mechanism. This causes the contract to miscalculate the actual end time of an auction, resulting in auctions that either conclude prematurely or run longer than specified.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.