Rock Paper Scissors

First Flight #38
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: medium
Invalid

Game does not update state to `GameState.Finished` after the last turn in `RockPaperScissors::_determineWinner()`

Summary

The game does not update the state to GameState.Finished after the last turn in the _determineWinner() function. This could leave the game in a Committed state even though the game has concluded, potentially allowing inconsistent behavior or unintended interactions.

Vulnerability Details

In the RockPaperScissors contract, after the final move is revealed and the winner is determined in _determineWinner(), the game state is not explicitly updated to GameState.Finished. This results in the game potentially remaining in the Committed state even after all turns are completed.

Impact

The game remains in an incorrect state (Committed) even after completion.

  • External or internal logic relying on the Finished state may behave incorrectly.

  • Could enable replay or redundant commit/reveal attempts depending on future implementations.

  • Potential implications for fee withdrawal, prize claiming, or cleanup mechanisms that depend on proper state transitions.

Tools Used

Foundry

POC:

function test\_GameStateUpdatedToFinished() public {
// Create and join game
vm.prank(playerA);
gameId = game.createGameWithEth{value: BET\_AMOUNT}(TOTAL\_TURNS, TIMEOUT);
vm.prank(playerB);
game.joinGameWithEth{value: BET_AMOUNT}(gameId);
// Use consistent secrets (as bytes32)
bytes32 secretA = keccak256("secretA");
bytes32 secretB = keccak256("secretB");
RockPaperScissors.Move moveA = RockPaperScissors.Move.Rock;
RockPaperScissors.Move moveB = RockPaperScissors.Move.Scissors;
for (uint256 i = 0; i < TOTAL_TURNS; i++) {
bytes32 playerACommit = keccak256(abi.encodePacked(uint8(moveA), secretA));
bytes32 playerBCommit = keccak256(abi.encodePacked(uint8(moveB), secretB));
vm.prank(playerA);
game.commitMove(gameId, playerACommit);
vm.prank(playerB);
game.commitMove(gameId, playerBCommit);
vm.prank(playerA);
game.revealMove(gameId, uint8(moveA), secretA);
vm.prank(playerB);
game.revealMove(gameId, uint8(moveB), secretB);
}
// Final assertion to ensure state is updated
(, , , , , , , , , , , , , , , RockPaperScissors.GameState state) = game.games(gameId);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Finished), "Game did not end in Finished state");

}

Recommendations

Update the _determineWinner() function to explicitly set the game state to GameState.Finished once the final turn has been processed. For example:

if (game.currentTurn > game.totalTurns) {

// Finalize the game

game.state = GameState.Finished;

emit GameFinished(gameId, overallWinner, prize);

}

Updates

Appeal created

m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational

Code suggestions or observations that do not pose a direct security risk.

m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational

Code suggestions or observations that do not pose a direct security risk.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.