Bid Beasts

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

Seller Locked from NFT Recovery Post-Bidding

Seller Locked from NFT Recovery Post-Bidding

Description

Sellers list NFTs for auction and can unlist only if no bids exist. Once a bid is placed, sellers cannot unlist or bid to outbid others, preventing recovery of their own asset if they change their mind.

/**
* @notice Allows the seller to unlist an NFT if no bids have been made.
*/
function unlistNFT(uint256 tokenId) external isListed(tokenId) isSeller(tokenId, msg.sender) {
@>require(bids[tokenId].bidder == address(0), "Cannot unlist, a bid has been placed");@>
@>// ...@>
}
/**
* @notice Places a bid on a listed NFT. Extends the auction on each new bid.
*/
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
Listing storage listing = listings[tokenId];
@>require(listing.seller != msg.sender, "Seller cannot bid");@>
// ...
}

Risk

Likelihood:

  • When a seller lists an NFT but receives an unsolicited low bid.

  • During volatile market conditions prompting seller regret after auction start.

Impact:

  • Seller forfeits control, risking sale at unfavorable price.

  • Erodes trust in marketplace, deterring listings.

Proof of Concept

Lists NFT, places initial bid, then attempts seller unlist (reverts) and self-bid (reverts), verifying unchanged bid to show locked recovery.

function testSellerCannotRecoverNFTAfterBid() public {
_mintNFT();
_listNFT();
// BIDDER_1 places initial bid
vm.deal(BIDDER_1, MIN_PRICE + 0.01 ether);
vm.prank(BIDDER_1);
market.placeBid{value: MIN_PRICE + 0.01 ether}(TOKEN_ID);
// Seller tries to unlist - should fail
vm.prank(SELLER);
vm.expectRevert("Cannot unlist, a bid has been placed");
market.unlistNFT(TOKEN_ID);
// Seller tries to bid to outbid - should fail
vm.expectRevert("Seller cannot bid");
vm.prank(SELLER);
market.placeBid{value: MIN_PRICE + 0.02 ether}(TOKEN_ID);
// Verify bid unchanged
BidBeastsNFTMarket.Bid memory currentBid = market.getHighestBid(TOKEN_ID);
assertEq(currentBid.bidder, BIDDER_1);
}

Recommended Mitigation

Removes unlist bid restriction with refund/delete logic; lifts seller bid ban, enabling post-bid recovery or self-outbidding.
The seller should be given a chance if he changes his mind by correcting one or both functions.

/**
* @notice Allows the seller to unlist an NFT if no bids have been made.
*/
function unlistNFT(uint256 tokenId) external isListed(tokenId) isSeller(tokenId, msg.sender) {
- require(bids[tokenId].bidder == address(0), "Cannot unlist, a bid has been placed");
+ // Allow unlist even with bids; refund bidder if any
+ Bid storage bid = bids[tokenId];
+ if (bid.bidder != address(0)) {
+ _payout(bid.bidder, bid.amount);
+ delete bids[tokenId];
+ }
Listing storage listing = listings[tokenId];
listing.listed = false;
BBERC721.transferFrom(address(this), msg.sender, tokenId);
emit NftUnlisted(tokenId);
}
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
Listing storage listing = listings[tokenId];
- require(listing.seller != msg.sender, "Seller cannot bid");
// ... rest unchanged
}
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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