Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: low
Likelihood: medium
Invalid

Inconsistent Auction State Due to Missing auctionEnd Update in Buy Now Logic

Root + Impact

The buyNow logic of placeBid function finalizes the NFT transfer and disables listing, but it does not update the auctionEnd timestamp. This creates a mismatch between the auction’s actual status (ended) and the auctionEnd value. Such inconsistent state can confuse off-chain indexers, marketplaces, and analytics that rely on accurate auction timing data.

Description

  • The normal behavior in auction systems is to maintain consistent state values for all auction properties when the auction status changes, including setting appropriate end times.

  • The specific issue is that when a user executes the "Buy Now" option in the placeBid function, the listing.listed status is updated to false, but the listing.auctionEnd value is never updated, leaving it in an inconsistent state.

// In placeBid function, Buy Now logic:
if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
uint256 salePrice = listing.buyNowPrice;
uint256 overpay = msg.value - salePrice;
// EFFECT: set winner bid to exact sale price (keep consistent)
bids[tokenId] = Bid(msg.sender, salePrice);
listing.listed = false; @> // Auction status updated
// No update to auctionEnd value @> // Inconsistency here
// ... remainder of function ...
}

Risk

Likelihood: Medium

  • This issue occurs in every instance where the Buy Now option is used, which is a core feature of the marketplace.

*The inconsistency is systematic and will happen consistently with normal usage.

Impact: Low

  • The contract state becomes inconsistent, with listing.listed = false (indicating auction is over) but auctionEnd potentially remaining at 0 or a future timestamp.

  • Off-chain systems or analytics tools querying the auction end time might receive confusing or incorrect information.

  • No direct financial loss occurs, but it creates potential logical inconsistencies in the contract's state representation.

Proof of Concept

async function demonstrateInconsistentAuctionEndState() {
// Setup: Deploy contracts, mint NFT, list for auction
const nft = await BidBeasts.deploy();
const marketplace = await BidBeastsNFTMarket.deploy(nft.address);
// Mint and list NFT with buyNowPrice
const tokenId = await nft.mint(seller.address);
await nft.connect(seller).approve(marketplace.address, tokenId);
const minPrice = ethers.utils.parseEther("1.0");
const buyNowPrice = ethers.utils.parseEther("3.0");
await marketplace.connect(seller).listNFT(tokenId, minPrice, buyNowPrice);
// Get listing before buy now
const listingBefore = await marketplace.getListing(tokenId);
console.log("Before Buy Now:");
console.log(" Listed:", listingBefore.listed);
console.log(" auctionEnd:", listingBefore.auctionEnd.toString());
// User executes buy now
await marketplace.connect(buyer).placeBid(tokenId, { value: buyNowPrice });
// Get listing after buy now
const listingAfter = await marketplace.getListing(tokenId);
console.log("After Buy Now:");
console.log(" Listed:", listingAfter.listed);
console.log(" auctionEnd:", listingAfter.auctionEnd.toString());
// Verification
if (!listingAfter.listed && listingAfter.auctionEnd.eq(listingBefore.auctionEnd)) {
console.log("INCONSISTENCY DETECTED: Auction is not listed but auctionEnd was not updated");
}
}
// Expected output:
// Before Buy Now:
// Listed: true
// auctionEnd: 0
// After Buy Now:
// Listed: false
// auctionEnd: 0
// INCONSISTENCY DETECTED: Auction is not listed but auctionEnd was not updated

This demonstrates that:

  1. When a listing is created, auctionEnd is set to 0

  2. After a Buy Now purchase, the listed status is updated to false

  3. However, the auctionEnd value remains at 0, creating an inconsistent state

  4. This inconsistency could confuse systems that rely on both values to determine auction status

Recommended Mitigation

if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
uint256 salePrice = listing.buyNowPrice;
uint256 overpay = msg.value - salePrice;
// EFFECT: set winner bid to exact sale price (keep consistent)
bids[tokenId] = Bid(msg.sender, salePrice);
listing.listed = false;
+ listing.auctionEnd = block.timestamp; // Set auction end time to current time
if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmount);
}

This simple change:

  1. State Consistency: Ensures all auction state variables are updated properly when an auction ends via Buy Now.

  2. Accurate Timing Information: Sets the auction end time to the actual time the auction ended, which is useful for historical data and analytics.

  3. Reliable Querying: Makes sure that both listed and auctionEnd values consistently reflect the ended state of the auction.

  4. Clear Data Model: Maintains a clear and consistent data model where "not listed" auctions always have a non-zero, past timestamp in auctionEnd.

By making this change, the contract state will be more consistent and reliable, especially for off-chain systems that may rely on the auctionEnd value for analytics or displaying auction status to users.

Updates

Lead Judging Commences

cryptoghost Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!