DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: low
Invalid

Precision Loss For Auction Tokens With Low Decimals

[H-01] Precision Loss For Auction Tokens With Low Decimals

Summary

The FjordAuction::auctionEnd function calculates the claim multiplier rate at the end of the auction. When a bidder wants to claim their tokens, this multiplier is used to determine the claimable amount. However, if the auction token has fewer decimals (e.g., 6 like USDC or even 2 like Gemini USD), the claimable amount calculation can be flawed. In extreme cases, the multiplier itself can be set to 0 if the totalBids amount is too large.

Vulnerability Details

When calculating the multiplier, if the totalTokens amount is less than totalBids / PRECISION_18, the multiplier will be set to 0. For example, if the auction token has 2 decimals and 10,000 of it is being auctioned, but the totalBids is higher than 1,000,000 tokens, the multiplier would be 0. Additionally, for claimable amount calculations, if userBids * multiplier is less than PRECISION_18, the user will lose their funds. For example, if the auction multiplier is equal to 1_000_000 * 1e18 / 1_000_000e18 = 1, and a user deposited less than 1e18, their deposit will result in zero tokens.

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);
}
function claimTokens() external {
if (!ended) {
revert AuctionNotYetEnded();
}
uint256 userBids = bids[msg.sender];
if (userBids == 0) {
revert NoTokensToClaim();
}
uint256 claimable = userBids.mul(multiplier).div(PRECISION_18);
bids[msg.sender] = 0;
auctionToken.transfer(msg.sender, claimable);
emit TokensClaimed(msg.sender, claimable);
}

Impact

If the multiplier faces precision loss and is set to zero, all bidders will lose their points, and the funds will be stuck in the contract forever. The other scenario, where a user's claimable amount is zero due to precision issues, is less severe because it affects only smaller deposits and is less likely to occur.

Proof of Concept

The proof of concept for the first case is demonstrated using the weird-erc20 repository and the existing auction.t.sol test suite. Note that the token used here has 2 decimals and showcases the most extreme case, but the issue can also occur with tokens having more decimals.

import {LowDecimalToken} from "lib/weird-erc20/src/LowDecimals.sol";
.
.
.
function testPrecisionLoss() public {
// Set up the auction
uint256 totalAuctionToken = 10000e2; // 1,000,000
lowDecimalToken = new LowDecimalToken(totalAuctionToken);
auction = new FjordAuction(
address(fjordPoints),
address(lowDecimalToken),
biddingTime,
totalAuctionToken
);
lowDecimalToken.transfer(address(auction), totalAuctionToken);
// This bidder represents all other bidders in the pool
address bidder = address(uint160(1));
uint256 bidAmount = 1_000_000 ether + 1; // A wei above the threshold
deal(address(fjordPoints), bidder, bidAmount);
vm.startPrank(bidder);
fjordPoints.approve(address(auction), bidAmount);
auction.bid(bidAmount);
vm.stopPrank();
// Auction ends
vm.roll(block.number + 1);
vm.warp(block.timestamp + 2 weeks);
auction.auctionEnd();
// Bidders claim
vm.startPrank(bidder);
auction.claimTokens();
// User receives zero of the auctioned token, and the multiplier is zero!
assertEq(lowDecimalToken.balanceOf(bidder), 0);
assertEq(auction.multiplier(), 0);
}

Tools Used

Manual Review

Recommendations

  • Adjust the calculation logic to handle tokens with low decimals more gracefully, possibly by increasing precision or using a different approach to prevent the multiplier from becoming zero.

  • Consider implementing additional checks and safeguards to ensure users' claims are accurately processed even with low-decimal tokens.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Low decimal tokens or super small bids can lead to 0 claims

Appeal created

4rdiii Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Low decimal tokens or super small bids can lead to 0 claims

Support

FAQs

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