Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

`Game::owner()` Can change Fee Parameters during an active to manipulate game outcome in their favor or ensure self-victory.

Root + Impact

Description

  • The Game::updateClaimFeeParameters(uint256 _newInitialClaimFee,uint256 _newFeeIncreasePercentage) function allows the contract owner to change the initialClaimFee and feeIncreasePercentage at any time, whether a game is active or not.

  • This affords Game::owner() the opportunity to set the fee parameters during an active game to high values and dicourage other players from claiming the throne thereby essentially stealing the win or skewing the game result in their favor

https://github.com/CodeHawks-Contests/2025-07-last-man-standing/blob/47d9d19a78acb52270269f4bff1568b87eb81a96/src/Game.sol#L294

Risk

Likelihood:

  • Owner can update the Claim fee parameters at any point during the game

Impact:

  • Breaks core game dynamics and deters players from trusting the system.

  • Owner can discourage all future participation after becoming king.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {Game} from "../src/Game.sol";
contract GameTest is Test {
Game public game;
address public deployer;
address public player1;
address public player2;
address public player3;
address public maliciousActor;
// Initial game parameters for testing
uint256 public constant INITIAL_CLAIM_FEE = 0.1 ether; // 0.1 ETH
uint256 public constant GRACE_PERIOD = 1 days; // 1 day in seconds
uint256 public constant FEE_INCREASE_PERCENTAGE = 10; // 10%
uint256 public constant PLATFORM_FEE_PERCENTAGE = 5; // 5%
function setUp() public {
deployer = makeAddr("deployer");
player1 = makeAddr("player1");
player2 = makeAddr("player2");
player3 = makeAddr("player3");
maliciousActor = makeAddr("maliciousActor");
vm.deal(deployer, 10 ether);
vm.deal(player1, 10 ether);
vm.deal(player2, 10 ether);
vm.deal(player3, 10 ether);
vm.deal(maliciousActor, 10 ether);
vm.startPrank(deployer);
game = new Game(
INITIAL_CLAIM_FEE,
GRACE_PERIOD,
FEE_INCREASE_PERCENTAGE,
PLATFORM_FEE_PERCENTAGE
);
vm.stopPrank();
}
function testCanUpdateFeeParametersWhenGameOn() public {
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}(); // player1 claims throne
vm.warp(GRACE_PERIOD / 2); // simulate time passage in an active game
uint256 newClaimFee = game.claimFee();
uint256 newFeeIncreasePercentage = 100;
vm.startPrank(game.owner());
game.updateClaimFeeParameters(newClaimFee, newFeeIncreasePercentage);
// owner update the fee parameters and increase next claim fee percentage to 100%
vm.stopPrank();
vm.startPrank(player2);
game.claimThrone{value: newClaimFee}(); // new player claims throne with the old claimfee, but next claimfee will be double of this
vm.stopPrank();
vm.startPrank(player3);
game.claimThrone{value: game.claimFee()}(); // player has to claim throne with double of player2's claimFee
vm.stopPrank();
}
}

Recommended Mitigation

* Restrict `Game::updateClaimFeeParameters` such that it can only be called when a game is not active/before the start of a new game. Eg by adding `gameEndedOnly` modifier.
```diff
- function updateClaimFeeParameters(
- uint256 _newInitialClaimFee,
- uint256 _newFeeIncreasePercentage
- ) external onlyOwner isValidPercentage(_newFeeIncreasePercentage){...}
+ function updateClaimFeeParameters(
+ uint256 _newInitialClaimFee,
+ uint256 _newFeeIncreasePercentage
+ ) external onlyOwner isValidPercentage(_newFeeIncreasePercentage) gameEndedOnly{..}
  • Set the Game::feeIncreasePercentage within reasonable limits, e.g (0-20%)

Updates

Appeal created

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.