Game.withdrawWinnings()
Normally, withdrawWinnings()
sends a user their accumulated winnings and resets their balance to 0
in pendingWinnings
.
The issue is that the function updates pendingWinnings[msg.sender]
after making an external call (call{value:}
), which could allow a malicious contract to reenter and exploit the delayed state update.
Likelihood:
Medium: Reentrancy is possible if msg.sender
is a malicious contract with a receive()
or fallback()
function that calls back into Game
before pendingWinnings
in `Game.sol` is cleared.
Cross-function reentrancy: The same pendingWinnings
state variable is used in declareWinner()
, meaning an attacker could manipulate winnings across multiple functions.
Impact:
Funds theft: An attacker could drain the contract by recursively calling withdrawWinnings()
before pendingWinnings
is set to 0
.
State corruption: If pendingWinnings
is used elsewhere (e.g., declareWinner()
), inconsistent state could lead to incorrect payouts or logic errors.
Contract Attacker is a malicious contract, which reenters before `pendingWinnings` is cleared.
Follow Checks-Effects-Interactions: Update state before external calls.
Use OpenZeppelin’s ReentrancyGuard
: Even though nonReentrant
is present, ensure it’s applied consistently across all state-modifying functions.
Consider Pull-over-Push: Let users withdraw funds themselves (e.g., via a claimWinnings()
pattern) instead of the contract pushing ETH.
We only audit the current code in scope. We cannot make speculation with respect to how this codebase will evolve in the future. For now there is a nonReentrant modifier which mitigates any reentrancy. CEI is a good practice, but it's not mandatory. Informational
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.