Rock Paper Scissors

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

The `joinGameWithToken` function Allows Overwriting of `playerB` in `RockPaperScissors.sol`

Summary

The joinGameWithToken function fails to transition the game state after a player successfully joins as playerB. This oversight allows subsequent calls to the same function (before the game moves to the commit phase) to replace the initially joined playerB, specifically in games designed to use the WinningToken.


Vulnerability Details

The intended flow for a token game involves:

  1. Player A creates a game using createGameWithToken. The game state is Created.

  2. Player B approves the RockPaperScissors contract to spend their WinningToken.

  3. Player B calls joinGameWithToken. The function verifies conditions, including game.state == GameState.Created, transfers the token via transferFrom, and sets game.playerB.

The vulnerability lies in the fact that after step 3, the game.state remains GameState.Created.

Consider this scenario:

  • Alice creates a token game (ID: 101). games[101].state is Created.

  • Bob approves the contract and calls joinGameWithToken(101). The token is transferred, and games[101].playerB becomes Bob's address. Crucially, games[101].state is still Created.

  • Before Alice or Bob calls commitMove, Charlie (who has also approved the contract) calls joinGameWithToken(101).

  • Charlie's call finds games[101].state is still Created, passes all checks, transfers his token, and overwrites games[101].playerB to Charlie's address.

  • The commitMove function is the first place the state would change to Committed, but this happens too late to prevent the overwrite during the joining phase.


Solidity

// In RockPaperScissors.sol
function joinGameWithToken(uint256 _gameId) external {
Game storage game = games[_gameId];
// ... initial checks pass for both Bob and Charlie ...
require(game.state == GameState.Created, "Game not accepting players");
// ... other checks ...
// Bob's token is transferred first.
// Then, Charlie's token is transferred in a subsequent call.
winningToken.transferFrom(msg.sender, address(this), 1);
// Bob's address is set first.
// Then, Charlie's address overwrites Bob's address.
game.playerB = msg.sender;
// !! No state change occurs here, leaving the door open !!
emit PlayerJoined(_gameId, msg.sender); // Emits for Bob, then later for Charlie
}

Impact

This vulnerability allows the second player (playerB) slot in token games to be usurped before the game properly starts. The first user (Bob in the example) successfully joins, has their token taken by the contract, but is then silently replaced by a subsequent joiner (Charlie). This leads to:

  • Game Disruption: The intended player B cannot participate.

  • Potential Griefing: Malicious actors can specifically target players trying to join games by quickly joining after them.

  • Locked Tokens : Bob's token is held by the contract leading to loss of funds.


Tools Used

  • Manual code review.


Recommendations

The vulnerability is resolved by ensuring the game state transitions immediately upon a successful join, preventing further joins. Add a state change within joinGameWithToken right after playerB is set.

function joinGameWithToken(uint256 _gameId) external {
Game storage game = games[_gameId];
// ... requires ...
// Transfer token from player B
winningToken.transferFrom(msg.sender, address(this), 1);
game.playerB = msg.sender;
+ // Prevent subsequent joins by changing state immediately
+ game.state = GameState.Committed;
+ // Set the reveal deadline now that players are confirmed
+ game.revealDeadline = uint64(block.timestamp + timeoutInterval);
emit PlayerJoined(_gameId, msg.sender);
}
Updates

Appeal created

m3dython Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Absence of State Change on Join Allows Player B Hijacking

Game state remains Created after a player joins

Support

FAQs

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