Scruffy Chartreuse Wolverine
High
The unrestricted timeBuffer mechanism in the createBid function will cause auctions to be perpetually extended for other bidders as a malicious bidder will exploit the time buffer to continuously extend auctions, preventing legitimate participants from winning.
/**
* @notice Create a bid for a Noun, with a given amount.
* @dev This contract only accepts payment in ETH.
*/
function createBid(uint256 nounId) external payable override {
createBid(nounId, 0);
}
/**
* @notice Create a bid for a Noun, with a given amount.
* @param nounId id of the Noun to bid on
* @param clientId the client which facilitate this action
* @dev This contract only accepts payment in ETH.
*/
function createBid(uint256 nounId, uint32 clientId) public payable override {
INounsAuctionHouseV2.AuctionV2 memory _auction = auctionStorage;
(uint192 _reservePrice, uint56 _timeBuffer, uint8 _minBidIncrementPercentage) = (
reservePrice,
timeBuffer,
minBidIncrementPercentage
);
require(_auction.nounId == nounId, 'Noun not up for auction');
require(block.timestamp < _auction.endTime, 'Auction expired');
require(msg.value >= _reservePrice, 'Must send at least reservePrice');
require(
msg.value >= _auction.amount + ((_auction.amount * _minBidIncrementPercentage) / 100),
'Must send more than last bid by minBidIncrementPercentage amount'
);
auctionStorage.clientId = clientId;
auctionStorage.amount = uint128(msg.value);
auctionStorage.bidder = payable(msg.sender);
// Extend the auction if the bid was received within `timeBuffer` of the auction end time
bool extended = _auction.endTime - block.timestamp < _timeBuffer;
emit AuctionBid(_auction.nounId, msg.sender, msg.value, extended);
if (clientId > 0) emit AuctionBidWithClientId(_auction.nounId, msg.value, clientId);
if (extended) {
auctionStorage.endTime = _auction.endTime = uint40(block.timestamp + _timeBuffer);
emit AuctionExtended(_auction.nounId, _auction.endTime);
}
address payable lastBidder = _auction.bidder;
// Refund the last bidder, if applicable
if (lastBidder != address(0)) {
_safeTransferETHWithFallback(lastBidder, _auction.amount);
}
}
The choice to implement an unbounded timeBuffer
in the createBid
function is flawed. This design allows auctions to be extended indefinitely by permitting bidders to place bids within the timeBuffer
period without imposing any limits on the number of extensions.
- An auction is currently active with a defined
endTime
. - A malicious bidder can place bids repeatedly within the
timeBuffer
period to extend the auction's duration. - Each new bid meets or exceeds the
minBidIncrementPercentage
requirement over the previous bid. - The contract does not impose a maximum number of allowed extensions per auction.
The attacker has access to automated bots capable of monitoring auction end times and placing bids precisely within the timeBuffer
. The contract owner has not set any restrictions or limits on auction extensions.
- The attacker deploys a Malicious Bidder Bot.
- The attacker deploys the MaliciousBidderBot contract, targeting the NounsAuctionHouseV2 or NounsAuctionHouseV3 contract.
- The bot places an initial bid that meets or exceeds the
reservePrice
andminBidIncrementPercentage
, becoming the highest bidder. - The bot continuously monitors the auction's
endTime
. - As the auction approaches its
endTime
(within thetimeBuffer
period), the bot places another bid, triggering the extension of the auction'sendTime
bytimeBuffer
. - The bot continues to place bids within the
timeBuffer
period, perpetually extending the auction and preventing it from settling.
Legitimate bidders are unable to win auctions, leading to loss of participation and potential financial losses. The auction cadence is disrupted, potentially leading to delays in asset distribution and complications in system state management.
No response
- Introduce a maximum number of allowed extensions per auction (e.g., a maximum of 5 extensions).
- Enforce a cooldown period after each extension to prevent back-to-back last-second bids