The documentation describes a fair, open market where the highest bidder wins through competitive bidding, enabling true price discovery and equal opportunity for all participants.
The implementation in code allows leveraging _buyNowPrice in the function `listNft()`, which sabotages "Price Discovery". The final price is no longer discovered through the competition of bidders and does not reflect the true maximum a buyer was willing to pay. Additionally, the function `placeBid()` allows bidders to buy instantly at an already fixed final price. Therefore, any participant can bypass the entire competitive bidding process at any time by paying a fixed price aka _buyNowPrice.
struct Listing {
address seller;
uint256 minPrice;
@> uint256 buyNowPrice;
uint256 auctionEnd;
bool listed;
}
@> function listNFT(uint256 tokenId, uint256 _minPrice, uint256 _buyNowPrice) external {
require(BBERC721.ownerOf(tokenId) == msg.sender, "Not the owner");
require(_minPrice >= S_MIN_NFT_PRICE, "Min price too low");
@> if (_buyNowPrice > 0) {
@> require(_minPrice <= _buyNowPrice, "Min price cannot exceed buy now price");
@> }
BBERC721.transferFrom(msg.sender, address(this), tokenId);
listings[tokenId] = Listing({
seller: msg.sender,
minPrice: _minPrice,
@> buyNowPrice: _buyNowPrice,
auctionEnd: 0,
listed: true
});
emit NftListed(tokenId, msg.sender, _minPrice, _buyNowPrice);
}
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
Listing storage listing = listings[tokenId];
address previousBidder = bids[tokenId].bidder;
uint256 previousBidAmount = bids[tokenId].amount;
require(listing.seller != msg.sender, "Seller cannot bid");
require(listing.auctionEnd == 0 || block.timestamp < listing.auctionEnd, "Auction ended");
@>
@> if (listing.buyNowPrice > 0 && msg.value >= listing.buyNowPrice) {
@> uint256 salePrice = listing.buyNowPrice;
@> uint256 overpay = msg.value - salePrice;
@>
@> bids[tokenId] = Bid(msg.sender, salePrice);
@> listing.listed = false;
@> if (previousBidder != address(0)) {
@> _payout(previousBidder, previousBidAmount);
@> }
_executeSale(tokenId);
@>
@> if (overpay > 0) {
@> _payout(msg.sender, overpay);
@> }
@> return;
}
// Remove the buyNowPrice variable
struct Listing {
address seller;
uint256 minPrice;
- uint256 buyNowPrice;
uint256 auctionEnd;
bool listed;
}
// Remove the leverage to set the final price
+ function listNFT(uint256 tokenId, uint256 _minPrice) external {
- function listNFT(uint256 tokenId, uint256 _minPrice, uint256 _buyNowPrice) external {
require(BBERC721.ownerOf(tokenId) == msg.sender, "Not the owner");
require(_minPrice >= S_MIN_NFT_PRICE, "Min price too low");
- if (_buyNowPrice > 0) {
- require(_minPrice <= _buyNowPrice, "Min price cannot exceed buy now price");
- }
BBERC721.transferFrom(msg.sender, address(this), tokenId);
listings[tokenId] = Listing({
seller: msg.sender,
minPrice: _minPrice,
- buyNowPrice: _buyNowPrice,
auctionEnd: 0, // Timer starts only after the first valid bid.
listed: true
});
+ emit NftListed(tokenId, msg.sender, _minPrice);
- emit NftListed(tokenId, msg.sender, _minPrice, _buyNowPrice);
}
// Remove the leverage for immidiate winning
function placeBid(uint256 tokenId) external payable isListed(tokenId) {
Listing storage listing = listings[tokenId];
address previousBidder = bids[tokenId].bidder;
uint256 previousBidAmount = bids[tokenId].amount;
require(listing.seller != msg.sender, "Seller cannot bid");
// auctionEnd == 0 => no bids yet => allowed
// auctionEnd > 0 and block.timestamp >= auctionEnd => auction ended => block
require(listing.auctionEnd == 0 || block.timestamp < listing.auctionEnd, "Auction ended");
// --- 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;
- if (previousBidder != address(0)) {
- _payout(previousBidder, previousBidAmount);
- }
// NOTE: using internal finalize to do transfer/payouts. _executeSale will assume bids[tokenId] is the final winner.
- _executeSale(tokenId);
- // Refund overpay (if any) to buyer
- if (overpay > 0) {
- _payout(msg.sender, overpay);
- }
- return;
- }
require(msg.sender != previousBidder, "Already highest bidder");
emit AuctionSettled(tokenId, msg.sender, listing.seller, msg.value);
// --- 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;
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);
}
}
// EFFECT: update highest bid
bids[tokenId] = Bid(msg.sender, msg.value);
if (previousBidder != address(0)) {
_payout(previousBidder, previousBidAmount);
}
emit BidPlaced(tokenId, msg.sender, msg.value);
}