L-1: Auction tokens can be stuck in the FjordAuction contract
Summary
Users can bid an unrestricted amount of Fjord Points to participate in an auction. If a situation arises where high bidders submit a large number of Fjord Points, the auction contract may fail to distribute auction tokens to users with lower bid amounts.
Vulnerability Details
The multiplier(ratio between fjord points and auction token) is calculated based on the total auction tokens and the total bids received:
if (totalBids == 0) {
auctionToken.transfer(owner, totalTokens);
return;
}
multiplier = totalTokens.mul(PRECISION_18).div(totalBids);
If totalBids
is large (due to high bids by some users), the multiplier becomes small, affecting the claimable amount for users with lower bids.
Each user's claimable amount is calculated using their bids and the multiplier:
uint256 claimable = userBids.mul(multiplier).div(PRECISION_18);
bids[msg.sender] = 0;
auctionToken.transfer(msg.sender, claimable);
Here, if a user has a low amount of userBids
and the multiplier
is very small, the calculated claimable
tokens may end up being zero, making it impossible for low-bid users to receive their share of auction tokens.
Following test can be added to auction.t.sol
and run forge test -vv --mt testLowAmountBid
function testLowAmountBid() public {
address bidderBig = address(0x2);
uint256 bidAmountBig = 1_000_000 ether;
address bidderLow = address(0x3);
uint256 bidAmountLow = 1000;
deal(address(fjordPoints), bidderBig, bidAmountBig);
deal(address(fjordPoints), bidderLow, bidAmountLow);
vm.startPrank(bidderBig);
fjordPoints.approve(address(auction), bidAmountBig);
auction.bid(bidAmountBig);
vm.stopPrank();
vm.startPrank(bidderLow);
fjordPoints.approve(address(auction), bidAmountLow);
auction.bid(bidAmountLow);
vm.stopPrank();
skip(biddingTime);
auction.auctionEnd();
vm.prank(bidderBig);
auction.claimTokens();
vm.prank(bidderLow);
auction.claimTokens();
console.log(auction.auctionToken().balanceOf(bidderBig));
console.log(auction.auctionToken().balanceOf(bidderLow));
}
Logs:
999999999999999000000
0
Impact
Auction token being stuck in the contract.
Tools Used
Manual review, Foundry.
Recommendations
To address this issue, consider adding a withdrawal function that allows the protocol to recover unclaimable Fjord Points. Alternatively, increase the precision in calculations to reduce the chances of this issue occurring.
L-2: Possible overflow in FjordAuction
contract
Summary
totalPoints
is set in the contract constructor and does not have any restrictions. Huge values can cause overflow issues in auctionEnd()
.
Vulnerability Details
constructor(
address _fjordPoints,
address _auctionToken,
uint256 _biddingTime,
uint256 _totalTokens
) {
if (_fjordPoints == address(0)) {
revert InvalidFjordPointsAddress();
}
if (_auctionToken == address(0)) {
revert InvalidAuctionTokenAddress();
}
fjordPoints = ERC20Burnable(_fjordPoints);
auctionToken = IERC20(_auctionToken);
owner = msg.sender;
auctionEndTime = block.timestamp.add(_biddingTime);
totalTokens = _totalTokens;
}
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);
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn);
}
Huge totalTokens value would cause the totalTokens.mul(PRECISION_18).div(totalBids)
to overflow leading to being unable to end the auction.
Impact
Locked fjord points
Tools Used
Manual review
Recommendations
Consider adding a restriction on the totalTokens
values.