totalFees is declared as uint64 (line 30), and selectWinner() casts the uint256 fee down to uint64 before accumulating it (line 134):
In Solidity 0.7.6 there is no built-in overflow check on the cast — a value exceeding uint64 capacity is silently truncated (wrapping), so the recorded fee no longer matches the fee actually owed. Corrupted totalFees then breaks withdrawFees(), whose address(this).balance == totalFees check (line 158) can never hold against a truncated book value.
Stated honestly on reachability: triggering the truncation requires fee > type(uint64).max (~18.4 ETH), i.e. an entranceFee above ~90 ETH per player — far outside any realistic deployment of this raffle. This is therefore a code-hygiene / defense-in-depth finding, not a practical exploit path: the cast is a latent accounting hazard that becomes real the moment deployment parameters grow.
Likelihood: Low — exploitation needs an unrealistically large entranceFee (above ~90 ETH per player).
Impact: Low — in the unrealistic regime, fee accounting is silently corrupted and withdrawFees() can brick; in realistic deployments there is no impact.
Not demonstrated with a runnable PoC, deliberately: reaching the truncation requires an entranceFee above ~90 ETH per player, which is outside any realistic deployment. The arithmetic is straightforward to verify by inspection — uint64 wraps at 2^64 - 1 wei (~18.4 ETH), and Solidity 0.7.6 performs the uint256 → uint64 conversion with silent truncation:
Declare totalFees as uint256 so no narrowing cast exists at all. With the full 256-bit domain, the accumulation is exact for any deployment parameter — the truncation hazard disappears by construction rather than by hoping parameters stay small.
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.