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

Unchecked Multiplier Overflow in FjordAuction Contract

Summary

The FjordAuction contract is vulnerable to an integer overflow in the calculation of the multiplier during the auctionEnd process. This overflow occurs due to unchecked arithmetic operations when calculating the multiplier with extremely large values of totalTokens and very small totalBids. This flaw can lead to incorrect token distribution and potential financial discrepancies.

uint256 claimable = userBids.mul(multiplier).div(PRECISION_18);

Vulnerability Details

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

1.Contract Setup:

  • Deploy the FjordAuction contract with the following parameters:

    • totalTokens set to 2^256 – 1 (maximum value for uint256).

    • totalBids set to 1.

2.Multiplier Calculation:

  • The multiplier is calculated as:

multiplier = (totalTokens * PRECISION_18) / totalBids;
  • With totalTokens = 2^256 – 1 and totalBids = 1, this results in:

multiplier = ((2^256 - 1) * 10^18) / 1;
  • The multiplication (2^256 - 1) * 10^18 exceeds the maximum value for uint256, causing an overflow.

3.Triggering the Vulnerability:

  • Call the auctionEnd function after the auction duration has passed. This will attempt to calculate the multiplier using the aforementioned values.

4.Expected Behavior:

  • The compiler will catch this overflow and roll back the transaction with a panic error (0x11), indicating an arithmetic overflow or understatement.

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity =0.8.21;
import "forge-std/Test.sol";
import "../src/FjordAuction.sol";
import "./CustomERC20.sol";
contract FjordAuctionTest is Test {
FjordAuction auction;
CustomERC20 fjordPoints;
CustomERC20 auctionToken;
address owner = address(0x123);
address user = address(0x456);
function setUp() public {
fjordPoints = new CustomERC20("FjordPoints", "FJP");
auctionToken = new CustomERC20("AuctionToken", "AUC");
auction = new FjordAuction(address(fjordPoints), address(auctionToken), 1 days, type(uint256).max);
// Set initial state
vm.startPrank(owner);
fjordPoints.mint(address(auction), 1);
auctionToken.mint(address(auction), type(uint256).max);
vm.stopPrank();
}
function testMultiplierCalculation() public {
// Set totalBids to 1
vm.startPrank(owner);
fjordPoints.mint(owner, 1); // Ensure owner has enough tokens
fjordPoints.approve(address(auction), 1);
auction.bid(1);
vm.stopPrank();
// End the auction to calculate the multiplier
vm.warp(block.timestamp + 1 days);
try auction.auctionEnd() {
assertTrue(false, "Expected overflow error not thrown");
} catch (bytes memory /* lowLevelData */) {
// This is expected to catch the panic error (0x11)
assertTrue(true);
}
}
}
forge test --match-path test/FjordAuctionTest.t.sol -vvvv
[⠊] Compiling...
[⠒] Compiling 1 files with Solc 0.8.21
[⠢] Solc 0.8.21 finished in 2.00s
Compiler run successful!
Ran 1 test for test/FjordAuctionTest.t.sol:FjordAuctionTest
[PASS] testMultiplierCalculation() (gas: 123940)
Traces:
[160190] FjordAuctionTest::testMultiplierCalculation()
├─ [0] VM::startPrank(0x0000000000000000000000000000000000000123)
│ └─ ← [Return]
├─ [29792] CustomERC20::mint(0x0000000000000000000000000000000000000123, 1)
│ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x0000000000000000000000000000000000000123, value: 1)
│ └─ ← [Stop]
├─ [24652] CustomERC20::approve(FjordAuction: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], 1)
│ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000123, spender: FjordAuction: [0xF62849F9A0B5Bf2913b396098F7c701
9b51A820a], value: 1) │ └─ ← [Return] true
├─ [61512] FjordAuction::bid(1)
│ ├─ [10529] CustomERC20::transferFrom(0x0000000000000000000000000000000000000123, FjordAuction: [0xF62849F9A0B5Bf2913b396098F7
c7019b51A820a], 1) │ │ ├─ emit Approval(owner: 0x0000000000000000000000000000000000000123, spender: FjordAuction: [0xF62849F9A0B5Bf2913b396098F7
c7019b51A820a], value: 0) │ │ ├─ emit Transfer(from: 0x0000000000000000000000000000000000000123, to: FjordAuction: [0xF62849F9A0B5Bf2913b396098F7c7019b
51A820a], value: 1) │ │ └─ ← [Return] true
│ ├─ emit BidAdded(bidder: 0x0000000000000000000000000000000000000123, amount: 1)
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::warp(86401 [8.64e4])
│ └─ ← [Return]
├─ [26560] FjordAuction::auctionEnd()
│ ├─ emit AuctionEnded(totalBids: 1, totalTokens: 11579208923731619542357098500868790785326998466564056403945758400791312963993
5 [1.157e77]) │ └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
├─ [0] VM::assertTrue(true) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 699.30µs (122.60µs CPU time)
Ran 1 test suite in 5.52ms (699.30µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

  • Due to the overflow, the multiplier calculation fails, leading to incorrect or failed distribution of auction tokens to bidders.

  • Users may receive incorrect amounts of tokens, resulting in financial discrepancies and potential loss of trust in the auction mechanism.

  • The overflow causes the contract to revert unexpectedly, disrupting the normal operation and usability of the contract.

Tools Used

  • Manual review

  • Foundry

Recommendations

  • Ensure that totalBids is not too small relative to totalTokens to prevent unrealistic multiplier values. Implement checks to validate that totalBids is above a certain threshold before performing the calculation.

  • Set a reasonable cap on totalTokens to prevent extreme values that could lead to overflow. This can be done by introducing a maximum limit for totalTokens during the contract setup or auction initialization.

  • Before performing the multiplication, check if the result will exceed uint256 limits. This can be done by rearranging the calculation to avoid large intermediate results or by implementing logic to detect potential overflow conditions.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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