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

Bid/unbid can be placed even the auction is ended, causing other bidders to lose money due to reduced liqudity

Summary

If a bid is placed post-auction due to timing issues, the FjordPoints remain stuck, and the bidder can claim tokens despite their bid not factoring into calculations, preventing earlier bidders from reclaiming their tokens and causing potential fund losses.

Vulnerability Details

In bid function, users can place bids if the current block timestamp is not greater than the auctionEndTime:

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 multiplier which then determines each users' propotion of token claim.

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 issue arises because both the bid and auctionEnd functions can be executed when the block timestamp matches auctionEndTime. If a user submits a bid in the same block where auctionEndTime occurs and the auctionEnd function executes first, the bid will be processed after the auction officially concludes, with claimable tokens for each bidder already determined.
Same thing can happen with unbid.

Impact

  1. If a bid is placed after the auction ends due to the scenario described, the FjordPoints sent with that bid won't be burned as they should when auctionEnd is called, leaving them indefinitely stuck in the contract. Additionally, the bidder can claim tokens, but since their bid wasn't included in the multiplier calculation, the last user(s) to claim may be unable to retrieve their tokens post-auction.

  2. Bidders can still place bids even after the auction has ended (ended = true). Consequently, early bidders may be unable to claim the auctionToken tokens allocated to them and cannot reclaim their FjordPoints tokens since attempting to call FjordAuction::unbid will trigger an AuctionAlreadyEnded error, resulting in a loss of funds for the bidders.

Tools Used

Manual Review

Recommendations

Check ended state in bid function:

function bid(uint256 amount) external {
- if (block.timestamp > auctionEndTime) {
+ if (block.timestamp > auctionEndTime || ended ) {
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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 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.