The RockPaperScissors contract contains a receive()
function that allows it to accept ETH sent directly to it, but provides no mechanism to track or withdraw this ETH. This creates an accounting discrepancy and permanently locks any ETH sent outside of the game functions.
The contract includes a receive()
function that allows it to accept ETH sent directly:
However, unlike ETH sent through the game functions (which is tracked in game.bet
and accumulatedFees
), ETH sent directly to the contract:
Is not tracked in any state variable
Is not associated with any game
Is not included in the accumulatedFees
Has no withdrawal mechanism
This means that any ETH sent directly to the contract becomes permanently locked with no way to retrieve it.
The contract's accounting system only tracks:
Game bets: game.bet
for each game
Protocol fees: accumulatedFees
for admin withdrawal
This creates a situation where:
This vulnerability has several severe impacts:
Permanent Fund Loss: Any ETH sent directly to the contract (accidentally or intentionally) is permanently locked with no way to recover it.
Accounting Discrepancy: The contract's actual ETH balance will not match the sum of tracked game bets and fees, making it difficult to audit and verify the contract's financial state.
Contract Insolvency Risk: If the accounting discrepancy becomes significant, it could lead to confusion about the contract's financial state and potentially disrupt operations.
Griefing Attack: A malicious actor could intentionally send small amounts of ETH directly to the contract to create accounting discrepancies and confusion.
Protocol Fee Bypass: ETH sent directly doesn't contribute to protocol fees, potentially reducing protocol revenue.
This is classified as a high severity issue because it can lead to permanent loss of user funds with no recovery mechanism.
Manual code review
Static analysis of the contract's ETH handling mechanisms
Analysis of contract accounting system
Implement one of the following solutions:
Remove the receive()
function to prevent direct ETH transfers:
Revert direct ETH transfers by implementing a check in the receive()
function:
Track direct ETH transfers by adding them to the accumulated fees:
Implement a rescue function that allows the admin to withdraw untracked ETH:
Option 1 or 2 is recommended as they prevent the issue entirely, while options 3 and 4 provide mechanisms to handle direct ETH transfers if they need to be allowed for some reason.
ETH sent directly to the contract via the receive function or after a canceled game becomes permanently locked
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.