Summary
Games can become permanently stuck if one player commits their move but the second player never does. This occurs because the revealDeadline
is only set after both players commit, leaving no timeout mechanism for the commit phase itself once the game state has transitioned to Committed
.
Vulnerability Details
The commitMove
function handles the transition from Created
to Committed
state and the setting of the revealDeadline
:
When the first player commits (and Player B has joined), the state is potentially changed:
However, the revealDeadline
, which is crucial for the timeoutReveal
function, is only set later in the same function, after checking if both players have committed:
If Player A commits (changing the state to Committed
), but Player B never calls commitMove
, the condition game.commitA != bytes32(0) && game.commitB != bytes32(0)
will never be true. Consequently, game.revealDeadline
remains 0.
timeoutReveal
cannot be called because it requires block.timestamp > game.revealDeadline
, which fails if revealDeadline
is 0 or hasn't passed.
cancelGame
cannot be called because it requires game.state == GameState.Created
, but the state is now Committed
.
timeoutJoin
cannot be called because Player B has joined (otherwise, the state wouldn't have changed to Committed
on the first commit).
The game is therefore stuck in the Committed
state with no path forward to resolution or cancellation.
Impact
Permanent Game Stall: The game cannot proceed or be cancelled, becoming permanently unusable.
Locked Funds/Tokens: The ETH or WinningTokens deposited by both Player A and Player B for the game remain locked in the contract indefinitely.
Tools Used
Manual code review.
Recommendations
Introduce a timeout specifically for the commit phase after the game state becomes Committed
.
Add commitDeadline
to Game struct:
Set commitDeadline
when state becomes Committed
: Modify commitMove
to set this deadline when the first player commits and the state changes. Use a reasonable duration (e.g., similar to joinTimeout
or timeoutInterval
).
Create timeoutCommit
function: Add a new external function allowing either player (or potentially anyone) to cancel the game if the commitDeadline
passes and only one player has committed.
Adjust commitMove
checks: Ensure commitMove
also checks against commitDeadline
if applicable.
Attack allows a player to reveal their move for the next turn before the opponent commits
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.