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

Incomplete Token Distribution Leading to Total Loss for Some Auction Bidders

Summary

There is a vulnerability in the AuctionFactory and FjordAuction contracts, arising due to improper handling of fee-on-transfer ERC-20 tokens. While it is a known issue that additional fees can lead to higher-than-expected costs for users, the impact here is significantly more severe. When fee-on-transfer tokens are used in the auction process, fewer tokens than intended are transferred to the auction contract. This discrepancy creates a scenario where the initial claimants might receive their tokens as expected, but subsequent bidders – potentially a substantial portion – receive no tokens at all. This results in a total loss for these users, far exceeding mere higher-than-expected costs

Vulnerability Details

The vulnerability stems from the improper handling of fee-on-transfer tokens within the AuctionFactory's createAuction function and the FjordAuction contract. Specifically, the assumption that the entirety of totalTokens will be transferred to the auction contract is flawed when fee-on-transfer tokens are used.

In the createAuction function:

https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordAuctionFactory.sol#L63C9-L63C84

IERC20(auctionToken).transferFrom(msg.sender, auctionAddress, totalTokens);
  • This line transfers totalTokens from the sender to the auction contract. However, if the auctionToken is a fee-on-transfer token, fewer tokens than totalTokens will be received by the auction contract due to the fee.

  • In the FjordAuction contract within the auctionEnd function:

https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordAuction.sol#L197C9-L197C67

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

The calculation here uses totalTokens instead of the actual balance of the auction contract, leading to an overestimation of the tokens available for distribution.

  • Token Transfer Discrepancy: When the auctionToken is transferred from the sender to the auction contract, the token’s transfer mechanism deducts a fee (e.g., 3% ). This means that the auction contract receives less than totalTokens.

  • Multiplier Miscalculation: The miscalculation of the multiplier leads to the incorrect assumption that the auction contract holds totalTokens for distribution, rather than the actual received amount. As a result, claimable tokens per user are overestimated.

  • Token Claim Failure: During the token claim process:

https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordAuction.sol#L217C9-L220C54

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

Because of the overestimated multiplier, early claimants can claim the expected amount of tokens, but as the token balance of the auction contract depletes, later claimants receive nothing. For instance, with a 3% fee and 100 bidders, the last 3 bidders will potentially get nothing, causing a total loss for these users.

Impact

Bidders who claim their tokens later in the process may receive nothing due to an overestimation of available tokens, resulting in complete financial loss for these users.

Tools Used

Foundry

Recommendations

Modify the FjordAuction's auctionEnd function to use the actual balance of the auction contract instead of totalTokens for calculating the multiplier. This will account for any transfer fees and ensure accurate token distribution.

function auctionEnd() external {
if (block.timestamp < auctionEndTime) {
revert AuctionNotYetEnded();
}
if (ended) {
revert AuctionEndAlreadyCalled();
}
ended = true;
emit AuctionEnded(totalBids, totalTokens);
++ uint256 actualTotalTokens = auctionToken.balanceOf(address(this));
if (totalBids == 0) {
auctionToken.transfer(owner, totalTokens);
return;
}
-- multiplier = totalTokens.mul(PRECISION_18).div(totalBids);
++ multiplier = actualTotalTokens.mul(PRECISION_18).div(totalBids);
// Burn the FjordPoints held by the contract
uint256 pointsToBurn = fjordPoints.balanceOf(address(this));
fjordPoints.burn(pointsToBurn);
}
Updates

Lead Judging Commences

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

Appeal created

galturok Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
galturok Submitter
about 1 year ago
galturok Submitter
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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