Normal Behavior:
The selectWinner function accumulates 20% of the prize pool into totalFees. The withdrawFees function then requires address(this).balance == uint256(totalFees) before allowing the owner to withdraw the accumulated fees.
Specific Issue:
totalFees is declared as uint64, but the contract uses Solidity ^0.7.6, which does not include built-in overflow checks. When the accumulated fees exceed the maximum value of uint64 (18,446,744,073,709,551,615 wei, or ~18.45 ether), totalFees silently overflows and wraps around to a smaller value. This breaks the equality check in withdrawFees, permanently blocking fee withdrawa
Likelihood:
The overflow occurs naturally as the raffle continues over many rounds. With 4 players per round and 0.8 ether added per round, it takes about 24 rounds to reach the uint64 limit. No attacker action or special permissions are required — it is a time-based risk that grows with normal usage.
Impact:
When totalFees overflows, it wraps around to a small value. The withdrawFees function requires address(this).balance == uint256(totalFees), so the equality check fails permanently. The owner can never withdraw the accumulated fees, and the funds are locked in the contract.
The test demonstrates the overflow with concrete numbers:
| Step | Value (wei) |
|---|---|
uint64 maximum |
18446744073709551615 |
totalFees before overflow |
18400000000000000000 |
| Fee added by next round | 800000000000000000 |
Sum before uint64 wrap |
19200000000000000000 |
Expected totalFees after wrap |
753255926290448384 |
| Contract balance before overflow | 18400000000000000000 |
Actual totalFees after overflow |
753255926290448384 |
| Contract balance after overflow | 19200000000000000000 |
The test accumulates fees over multiple rounds until totalFees reaches 18.4 ether. The next round adds 0.8 ether, pushing the sum to 19.2 ether, which exceeds the uint64 maximum of 18.4467 ether. As a result, totalFees silently wraps around to 0.753 ether.
At this point, the contract balance is 19.2 ether, but totalFees is only 0.753 ether. The withdrawFees function requires address(this).balance == uint256(totalFees), so the equality check fails permanently. The test confirms this with vm.expectRevert, proving that the owner can never withdraw the accumulated fees.
Change totalFees from uint64 to uint256 and remove the unsafe cast.
## 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.