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

ERC20 Tokens with Small Decimals Cause Tokens to be Stuck in `FjordAuction` Contract

Summary

The use of ERC20 tokens with decimals different from 18, such as GUSD with 2 decimals, as the auctionToken in the FjordAuction contract can result in a loss of funds. When the totalBids exceed totalTokens, the multiplier becomes less than 1e18, causing the users to receive zero tokens upon claiming. This issue can also occur with 18-decimal tokens when totalBids is greater than totalTokens.

Vulnerability Details

The protocol specifies that ERC20 tokens with decimals different from 18 are in-scope for potential issues. The auction mechanism allows users to bid using FjordPoints token, which has 18 decimal. However, if the auction uses tokens with fewer decimals (like GUSD with 2 decimals) as the auctionToken, complications arise.

When such tokens are used, and the auction ends via the auctionEnd function, totalBids (the sum of all bids by participants) can exceed totalTokens either intentionally or unintentionally. When this occurs, the multiplier variable becomes less than 1e18 due to the following calculation:

multiplier = totalTokens.mul(PRECISION_18).div(totalBids);

Because the multiplier is less than 1e18, users attempting to claim their tokens through the claimTokens function will receive zero tokens:

uint256 claimable = userBids.mul(multiplier).div(PRECISION_18);
bids[msg.sender] = 0;
auctionToken.transfer(msg.sender, claimable);

If userBids multiplied by the multiplier results in a value less than 1e18, the user's claimable tokens amount to zero. Consequently, all tokens remain stuck in the FjordAuction contract, causing a loss of funds.

This issue can also occur with tokens having 18 decimals if totalBids exceeds totalTokens and the multiplier is less than 1e18. A malicious actor can manipulate this by contributing more to the auction to inflate totalBids beyond totalTokens, making it impossible for other users to claim their tokens.

Attack Scenario:

  1. An auction is initiated with an ERC20 token having decimals other than 18 (e.g., GUSD with 2 decimals).

  2. Users place bids using FjordPoints token, which has 18 decimal.

  3. The totalBids surpass the totalTokens due to overbidding, or intentional manipulation.

  4. The auction ends using the auctionEnd function, and the multiplier is calculated to be less than 1e18.

  5. Users attempt to claim their tokens using the claimTokens function but receive zero tokens due to the userBids multiplied by the multiplier results in a value less than 1e18. This amount when divided by PRECISION_18 results in zero amount of token to be claimed by the user.

Impact

The users who participated in the auction suffer a loss of their bid amounts as they cannot retrieve their claimable tokens from the contract. The tokens remain locked in the FjordAuction contract, leading to a permanent loss of funds.

Auctions launched with smaller decimal tokens such as tokens with 10 or less decimals will leads to loss of funds for all the users participating in that auction.
Auctions launched with 18 decimal tokens can still result in loss of funds for some users who have smaller bids.

Tools Used

Manual Review

Recommendations

  1. Prevent Excess Bidding: Add a condition in the FjordAuction::bid function that reverts if totalBids is greater than totalTokens.
    Add this code in FjordAuction::bid function:

function bid(uint256 amount) external {
if (block.timestamp > auctionEndTime) {revert AuctionAlreadyEnded();}
bids[msg.sender] = bids[msg.sender].add(amount);
totalBids = totalBids.add(amount);
++ if (totalBids > totalTokens) revert;
  1. Adjust Decimal Precision: Instead of using a fixed PRECISION_18, replace it with a precision value that matches the auctionToken's decimals. This adjustment will allow the contract to handle tokens with any number of decimals correctly, avoiding calculation errors that lead to a loss of funds.
    Import IERC20Metadata interface in the FjordAuction contract, create a new uint variable named decimalPrecision and add this code in FjordAuction's constructor:

++ uint256 public decimalPrecision;
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;
++ decimalPrecision = 1 * 10 ** (IERC20Metadata(_auctionToken).decimals());
}

Add this code in FjordAuction::auctionEnd function:

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);
++ multiplier = totalTokens.mul(decimalPrecision).div(totalBids);
// Burn the FjordPoints held by the contract
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn);
}

Add this code in FjordAuction::claimTokens function:

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);
++ uint256 claimable = userBids.mul(multiplier).div(decimalPrecision);
bids[msg.sender] = 0;
auctionToken.transfer(msg.sender, claimable);
emit TokensClaimed(msg.sender, claimable);
}
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

0x0bserver Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge 12 months 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.