PuppyRaffle::totalFees might cause overflow and uses an unsafe cast, blocking the PuppyRaffle::withdrawFees functionDescription: PuppyRaffle::totalFees is declared as a uint64 variable instead of a uint256. This causes two bugs:
Unsafe cast: PuppyRaffle::fee is declared as a uint256 but when calculating PuppyRaffle::totalFees its casted to uint64 without any safety. If: fee > type(uint64).max (~18.44 ETH) the variable wraps modulo $2^{64}$ — the high bits are silently discarded, so totalFees ends up far below the real amount. For that to happen would be needed 93 players (if PuppyRaffle::entranceFee = 1 ETH): 93 players * 1 ETH * 20 / 100 = 18.6 ETH > type(uint64).max.
Overflow: raffles whose individual fee fits in uint64 still may cause an overflow in PuppyRaffle::totalFees as its a uint64 declared variable. The accumulated sum in totalFees wraps modulo $2^{64}$ as well.
Impact: As a consequence of this bug, uint64 variable and the uint64 forced cast might silently wrap modulo $2^{64}$ without any warning causing the loss of the real value of PuppyRaffle::totalFees. Both bugs leads to a block of PuppyRaffle::withdrawFees function, which uses PuppyRaffle::totalFees to send the fees and its require(address(this).balance == uint256(totalFees)), as this last require could not be passed the function stay locked forever.
Proof of Concept: In the following test we can see how the unsafe cast corrupts the real accounting of PuppyRaffle::fee reflected in PuppyRaffle::totalFees. Since the raffle was initialized from the start, total fees was 0 so it couldn't be the problem. PuppyRaffle::totalFees and its overflow bug is not tested in the PoC but also produces the same issue and its fixes are the same as the unsafe cast.
In the following test we clearly see how the real output — uint64 variable — overflows without any warning.
In this case we get as outputs:
The uint64 value wrapped: 19e18 was stored as 19e18 - $2^{64}$ 0.55e18.
Place the following test into PuppyRaffle.t.sol.
Recommended Mitigation: There are a few recommendations:
Change the PuppyRaffle::totalFees type variable from uint64 to uint256 and remove the forced cast. This solves the issue as the max of a uint256 variable is huge — 1.158e77 —, compared against the uint64 max — 1.845e19.
Consider using the library SafeMath and SafeCast from OpenZeppelin. For compiler 0.7.6 use v3.4.0 from the contract library.
Consider that ".toUint64()" will fire a revert which removes the unwarned truncation but does not fix the uint64 space issue. Knowing that, mitigation 2 is better only if the uint64 is strictly wanted, if not, use mitigation 1.
## 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.