The totalFees variable is declared as uint64, which has a maximum value of approximately 18.44 ETH. Fees accumulate across multiple raffles without bounds checking, causing an overflow after approximately 23 raffles. This makes fees permanently unwithdrawable.
Describe the normal behavior in one or more sentences:
Fees should accumulate correctly across multiple raffles
The fee address should be able to withdraw accumulated fees when no raffle is active
Explain the specific issue or problem in one or more sentences:
The totalFees variable is uint64 which can only hold up to 18,446,744,073,709,551,615 wei (approximately 18.44 ETH)
Each raffle with 4 players at 1 ETH entrance fee generates 0.8 ETH in fees
After 23 raffles, totalFees overflows and wraps around to a small value
The withdrawFees() function checks address(this).balance == totalFees, which fails after overflow, permanently locking the fees
Likelihood:
Reason 1: Guaranteed to occur after approximately 23 raffles with standard parameters (4 players at 1 ETH)
Reason 2: Occurs faster with higher entrance fees or more players per raffle
Impact:
Impact 1: All accumulated fees become permanently locked in the contract
Impact 2: Fee recipient loses all earned revenue with no recovery mechanism
Place the following test in test/AuditTest.t.sol:
Run with: forge test --mt testIntegerOverflow -vv
The test passes, confirming that withdrawFees() reverts after overflow.
Use uint256 instead of uint64 for totalFees:
## Description ## Vulnerability Details The type conversion from uint256 to uint64 in the expression 'totalFees = totalFees + uint64(fee)' may potentially cause overflow problems if the 'fee' exceeds the maximum value that a uint64 can accommodate (2^64 - 1). ```javascript totalFees = totalFees + uint64(fee); ``` ## POC <details> <summary>Code</summary> ```javascript function testOverflow() public { uint256 initialBalance = address(puppyRaffle).balance; // This value is greater than the maximum value a uint64 can hold uint256 fee = 2**64; // Send ether to the contract (bool success, ) = address(puppyRaffle).call{value: fee}(""); assertTrue(success); uint256 finalBalance = address(puppyRaffle).balance; // Check if the contract's balance increased by the expected amount assertEq(finalBalance, initialBalance + fee); } ``` </details> In this test, assertTrue(success) checks if the ether was successfully sent to the contract, and assertEq(finalBalance, initialBalance + fee) checks if the contract's balance increased by the expected amount. If the balance didn't increase as expected, it could indicate an overflow. ## Impact This could consequently lead to inaccuracies in the computation of 'totalFees'. ## Recommendations To resolve this issue, you should change the data type of `totalFees` from `uint64` to `uint256`. This will prevent any potential overflow issues, as `uint256` can accommodate much larger numbers than `uint64`. Here's how you can do it: Change the declaration of `totalFees` from: ```javascript uint64 public totalFees = 0; ``` to: ```jasvascript uint256 public totalFees = 0; ``` And update the line where `totalFees` is updated from: ```diff - totalFees = totalFees + uint64(fee); + totalFees = totalFees + fee; ``` This way, you ensure that the data types are consistent and can handle the range of values that your contract may encounter.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.