Puppy Raffle

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

totalFees uint64 overflow and unsafe uint256->uint64 cast silently corrupt fee accounting in Solidity 0.7.6

Root + Impact

  • totalFees is a uint64 and is updated with an unchecked uint256->uint64 cast under Solidity 0.7.6 (no built-in overflow protection). Large or accumulated fees are silently truncated, so the recorded fee total no longer matches the ETH actually held for fees.

Description

  • Each raffle adds its 20% fee to totalFees so that feeAddress can later withdraw the exact amount of accumulated protocol fees.

  • fee is a uint256 but is cast to uint64 before being added, and the addition itself is unchecked in 0.7.6. Once fee (in wei) exceeds type(uint64).max (about 18.44 ETH), or the running total exceeds it, the high bits are lost and totalFees becomes smaller than the fees truly owed.

uint256 fee = (totalAmountCollected * 20) / 100;
@> totalFees = totalFees + uint64(fee); // uint256 fee truncated to uint64; sum unchecked in 0.7.6

Risk

Likelihood:

  • This occurs once a single raffle's fee exceeds type(uint64).max (~18.44 ETH), reachable with ~93+ entrants at a 1 ETH entrance fee.

  • It also occurs cumulatively, as totalFees adds up across many raffles until the uint64 ceiling is crossed.

Impact:

  • feeAddress can withdraw only the truncated totalFees, permanently under-collecting the real protocol fees.

  • The truncated totalFees no longer equals the contract balance, which also breaks the strict balance check in withdrawFees().

Proof of Concept

  • The following test shows that casting a fee larger than type(uint64).max to uint64 discards the high bits, so the stored value is strictly smaller than the true fee.

function test_FeeCastTruncates() public {
// type(uint64).max = 18_446_744_073_709_551_615 wei ~= 18.446 ether
uint256 fee = 20 ether; // exceeds type(uint64).max
uint64 stored = uint64(fee);
// The stored fee is smaller than the real fee: high bits were silently lost
assertLt(uint256(stored), fee);
}

Recommended Mitigation

  • Store totalFees as a uint256 and remove the uint64 cast, so no truncation is possible. If the contract must stay on 0.7.6, additionally use SafeMath for the addition; upgrading to ^0.8.0 gives checked arithmetic by default.

- uint64 public totalFees = 0;
+ uint256 public totalFees = 0;
...
- totalFees = totalFees + uint64(fee);
+ totalFees = totalFees + fee;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 5 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!