Protocol fees are accumulated in a uint64 state variable. In Solidity 0.7.6, the explicit conversion uint64(fee) silently truncates (keeps the low 64 bits) and uint64 addition wraps without any overflow check (checked arithmetic only arrived in 0.8.0). Once cumulative fees pass 2^64 wei (≈ 18.45 ether), the recorded totalFees no longer equals the ether actually held, and withdrawFees() — which requires strict equality between the contract balance and totalFees — reverts forever. All collected protocol fees are locked with no other exit function.
In src/PuppyRaffle.sol:
Trigger paths:
Single round: one round's fee exceeds ≈ 18.45 ether, i.e. round turnover above ≈ 92.2 ether (e.g. 93 players at a 1 ether entrance fee; enterRaffle places no cap on the number of players).
Cumulative: totalFees is never reset except by a successful withdrawFees(), so many small rounds accumulate across the 2^64 boundary just as well if the fee address does not withdraw regularly.
Relatedly, the strict-equality check has zero tolerance for balance drift: ether force-sent via selfdestruct also makes balance > totalFees and bricks withdrawals, with no recovery function.
The loser is the protocol / fee address: once triggered, all accumulated fees — the truncated difference plus every future fee — become permanently unwithdrawable, and each further round only widens the gap (no self-healing path). No attacker is required; normal operation (a large round, or long-term fee accumulation) reaches the boundary on its own.
Severity is assessed as Medium rather than High because the single-round path needs ≈ 92 ether of turnover, the cumulative path depends on operating cadence, and the bug locks fee withdrawal rather than destroying user principal.
Verified locally with forge test (entranceFee 25 ether × 4 players = 100 ether turnover, single-round fee = 20 ether):
Store fees in uint256 and drop the narrowing cast (totalFees = totalFees + fee;), and make withdrawFees() tolerant of drift — e.g. check balance >= totalFees and transfer exactly totalFees — so a balance mismatch can never permanently lock the fees.
## 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.