Rock Paper Scissors

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

Missing upper bound on total turns allows uint8 score overflow, breaking game progression.

Summary

The RockPaperScissors::createGameWithEth and RockPaperScissors::createGameWithToken
functions allows users to specify the number of game turns using the _totalTurns parameter.
Although the function validates that _totalTurns is greater than zero and an odd number, it does not
enforce an upper limit on the number of turns.


Internally, the game uses uint8 types to store player scores (scoreA and scoreB). A uint8 has a maxi‑
mum value of 255. If a game is configured to allow more than 255 turns, and one player wins over 255
rounds, the scoreX++ operation will cause an overflow and trigger a revert due to built‑in overflow
checks in Solidity versions ≥ 0.8.
This creates a scenario where malicious users can exploit the absence of an upper bound to manipu‑
late or break the game logic

Vulnerability Details

Proof of Concept:

Create a game with totalTurns set to a value greater than 255.
Join the game using a second account.
Simulate gameplay where playerA wins every round, incrementing scoreA until it exceeds the uint8 limit.

function testScoreOverflow() public {
vm.prank(playerA);
gameId = game.createGameWithEth{value: 1 ether}(301, 5 minutes); // >255 turns
vm.prank(playerB);
game.joinGameWithEth{value: 1 ether}(gameId);
for (uint256 i = 0; i < 300; i++) {
// Always let playerA win (Rock vs Scissors)
playTurn(
gameId,
RockPaperScissors.Move.Rock,
RockPaperScissors.Move.Scissors
);
}
// Optionally fetch scoreA from the contract
(, , , , , , , , , , , , , uint8 scoreA, , ) = game.games(gameId);
console2.log("score A is ", scoreA);
assertGt(scoreA, 255, "scoreA should overflow");
}

Impact

If exploited, this issue can:

- Cause score variables to overflow and trigger transaction reverts mid-game.

- Prevent games from finishing correctly, leading to denial of service for both players.

- Lock up ETH bets indefinitely if _determineWinner cannot complete due to score overflow.

This vulnerability may be used to grief opponents or disrupt the contract's availability by repeatedly creating or participating in games with abnormally high turn counts.

Tools Used

  • Foundry

Recommendations

  • Introduce a Maximum Turn Limit: Add a validation check to cap the number of game turns to a reasonable maximum that cannot exceed the capacity of uint8 (i.e., 255):

    + uint256 public constant MAX_TURNS = 255;
    ..
    ..
    function createGameWithEth(uint256 _totalTurns, uint256 _timeoutInterval) external payable returns (uint256) {
    require(msg.value >= minBet, "Bet amount too small");
    - require(_totalTurns > 0, "Must have at least one turn");
    + require(_totalTurns > 0 && _totalTurns, "Must have at least one turn");
    ..
    ..
    }
Updates

Appeal created

m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
m3dython Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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