The RockPaperScissors::_finishGame
and _handleTie
functions calculate the protocol fee using Solidity's integer division. If the totalPot
is not perfectly divisible by 100, the remainder will be silently discarded. This leads to unclaimable "dust" ETH that stays permanently locked in the contract and causes accounting discrepancies. Over time, this could accumulate to a noticeable amount, especially as the number of games increases.
Integer Truncation
Solidity truncates results when performing division with integers.
For example, 3 * 10 / 100 = 0
when ideally you want 0.3 → 1 wei or round up.
Locked ETH (Dust)
Any leftover remainder from the division will not be included in either fee
or prize
.
This remainder becomes permanently trapped in the contract.
Accounting Discrepancy
accumulatedFees
only tracks the truncated fee, but address(this).balance
will include the leftover.
This mismatch can create confusion during audits or when calculating withdrawable fees.
Lost Funds: Small ETH amounts will be unrecoverable, especially impactful over many games.
Balance Mismatch: The tracked accumulatedFees
will not match the actual contract balance.
User Trust Impact: Players may be concerned about unexplained leftover balances.
This test creates a game where the total pot is not divisible by 100, ensuring a truncated remainder. The PoC confirms that fee < ceil(fee)
, proving dust is created.
This confirms a 1 wei discrepancy, which will remain locked in the contract and unaccounted for in accumulatedFees.
Manual Review
Foundry Unit Test
Use Safe Rounding Up
Prevent truncation using:
Use Subtractive Fee Calculation
Add Dust Recovery (Optional)
Let admin recover any excess ETH not tracked in accumulatedFees
.
The tie-handling logic loses one wei due to integer division
The tie-handling logic loses one wei due to integer division
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.