uint64 cast in PuppyRaffle::selectWinner corrupts fee accounting and locks feesLikelihood: Medium — Triggers automatically once the fee for a raffle exceeds type(uint64).max (~18.44 ETH of fees, i.e. ~92 entrants at a 1 ETH entrance fee), or cumulatively across rounds. No attacker action required.
Impact: High — The recorded totalFees no longer matches the ETH actually held, so fees are unaccounted for and withdrawFees reverts permanently — the fees are locked in the contract.
Severity: Medium (High impact × Medium likelihood).
totalFees is a uint64, and selectWinner adds the fee to it using an unchecked uint64 cast under Solidity 0.7.6 (which does not have built-in overflow protection):
If fee > type(uint64).max (≈ 1.8446e19 wei ≈ 18.44 ETH), the uint64(fee) cast truncates the value (keeps only fee % 2**64), and uint64 addition can silently wrap. totalFees then records far less than the fees the contract actually holds.
Fee accounting is corrupted: totalFees undercounts the real fees.
withdrawFees requires address(this).balance == uint256(totalFees); since the balance now exceeds the truncated totalFees, this check can never pass, so the legitimately-earned fees are permanently locked.
93 players enter (real fee = 18.6 ETH), the winner is selected, and:
recorded totalFees ≈ 0.153 ETH (truncated), vs 18.6 ETH actually held — assertLt(recordedFees, expectedFee) passes.
assertEq(address(puppyRaffle).balance, 18.6 ETH) passes.
withdrawFees() reverts with "There are currently players active!" — fees locked.
Use Solidity ^0.8.x (built-in overflow checks) and store totalFees as uint256 with no narrowing cast:
Also prefer tracking fees with an internal accounting variable rather than comparing against address(this).balance.
## 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.