Rock Paper Scissors

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

Inconsistent Token Betting Mechanism in joinGameWithToken Function.

Summary

There are a few issues leading to this vulnerability. The amount of Token bet in the game wasn't declared in the Game Struct. Also, the joinGameWithToken function lacks proper enforcement of token betting consistency. It doesn't verify the token amount required to join a game, and it does not validate whether the joining player (Player B) stakes the same token amount as the game creator (Player A). This allows players to cheat by joining with fewer tokens than intended, leading to fund imbalance and unfair gameplay.

Vulnerability Details

Within the joinGameWithToken function, the contract allows a player to join a token-based game simply by holding and transferring 1 token:

winningToken.transferFrom(msg.sender, address(this), 1);

However:

  1. Token Bet Amount Not Declared: There is no variable storing how many tokens Player A staked when creating the game. This is unlike ETH games, where game.bet is explicitly recorded.

  2. Token Bet Not Matched: Player B is never required to stake the same number of tokens as Player A. A malicious user could create a game and commit more value (e.g., 100 tokens), while another player joins by transferring only 1 token, and still has equal winning power.

    This inconsistency breaks the fairness of the game and could allow manipulation where:

    • Players risk vastly different values.

    • One side (e.g., Player A) can lose much more than the opponent (Player B) if they lose.

    • A malicious actor can repeatedly join games with minimal stake hoping to win against high-stake opponents.

Impact

This vulnerability allows unfair participation in games. Players can cheat the system by staking fewer tokens and potentially winning more than they risked. This can lead to:

  • Loss of player trust.

  • Economic imbalance in the system.

  • Potential token farming or abuse scenarios.

Tools Used

Manual Review.

Recommendations

Introduce a tokenBet variable in the Game struct to explicitly store the number of tokens staked by Player A.

struct Game {
address playerA; // Creator of the game
address playerB; // Second player to join
uint256 bet; // Amount of ETH bet
uint256 tokenBet; //Amount of token bet
uint256 timeoutInterval; // Time allowed for reveal phase
uint256 revealDeadline; // Deadline for revealing moves
uint256 creationTime; // When the game was created
uint256 joinDeadline; // Deadline for someone to join the game
uint256 totalTurns; // Total number of turns in the game
uint256 currentTurn; // Current turn number
bytes32 commitA; // Hashed move from player A
bytes32 commitB; // Hashed move from player B
Move moveA; // Revealed move from player A
Move moveB; // Revealed move from player B
uint8 scoreA; // Score for player A
uint8 scoreB; // Score for player B
GameState state; // Current state of the game
}

  • In joinGameWithToken, enforce:

    require(tokenAmount == game.tokenBet, "Token amount must match creator's bet");
  • Additionally, ensure that the amount transferred via transferFrom() matches the required stake.

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.