Game does not finish even after a player has won the majority of turns. Since the opponent has essentially lost, the opponent has no incentive to continue playing. The opponent can refuse to play (commit) the remaining turns. This causes the game to be unable to reach Finished
state, hence preventing the distribution of rewards to the winning player. In this game state, there are several resolution cases, all of which harms the winning player (detailed below) and results in the winning player not able to receive their rightful rewards, with 1 case even benefitting the losing player. This severely disrupts the fairness of the game.
In RockPaperScissors::_determineWinner#L441
, the game is reset for the next turn if totalTurns
is not met. Even if a player has won the majority of turns, it does not end the game but resets the game for the next turn, expecting the players to continue playing the game.
However, the losing player has no incentive to continue playing and can refuse to play (commit) the remaining turns. Since the winning player cannot do anything for the game to reach Finished
state before totalTurns
has been played, the winning player will be unable to receive the rewards. This issue is applicable for both games created with token and ETH.
RockPaperScissors::_determineWinner#L441
In this game state, there are several resolution cases, neither of which benefits the winning player.
Case 1: Player A wins majority, stalemate
Player A (game creator) wins majority
Player B refuses to play (commit)
Player A cannot cancel game (RockPaperScissors::cancelGame
) due to checks (require(game.state == GameState.Created)
)
Both player A and player B loses out on their ETH bet
Case 2: Player B wins majority, stalemte
Player B wins majority
Player A (game creator) refuses to play (commit)
Player B cannot cancel game (RockPaperScissors::cancelGame
) due to checks (require(msg.sender == game.playerA)
)
Both player A and player B loses out on their ETH bet
(this is functionally similar to Case 1)
Case 3: Player wins majority, winning player use RockPaperScissors::timeoutReveal
as escape hatch, benefitting the losing player
Player wins majority (at turn N)
Losing player refuses to play (commit) (at turn N+1)
Neither player can cancel game (RockPaperScissors::cancelGame
) due to the checks (require(game.state == GameState.Created)
) and (require(msg.sender == game.playerA)
)
However, during turn N when both players have committed, RockPaperScissors::cancelGame#L219
has set a revealDeadline
Winning player can wait for the revealDeadline
to elapse and call RockPaperScissors::timeoutReveal
to rescue their bet
This calls RockPaperScissors::_cancelGame
which refunds the bet to both players. Winning player only receives a refund of the initial bet amount but does not get the reward they rightfully deserves and losing player does not lose the bet that they have risked (losing player gets refunded as well). This severely disrupts the fairness of the game.
Place the following into RockPaperScissorsTest.t.sol
and run
forge test --mt testGameDoesNotEndAfterMajorityWins
Impact: High. Game is stuck and winning player cannot receive rewards
Likelihood: High. It is common occurrence in games to have majority wins before totalTurns
has been played. If a player has lost before totalTurns
, there is no incentive to continue playing. Additionally, the player can choose to use this vulnerability to exert revenge as griefing attack to prevent winning player from receiving rewards
Severity: High
Manual review
End the game when a player has won a majority of turns.
Protocol does not provide a way for Player B to exit a game and reclaim their stake if Player A stops participating
Protocol does not provide a way for Player B to exit a game and reclaim their stake if Player A stops participating
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.