DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: medium
Valid

User’s can bid after an auction has ended

Vulnerability Details:

The bid function in the FjordAuction contract allows users to place bids as long as the current block timestamp is not greater than the auctionEndTime, as shown below:

function bid(uint256 amount) external {
if (block.timestamp > auctionEndTime) {
revert AuctionAlreadyEnded();
}
bids[msg.sender] = bids[msg.sender].add(amount);
totalBids = totalBids.add(amount);
fjordPoints.transferFrom(msg.sender, address(this), amount);
emit BidAdded(msg.sender, amount);
}

The auctionEnd function can be called when the block timestamp is greater than or equal to auctionEndTime to conclude the auction and calculate the claimable tokens for each bidder based on their bid proportion.

function auctionEnd() external {
if (block.timestamp < auctionEndTime) {
revert AuctionNotYetEnded();
}
if (ended) {
revert AuctionEndAlreadyCalled();
}
ended = true; // @audit-info users can still bid after auction ends
emit AuctionEnded(totalBids, totalTokens);
if (totalBids == 0) {
auctionToken.transfer(owner, totalTokens);
return;
}
multiplier = totalTokens.mul(PRECISION_18).div(totalBids);
// Burn the FjordPoints held by the contract
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn);
}

The problem is because both the bid function and auctionEnd can be called when the block timestamp is exactly equal to auctionEndTime. If a user places a bid in the same block when auctionEndTime is reached and the auctionEnd function is executed first in that block, the bid will be placed after the auction has technically ended, and the claimable tokens for each bidder will have already been calculated.

Impact:

  • If a bid is placed after the auction has ended through the scenario described above, the FjordPoints sent to the contract as part of that bid will not be burned as they should be when auctionEnd is called, causing them to remain stuck in the contract indefinitely.

  • Additionally, the user who placed the bid will be able to claim tokens for their bid, but since their bid was not considered during the multiplier calculation, the last user(s) to claim will not be able to claim their tokens after the auction has ended.

Tools Used:

  • Manual analysis

Recommendation:

To prevent this issue, modify the auctionEnd function to revert if block.timestamp is less than or equal to auctionEndTime. This change ensures that users will not be able to place bids after the auction has ended.

function auctionEnd() external {
if (block.timestamp <= auctionEndTime) {
revert AuctionNotYetEnded();
}
...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Users can bid in the same block when the actionEnd could be called (`block.timestamp==actionEndTime`), depending on the order of txs in block they could lose funds

The protocol doesn't properly treat the `block.timestamp == auctionEndTime` case. Impact: High - There are at least two possible impacts here: 1. By chance, user bids could land in a block after the `auctionEnd()` is called, not including them in the multiplier calculation, leading to a situation where there are insufficient funds to pay everyone's claim; 2. By malice, where someone can use a script to call `auctionEnd()` + `bid(totalBids)` + `claimTokens()`, effectively depriving all good faith bidders from tokens. Likelihood: Low – The chances of getting a `block.timestamp == auctionEndTime` are pretty slim, but it’s definitely possible.

Support

FAQs

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