Description
Leading by the docs we must have endAuction function and 3days lifespan of the Auctions.
Docs: https://github.com/CodeHawks-Contests/2025-09-bid-beasts/blob/main/README.md
Risk
Likelihood:
Impact:
Proof of Concept
Missing endAuction function that is described in the docs
Missing/Wrong logic for tracking 3 days required for proper functioning of the contract
Incorrect
The lifecycle of the auction is fundamentally different from what is documented
It starts with 15 minutes window and then refreshes this window if anyone place a bid.
Correct
The 3 days start when the auction is listed
after this 3 days the endAuction function can be called to transfer the NFT
to the seller if no bids were placed or to the highest bidder.
Wrong logic for tracking Auction lifecycle:
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
More code...
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);
@> }
@> }
More code...
}
There is Incorect Auction lifecycle that leads to ending of listings after 15 minutes of innactivity.
To fix the issues we need to:
Recommended Mitigation
To fix all the issues related to the Incomplete/Wrong Implementation
We need to:
Refactor placeBid function
on line 34 we need to change the constant https://github.com/CodeHawks-Contests/2025-09-bid-beasts/blob/449341c55a57d3f078d1250051a7b34625d3aa04/src/BidBeastsNFTMarketPlace.sol#L34
we refactor the placeBid function code to handle correctly the 3 days as expected
- uint256 constant public S_AUCTION_EXTENSION_DURATION = 15 minutes;
+ uint256 public constant S_AUCTION_DEADLINE = 3 days;
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
More code...
// --- Regular Bidding Logic ---
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;
+ listing.auctionEnd = block.timestamp + S_AUCTION_DEADLINE;
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);
- }
- }
More code...
}
Implement endAuction
Add the function + Event
// --- Events ---
event NftListed(uint256 tokenId, address seller, uint256 minPrice, uint256 buyNowPrice);
event NftUnlisted(uint256 tokenId);
event BidPlaced(uint256 tokenId, address bidder, uint256 amount);
event AuctionExtended(uint256 tokenId, uint256 newDeadline);
event AuctionSettled(uint256 tokenId, address winner, address seller, uint256 price);
event FeeWithdrawn(uint256 amount);
+ event AuctionEnded(uint256 tokenId);
function endAuction(uint256 tokenId) external isListed(tokenId) {
Listing storage listing = listings[tokenId];
require(block.timestamp >= listing.auctionEnd, "Auction has not ended");
if (listing.auctionEnd != 0) {
_executeSale(tokenId);
} else {
listing.listed = false;
delete bids[tokenId];
BBERC721.transferFrom(address(this), listing.seller, tokenId);
}
emit AuctionEnded(tokenId);
}
Test the endAuction
function test_endAuctionWithBidder() public {
_mintNFT();
_listNFT();
uint256 newBid = MIN_PRICE + 10;
vm.prank(BIDDER_1);
market.placeBid{value: newBid}(TOKEN_ID);
vm.warp(block.timestamp + 3 days);
market.endAuction(TOKEN_ID);
assertEq(nft.ownerOf(TOKEN_ID), BIDDER_1, "NFT should be transferred to highest bidder");
}
function test_endAuctionWithoutBidder() public {
_mintNFT();
_listNFT();
vm.warp(block.timestamp + 3 days);
market.endAuction(TOKEN_ID);
assertEq(nft.ownerOf(TOKEN_ID), SELLER, "NFT should be transferred back to SELLER");
}