Rock Paper Scissors

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

Users Can Join Token-Based Games Without Transferring Required Tokens

Summary

A vulnerability exists in the Rock-Paper-Scissors (RPS) game smart contract that allows users to bypass token requirements when joining a token-based game. The issue stems from insufficient validation in the joinGameWithEth function, allowing users to join token games without transferring the required tokens, potentially leading to accounting inconsistencies and economic damage to the protocol.

Vulnerability Details

The contract distinguishes between ETH-based games and token-based games using the game.bet value:

  • ETH games: game.bet > 0 (set to the ETH amount sent by the creator)

  • Token games: game.bet = 0 (explicitly set to zero for token games)

The vulnerability exists in the joinGameWithEth function:

function joinGameWithEth(uint256 _gameId) external payable {
Game storage game = games[_gameId];
require(game.state == GameState.Created, "Game not open to join");
require(game.playerA != msg.sender, "Cannot join your own game");
require(block.timestamp <= game.joinDeadline, "Join deadline passed");
require(msg.value == game.bet, "Bet amount must match creator's bet");
game.playerB = msg.sender;
emit PlayerJoined(_gameId, msg.sender);
}

For token-based games where game.bet = 0, a user can call joinGameWithEth with msg.value = 0, which passes the requirement check msg.value == game.bet. This allows them to join a token game without sending any tokens, bypassing the token transfer requirement that exists in the proper joinGameWithToken function:

function joinGameWithToken(uint256 _gameId) external {
// ...
require(game.bet == 0, "This game requires ETH bet");
require(winningToken.balanceOf(msg.sender) >= 1, "Must have winning token");
// Transfer token to contract
winningToken.transferFrom(msg.sender, address(this), 1);
// ...
}

Impact

  • Users can participate in games without contributing any tokens#

  • Lack of correct token balancies which will cause errors when it comes time for rewards

  • The protocol will lack adequate funds to distribute to the rightful recipients

Tools Used

Manual code review

Recommendations

Add an explicit check in the joinGameWithEth function to ensure it can only be used for ETH-based games:

function joinGameWithEth(uint256 _gameId) external payable {
Game storage game = games[_gameId];
require(game.state == GameState.Created, "Game not open to join");
require(game.playerA != msg.sender, "Cannot join your own game");
require(block.timestamp <= game.joinDeadline, "Join deadline passed");
require(game.bet > 0, "This game requires ETH bet"); // Add this check
require(msg.value == game.bet, "Bet amount must match creator's bet");
game.playerB = msg.sender;
emit PlayerJoined(_gameId, msg.sender);
}

This additional check prevents users from using the ETH joining function for token-based games, ensuring that users must use the appropriate function which enforces token transfers.

Updates

Appeal created

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

Game Staking Inconsistency

joinGameWithEth function lacks a check to verify the game was created with ETH

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

Game Staking Inconsistency

joinGameWithEth function lacks a check to verify the game was created with ETH

Support

FAQs

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