Rock Paper Scissors

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

Unfair Game Cancellation by PlayerA leading to Loss of Funds for PlayerB

Summary

The cancelGame() function allows PlayerA to cancel a game even after PlayerB has joined, which is unfair because:

  1. PlayerB have already committed funds (or winning tokens) expecting the game to proceed.

  2. PlayerB might be winning the game, but if PlayerA cancels, PlayerB will lose the reward, and their bet will remain stuck in the contract.

Vulnerability Details

No check prevents cancellation after PlayerB joins, violating game fairness.

function cancelGame(uint256 _gameId) external {
Game storage game = games[_gameId];
require(game.state == GameState.Created, "Game must be in created state");
require(msg.sender == game.playerA, "Only creator can cancel");
_cancelGame(_gameId);
}

POC:

Add this to the test file

// Test - should not cancel game after someone joined, but it can
function test_RevertWhen_CancelAfterJoin() public {
gameId = createAndJoinGame();
vm.prank(playerA);
game.cancelGame(gameId);
(,,,,,,,,,,,,,,, RockPaperScissors.GameState state) = game.games(gameId);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Cancelled));
}

Impact

PlayerA can exploit this to cancel games strategically esp. if they see PlayerB is likely to win.

Tools Used

foundry tests

Recommendations

Add a validation to check that playerB is not in the game, to avoid stuck of funds

function cancelGame(uint256 _gameId) external {
Game storage game = games[_gameId];
require(game.state == GameState.Created, "Game must be in created state");
require(msg.sender == game.playerA, "Only creator can cancel");
+ require(game.playerB == address(0), "Someone has already joined the game");
_cancelGame(_gameId);
}
Updates

Appeal created

m3dython Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
m3dython Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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