Auction Duration Mismatch Violates Protocol Specification
Description
-
The normal behavior according to the protocol specification should be that auctions last exactly 3 days from the first bid, providing a predictable and fair timeframe for all participants.
-
The current implementation uses a 15-minute extension mechanism that can extend auctions indefinitely, completely violating the "exactly 3 days" requirement and creating unpredictable auction durations.
@> uint256 public constant S_AUCTION_EXTENSION_DURATION = 15 minutes;
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
if (previousBidAmount == 0) {
@> listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
} else {
if (timeLeft < S_AUCTION_EXTENSION_DURATION) {
@> listing.auctionEnd = listing.auctionEnd + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
}
}
}
Risk
Likelihood: High
Impact: Medium
-
Protocol violation: Core functionality doesn't match documented behavior
-
Trust issues: Protocol doesn't behave as advertised to users
-
User confusion: Participants can't predict when auctions will actually end
Proof of Concept
The PoC demonstrates how auctions that should last exactly 3 days can be extended indefinitely, violating the protocol specification.
Add the test below to the BidBeastsMarketPlaceTest.t.sol:
function testAuctionDoesntEndAfter3DaysWhenItShould() public {
_mintNFT();
vm.startPrank(SELLER);
nft.approve(address(market), TOKEN_ID);
market.listNFT(TOKEN_ID, MIN_PRICE, 0);
vm.stopPrank();
vm.deal(BIDDER_1, type(uint256).max - STARTING_BALANCE);
vm.deal(BIDDER_2, type(uint256).max - STARTING_BALANCE);
uint256 BID_AMOUNT = MIN_PRICE;
uint256 expectedListingEnd = block.timestamp + 3 days;
for (uint256 i = 0; i < 155; i++) {
vm.warp(block.timestamp + 14 minutes);
BID_AMOUNT = (BID_AMOUNT / 100) * (100 + 5);
vm.prank(BIDDER_1);
market.placeBid{value: BID_AMOUNT}(TOKEN_ID);
vm.warp(block.timestamp + 14 minutes);
BID_AMOUNT = (BID_AMOUNT / 100) * (100 + 5);
vm.prank(BIDDER_2);
market.placeBid{value: BID_AMOUNT}(TOKEN_ID);
}
BidBeastsNFTMarket.Listing memory listing = market.getListing(TOKEN_ID);
console.log("Final auction end time:", listing.auctionEnd);
console.log("Expected auction end time (3 days):", expectedListingEnd);
assertTrue(listing.auctionEnd > expectedListingEnd, "Auction extended beyond 3-day specification");
}
Run the test with:
forge test --match-path test/BidBeastsMarketPlaceTest.t.sol --match-test testAuctionDoesntEndAfter3DaysWhenItShould
Recommended Mitigation
There are two valid approaches to resolve this protocol specification mismatch:
Option 1: Fix the Implementation to Match Specification
- uint256 public constant S_AUCTION_EXTENSION_DURATION = 15 minutes;
+ uint256 public constant S_AUCTION_DURATION = 3 days;
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
// --- Buy Now Logic ---
// .....................
// --- Regular Bidding Logic ---
uint256 requiredAmount;
if (previousBidAmount == 0) {
- listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION;
+ listing.auctionEnd = block.timestamp + S_AUCTION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
+ }
// Remove all extension logic to maintain exactly 3 days
} 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);
- }
}
// EFFECT: update highest bid
// ..........................
}
Option 2: Update Documentation to Match Implementation
## The flow is simple:
3. **Auction Completion**:
- * After 3 days, anyone can call `endAuction(tokenId)` to finalize the auction.
+ * Auctions start with 15-minute duration and extend by 15 minutes on each new bid placed within the final 15 minutes.
## The contract also supports:
- * **Auction deadline** of exactly 3 days.
+ * **Dynamic auction extensions** of 15 minutes when bids are placed near auction end.