Rock Paper Scissors

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

A player can use `RockPaperScissors::joinGameWithEth` to join a game created with `RockPaperScissors::createGameWithToken` without paying anything and play for the full prize

Description:

playerB can call RockPaperScissors::joinGameWithEth with 0 ETH to join a game created by playerA using RockPaperScissors::createGameWithToken.

This results in playerB paying no token or ETH, but still able to participate and possibly win a game with a token prize.
The issue is caused by a lack of access control in the joinGameWithETH function, which only requires msg.value to be equal to the bet amount in ETH (0 for a token game).

Impact:

Both players can participate in a game equally with only one of them paying their bet, making the game unfair.

Proof of Code:

The following test proves that playerA can create a token game and playerB can join with 0 ETH:

function testAuditCreateTokenGameAndJoinWith0Eth() public {
// Create a game with token
vm.startPrank(playerA);
token.approve(address(game), 1);
gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
// Player B joins the game with ETH
vm.startPrank(playerB);
vm.expectEmit(true, true, false, true);
emit PlayerJoined(gameId, playerB);
game.joinGameWithEth{value: 0}(gameId);
vm.stopPrank();
// Verify game state
(address storedPlayerA, address storedPlayerB,,,,,,,,,,,,,, RockPaperScissors.GameState state) =
game.games(gameId);
assertEq(storedPlayerA, playerA);
assertEq(storedPlayerB, playerB);
assertEq(token.balanceOf(playerA), 9);
assertEq(token.balanceOf(playerB), 10);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Created));
}

Recommended Mitigation:

RockPaperScissors::joinGameWithEth should not be allowed to be called with 0 ETH. Alternatively, a new variable within the RockPaperScissors::Game struct could be used to differentiate between token and ETH 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(msg.value == game.bet, "Bet amount must match creator's bet");
+ require(msg.value > 0, "Cannot join token game")
}
Updates

Appeal created

m3dython Lead Judge about 1 month 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.