Last Man Standing

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

Exponential Claim Fee Growth Due to Static `feeIncreasePercentage` Can Stall the Game

Root

The claimThrone() function increases the claimFee using the formula:

@> claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;

The feeIncreasePercentage remains static, and there is no upper bound or dynamic adjustment mechanism. As a result, the claimFee compounds exponentially with every throne claim.

Over time, this causes the cost of claiming the throne to skyrocket, even though the percentage increase is fixed. Since the fee is based on the previous value, it grows faster with each claim.

Impact

Game Lock: After several claims, the claimFee becomes unaffordable, preventing players from continuing the game. This causes a soft DoS (denial-of-service) where the throne is no longer contestable.

  • Locked Funds: The pot becomes permanently unclaimable unless the owner resets the game. This breaks the autonomous nature of the protocol and introduces centralization risk.

  • Poor Game Dynamics: Early players benefit from low fees, while later players are effectively blocked, reducing fairness and engagement over time.

Description

🔹 Normal Behavior

The claimThrone() function allows players to claim the throne by paying a specified claimFee. After each successful claim, the fee is increased by a percentage (feeIncreasePercentage) to gradually raise the cost for subsequent players. This mechanism is designed to add increasing difficulty and grow the pot value over time.

🔻 Specific Issue

The feeIncreasePercentage remains static and is not updated or capped within the contract. As a result, the claimFee grows exponentially over successive claims. Eventually, the cost to claim the throne becomes prohibitively high, preventing new players from participating. This leads to a soft denial-of-service (DoS), where the game becomes stuck unless the owner manually resets or lowers the fee

function claimThrone() external payable gameNotEnded nonReentrant {
...
@> claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
...
}

Risk

Likelihood:

  • Reason 1: This occurs naturally after a moderate number of users claim the throne, since the claimFee increases exponentially with each claim due to compounding.

  • Reason 2: There is no automatic mechanism to reset or reduce the claimFee, so the fee continues to grow over time without owner intervention.

Impact:

  • Impact 1: Players will eventually be priced out of participation, leading to a soft denial-of-service (DoS) where no one can claim the throne anymore.

  • Impact 2: The pot becomes permanently locked unless the owner manually resets the game.

Proof of Concept

This test simulates multiple players claiming the throne in sequence. Since the feeIncreasePercentage is fixed and there is no cap or decay mechanism, the claimFee increases exponentially with each claim. The output shows how the cost quickly becomes unaffordable.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Game.sol";
contract GameFeeGrowthTest is Test {
Game game;
address player = address(0xBEEF);
function setUp() public {
// initialClaimFee = 1 ether
// gracePeriod = 1 day
// feeIncreasePercentage = 20%
// platformFeePercentage = 10%
game = new Game(1 ether, 1 days, 20, 10);
vm.deal(player, 100 ether); // give player funds
}
function testClaimFeeGrowth() public {
vm.startPrank(player);
uint256 expectedIncrease = 20; // 20%
for (uint256 i = 0; i < 10; i++) {
uint256 fee = game.claimFee();
uint256 increase = game.feeIncreasePercentage(); // <--- check it here
console.log(
"Claim #%s fee: %s ETH | feeIncrease: %s%%",
i + 1,
fee / 1e18,
increase
);
assertEq(
increase,
expectedIncrease,
"feeIncreasePercentage should remain constant"
);
game.claimThrone{value: fee}();
}
vm.stopPrank();
}
}

Output of the POC

[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/GameFeeGrowthTest.t.sol:GameFeeGrowthTest
[PASS] testClaimFeeGrowth() (gas: 420699)
Logs:
Claim #1 fee: 1 ETH | feeIncrease: 20%
Claim #2 fee: 1 ETH | feeIncrease: 20%
Claim #3 fee: 1 ETH | feeIncrease: 20%
Claim #4 fee: 1 ETH | feeIncrease: 20%
Claim #5 fee: 2 ETH | feeIncrease: 20%
Claim #6 fee: 2 ETH | feeIncrease: 20%
Claim #7 fee: 2 ETH | feeIncrease: 20%
Claim #8 fee: 3 ETH | feeIncrease: 20%
Claim #9 fee: 4 ETH | feeIncrease: 20%
Claim #10 fee: 5 ETH | feeIncrease: 20%
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.18ms (1.84ms CPU time)

Recommended Mitigation

- remove this code
+ add this code
Updates

Appeal created

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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

Give us feedback!