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

Bidders can bid after auction ends but cannot claim tokens

Summary

All users can end the auction when block.timestamp = auctionEndTime. However, at the same time, new bidders can still place bids. This can lead to issues where the multiplier cannot be calculated correctly, allowing new bidders to place bids even after the auction has ended, resulting in them being unable to claim tokens.

Vulnerability Details

The vulnerability stems from the timestamp validation checks within the code. The line if (block.timestamp > auctionEndTime) { revert AuctionAlreadyEnded(); } is meant to restrict bidding to periods where block.timestamp is less than or equal to auctionEndTime. However, this conflicts with the auctionEnd() function's timestamp check: if (block.timestamp < auctionEndTime) { revert AuctionNotYetEnded(); }. This conflict allows bidders to place bids when block.timestamp equals auctionEndTime but prevents them from calling the auctionEnd() function again if it has already been called.

Code Snippet

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);
}
function auctionEnd() external {
if (block.timestamp < auctionEndTime) {
revert AuctionNotYetEnded();
}
if (ended) {
revert AuctionEndAlreadyCalled();
}
ended = true;
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);
}

Impact

This vulnerability can result in bidders placing bids and locking their amount of fjord points in the auction contract but being unable to claim tokens after the auction ends.

  • The fjord points of bidders remain locked in the auction contract indefinitely.

  • Bidders are unable to claim tokens even after the auction has ended.

Proof Of Concept

  1. Bidder A places a bid at the auction contract.

  2. Bidder A ends the auction when block.timestamp equals auctionEndTime.

  3. Bidder B places a bid when block.timestamp equals auctionEndTime.

  4. Bidder B receives a revert error when trying to claim tokens after the auction has ended.

    function testAuctionWithWrongTokensClaim() public {
    address bob = address(0xb0b);
    address allen = address(0xa11e);
    uint256 bidAmount = 100 ether;
    deal(address(fjordPoints), bob, bidAmount);
    deal(address(fjordPoints), allen, bidAmount);
    vm.startPrank(bob);
    fjordPoints.approve(address(auction), bidAmount);
    auction.bid(bidAmount);
    vm.stopPrank();
    skip(biddingTime);
    auction.auctionEnd();
    vm.startPrank(allen);
    fjordPoints.approve(address(auction), bidAmount);
    auction.bid(bidAmount);
    vm.stopPrank();
    vm.prank(bob);
    auction.claimTokens();
    vm.expectRevert("ERC20: transfer amount exceeds balance");
    vm.prank(allen);
    auction.claimTokens();
    assertEq(fjordPoints.balanceOf(bob), 0);
    assertEq(fjordPoints.balanceOf(allen), 0);
    assertEq(fjordPoints.balanceOf(address(auction)), 100e18);
    assertEq(auctionToken.balanceOf(bob), 1000e18);
    assertEq(auctionToken.balanceOf(allen), 0);
    assertEq(auctionToken.balanceOf(address(auction)), 0);
    }

Tools Used

Manual code review

Recommendations

Consider restricting new bids when block.timestamp equals auctionEndTime.

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

Lead Judging Commences

inallhonesty Lead Judge about 1 year 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.