Bid Beasts

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

Mismatch Between Auction Specification and Implementation

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.

// Protocol specifies "exactly 3 days" but implementation uses:
@> 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; // Should be 3 days
emit AuctionExtended(tokenId, listing.auctionEnd);
} else {
// Extension logic that can extend auctions indefinitely
if (timeLeft < S_AUCTION_EXTENSION_DURATION) {
@> listing.auctionEnd = listing.auctionEnd + S_AUCTION_EXTENSION_DURATION;
emit AuctionExtended(tokenId, listing.auctionEnd);
}
}
}

Risk

Likelihood: High

  • The issue occurs in every auction

  • Extensions happen automatically on every qualifying bid

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;
// Strategic bidding to extend auction indefinitely
for (uint256 i = 0; i < 155; i++) {
vm.warp(block.timestamp + 14 minutes); // Near extension threshold
BID_AMOUNT = (BID_AMOUNT / 100) * (100 + 5);
vm.prank(BIDDER_1);
market.placeBid{value: BID_AMOUNT}(TOKEN_ID);
vm.warp(block.timestamp + 14 minutes); // Near extension threshold
BID_AMOUNT = (BID_AMOUNT / 100) * (100 + 5);
vm.prank(BIDDER_2);
market.placeBid{value: BID_AMOUNT}(TOKEN_ID);
}
// Verify auction has been extended far beyond 3-day specification
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);
// Prove violation of protocol specification
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.
Updates

Lead Judging Commences

cryptoghost Lead Judge 30 days ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeasts Marketplace: Improper Documentation

Documentation for BidBeasts Marketplace is incomplete or inaccurate, potentially leading to misconfigurations or security misunderstandings.

Support

FAQs

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