Bid Beasts

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

[L-1] Miner can manipulate `block.timestamp` to artificially delay the auction end by a few seconds, harming the playing field by allowing themselves or other bidders to place bids when the auction should have ended.

[L-1] Miner can manipulate block.timestamp to artificially delay the auction end by a few seconds, harming the playing field by allowing themselves or other bidders to place bids when the auction should have ended.

Description

  • Normal behaviour: By placing a bid on an NFT during a live auction that is due to end in the next 15 minutes, the auction end is extended by another 15 minutes.

  • Problematic behaviour: A miner can manipulate the regular bidding logic in BidFeastsNFTMarketPlace::placeBid function and give themselves more time to bid on a listed NFT by setting the block.timestamp up to 15 seconds in the future compared to real time.

Root cause:

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);
}

Risk

Likelihood: Low (small time window and minimal financial incentive for the miner)

This vulnerability occurs when

  • A miner manipulates block.timestamp and sets it up to 15 seconds to the future.

  • A miner wants to allow themselves or other bidders more time to place bids.

Impact: Low (small time window of up to 15 seconds)

The block timestamp manipulation impacts the contract by

  • changing the intended bidding logic as set out in the BidBeastsNFTMarketPlace::placeBid function.

  • creating an imbalance in the playing field, giving more time to some users to place bids and potentially win an NFT.

Proof of Concept

As a PoC, add the following test in the Foundry test suite and run with forge test --mt test_BlockTimestampManipulation_AllowsMinerToDelayAuctionEnd:

function test_BlockTimestampManipulation_AllowsMinerToDelayAuctionEnd() public {
_mintNFT();
_listNFT();
// Original (real-time) block timestamp
uint256 originalTimestamp = block.timestamp;
// Miner sets block.timestamp 15 seconds in the future
uint256 manipulatedTimestamp = block.timestamp + 15 seconds;
vm.warp(manipulatedTimestamp);
// 1st bid
uint256 firstBid = MIN_PRICE * 2;
vm.prank(BIDDER_1);
market.placeBid{ value: firstBid }(TOKEN_ID);
// Check auction end after first bid
BidBeastsNFTMarket.Listing memory listing = market.getListing(TOKEN_ID);
uint256 auctionEnd = listing.auctionEnd;
uint256 expectedAuctionEnd = manipulatedTimestamp + market.S_AUCTION_EXTENSION_DURATION();
assertEq(auctionEnd, expectedAuctionEnd, "AuctionEnd manipulation to 15 seconds in the future did not work");
// Warp to 1 second before manipulated auction end
vm.warp(auctionEnd - 1);
// Placing asecond bid should succees
vm.prank(BIDDER_2);
uint256 secondBid = firstBid * 2;
market.placeBid{ value: secondBid }(TOKEN_ID);
// Check that the bidder and amount has been updated
BidBeastsNFTMarket.Bid memory bid = market.getHighestBid(TOKEN_ID);
assertEq(bid.bidder, BIDDER_2, "Bidder is not equal to BIDDER_2");
assertEq(bid.amount, secondBid, "Amount is not equal to bid value of BIDDER_2");
}

Recommended Mitigation

A) Accept the vulnerability and clearly document it in the contract.
B) Use block.number instead of block.timestamp since it is harder to manipulate and adjust the contract logic accordingly.

Example:

- listing.auctionEnd = block.timestamp + S_AUCTION_EXTENSION_DURATION;
+ listing.auctionEndBlock = block.number + (S_AUCTION_EXTENSION_DURATION / AVERAGE_BLOCK_TIME_IN_SECONDS);

where AVERAGE_BLOCK_TIME_IN_SECONDS is a constant variable with the average Ethereum block time.

C) Use an oracle (eg., Chainlink) to completely get rid of the time manipulation vulnerability.

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!