Puppy Raffle

AI First Flight #1
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Integer overflow of totalFees and strict balance equality in withdrawFees() permanently locks protocol fees

Root + Impact

Description

The withdrawFees function is intended to allow the protocol fee recipient to withdraw accumulated raffle fees once active players have finished.

However, totalFees is typed as uint64 and incremented in Solidity 0.7.6 without overflow checks, causing it to silently overflow when accumulated fees exceed ~18.44 ETH. Furthermore, withdrawFees enforces a strict balance equality check require(address(this).balance == uint256(totalFees)). If totalFees overflows, or if any external ether is forcefully sent to the contract (e.g. via selfdestruct), the balance will never equal totalFees, permanently bricking fee withdrawals.

@> uint64 public totalFees = 0;
...
@> totalFees = totalFees + uint64(fee);
...
function withdrawFees() external {
@> require(address(this).balance == uint256(totalFees), "PuppyRaffle: There are currently players active!");
uint256 feesToWithdraw = totalFees;
totalFees = 0;
(bool success,) = feeAddress.call{value: feesToWithdraw}("");
require(success, "PuppyRaffle: Failed to withdraw fees");
}

Risk

Likelihood:

Solidity 0.7.6 lacks built-in overflow protection, and anyone can send 1 wei of ETH via selfdestruct to break the strict balance check at any time.

Impact:

Medium. Protocol fees become permanently inaccessible and locked inside the contract forever, causing financial loss to the protocol fee address.

Proof of Concept

Accumulating fees beyond the 64-bit unsigned integer limit triggers silent overflow to zero, and any unexpected contract balance prevents fees from ever being withdrawn.

function test_totalFeesOverflowAndLock() public {
// uint64 maximum value is ~18.446 ETH (18446744073709551615 wei)
uint64 maxFees = type(uint64).max;
// In Solidity 0.7.6 without SafeMath, uint64 wraps around to 0
uint64 overflow = maxFees + 1;
assertEq(overflow, 0);
// Additionally, if contract balance != totalFees due to direct transfer,
// withdrawFees() permanently reverts.
}

Recommended Mitigation

Upgrade Solidity to ^0.8.0, change totalFees to uint256, and avoid strict balance equality in withdrawFees by verifying the contract holds at least totalFees.

- uint64 public totalFees = 0;
+ uint256 public totalFees = 0;
function withdrawFees() external {
- require(address(this).balance == uint256(totalFees), "PuppyRaffle: There are currently players active!");
+ require(address(this).balance >= totalFees && totalFees > 0, "PuppyRaffle: No fees to withdraw");
uint256 feesToWithdraw = totalFees;
totalFees = 0;
(bool success,) = feeAddress.call{value: feesToWithdraw}("");
require(success, "PuppyRaffle: Failed to withdraw fees");
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!