Last Man Standing

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

Unbounded Fee Inflation Breaks Game Balance and Locks Out Players

Unbounded Fee Inflation Breaks Game Balance and Locks Out Players

Description

  • In the Last Man Standing game, each time a player claims the throne, the claimFee is increased by a percentage set at deployment through the variable feeIncreasePercentage. This mechanism is meant to gradually raise the entry cost and add tension to the game.

  • However, the constructor only ensures feeIncreasePercentage <= 100, which allows a 100% increase — effectively doubling the claimFee at each call:

@> claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
  • This leads to exponential growth (e.g., 0.5 ETH → 1 → 2 → 4 → 8 ...), pricing out most participants after just a few rounds. Since no upper bound or economic cap exists, the throne becomes unaffordable, and game rounds can lock themselves.

  • Although the contract includes a resetGame() function, only the owner can invoke it, and it resets the round after a winner has been declared, not during a live game. Therefore, it does not mitigate the fact that individual rounds can become inaccessible, or that whales can dominate early with no viable competition.

Risk

Likelihood:

  • The issue will occur in any deployment where feeIncreasePercentage is too high (e.g., ≥ 50), especially if many players interact in a short time.

  • It is deterministic and accumulative with every call to claimThrone.

Impact:

  • Rounds can become economically inaccessible after a few claims.

  • The escalation mechanic becomes abusive rather than challenging.

  • Early players or wealthy addresses can dominate and crowd out others.

  • The game logic of Last Man Standing (progressive but fair competition) is broken.

Proof of Concept

Add this Proof of Code into Game.t.sol that proves that with 100% fee increase the claimThrone become rapidly very expensive.

function test_FeeTooHigh() public {
assertEq(game.claimFee(), 0.1 ether);
// player1 becomes king
vm.prank(player1);
game.claimThrone{value: 0.1 ether}();
assertEq(game.claimFee(), 0.2 ether);
// player2
vm.prank(player2);
game.claimThrone{value: 0.2 ether}();
assertEq(game.claimFee(), 0.4 ether);
// Player3
vm.prank(player3);
game.claimThrone{value: 0.4 ether}();
assertEq(game.claimFee(), 0.8 ether);
// Player4
vm.prank(player4);
game.claimThrone{value: 0.8 ether}();
assertEq(game.claimFee(), 1.6 ether);
}

Recommended Mitigation

Mitigation should target the root cause: unbounded configuration of feeIncreasePercentage, and optionally, introduce caps for claimFee.

Option 1: Restrict feeIncreasePercentage

- require(_feeIncreasePercentage <= 100, "Game: Fee increase percentage must be 0-100.");
+ require(_feeIncreasePercentage <= 25, "Game: Fee increase too high — risk of exponential fee inflation.");

Option 2 (optional): Add max claim fee cap

+ uint256 public constant MAX_CLAIM_FEE = 5 ether;
...
+ uint256 increasedFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
+ if (increasedFee > MAX_CLAIM_FEE) {
+ claimFee = MAX_CLAIM_FEE;
+ } else {
+ claimFee = increasedFee;
+ }
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.