The contract is compiled with pragma solidity ^0.7.6, which has NO built-in overflow checks, and stores accumulated fees in a uint64:
Two problems compound:
uint64(fee) truncates a uint256 value to 64 bits. type(uint64).max is ~1.8447e19 wei (~18.4 ETH). Once a single raffle's fee exceeds that, the cast silently drops the high bits.
Even below that, totalFees + ... is unchecked in 0.7.6, so accumulation across raffles wraps around.
The result is that totalFees records a value far smaller than the fees actually collected, so withdrawFees() (which sends exactly totalFees) under-pays the fee address - the difference is permanently stuck in the contract. Fee accounting is corrupted and protocol revenue is lost.
Likelihood: Medium - requires a high-value raffle (a single raffle of ~93 players at 1 ETH overflows uint64) or accumulation across many raffles before withdrawFees is called.
Impact: High - collected fees are silently lost / mis-accounted, and the stuck portion is unrecoverable.
A raffle of 93 players produces a fee (18.6 ETH) above uint64 max, so uint64(fee) truncates and totalFees ends up far below the real fee. Runnable Foundry test (add to PuppyRaffleTest.t.sol):
Run forge test --mt test_PoC_feeOverflowUint64 -vv; it passes - totalFees is far below the true fee.
Upgrade to Solidity 0.8.x (built-in overflow checks) and store fees in uint256, removing 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.