Last Man Standing

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

Unbounded claimFee Growth in claimThrone Function

Root + Impact

Description

  • The claimFee in the Game.sol contract increases by 10% after each claim, leading to exponential growth that results in impractically high fees after multiple claims, deterring player participation and potentially rendering the game unplayable.

  • The claimThrone function allows players to claim the throne by paying the current claimFee, which starts at 0.1 ETH and increases by 10% after each successful claim, as implemented in:

// Root cause in the codebase with @> marks to highlight the relevant section
function claimThrone() external payable gameNotEnded nonReentrant {
require(msg.value >= claimFee, "Game: Insufficient ETH sent to claim the throne.");
require(msg.sender != currentKing, "Game: You are already the king. No need to re-claim.");
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// Calculate platform fee
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// Defensive check to ensure platformFee doesn't exceed available amount after previousKingPayout
if (currentPlatformFee > (sentAmount - previousKingPayout)) {
currentPlatformFee = sentAmount - previousKingPayout;
}
platformFeesBalance = platformFeesBalance + currentPlatformFee;
// Remaining amount goes to the pot
amountToPot = sentAmount - currentPlatformFee;
pot = pot + amountToPot;
// Update game state
currentKing = msg.sender;
lastClaimTime = block.timestamp;
playerClaimCount[msg.sender] = playerClaimCount[msg.sender] + 1;
totalClaims = totalClaims + 1;
// Increase the claim fee for the next player
@> claimFee = claimFee + (claimFee * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}

Risk

Likelihood:

  • The claimFee grows exponentially with each claim, reaching 2.81 ETH after 35 claims and 5.4 ETH after 42 claims in a game with active participation.

  • Competitive environments with frequent claims accelerate the fee’s growth, making high fees inevitable in prolonged rounds.

Impact:

  • Players are deterred from participating due to the high claimFee, reducing game activity and engagement, as fees like 5.4 ETH (~$15,000–$20,000) are unaffordable for most.

  • Impact 2

Proof of Concept

The following Foundry test demonstrates the exponential growth of the claimFee. It performs 50 claims, cycling through three players to avoid the currentKing restriction, and logs the claimFee after each claim. The test fails at the 36th claim due to an OutOfFunds error, as player3’s balance (initially 10 ETH) is insufficient for the claimFee of ~2.81 ETH.

The test output shows the claimFee growth up to claim 35, with the test failing at claim 36 due to OutOfFunds:

Claim fee after claim 33: 2322515441988780795

Claim fee after claim 34: 2554766986187658874

Claim fee after claim 35: 2810243684806424761

VM::prank(player3: [0xcC37919fDb8E2949328cDB49E8bAcCb870d0c9f3])

│ └─ ← [Return]

├─ [0] Game::claimThrone{value: 2810243684806424761}()

│ └─ ← [OutOfFunds] EvmError: OutOfFunds

└─ ← [Revert] EvmError: Revert

function testClaimFeeIncrease() public {
// Array of players to cycle through to avoid currentKing revert
address[50] memory players = [
player1, player2, player3, player1, player2,
player3, player1, player2, player3, player1,
player2, player3, player1, player2, player3,
player1, player2, player3, player1, player2,
player3, player1, player2, player3, player1,
player2, player3, player1, player2, player3,
player1, player2, player3, player1, player2,
player3, player1, player2, player3, player1,
player2, player3, player1, player2, player3,
player1, player2, player3, player1, player2
];
// Initial claim fee
uint256 currentFee = INITIAL_CLAIM_FEE; // 0.1 ether
// Perform up to 50 claims
for (uint256 i = 0; i < 50; i++) {
vm.prank(players[i]);
game.claimThrone{value: currentFee}();
// Calculate next expected fee
uint256 nextFee = currentFee + (currentFee * FEE_INCREASE_PERCENTAGE) / 100;
assertEq(game.claimFee(), nextFee, "Claim fee should increase by 10%");
console2.log("Claim fee after claim %d: %s", i + 1, nextFee);
currentFee = nextFee;
}
// Verify claimFee after 50 claims
uint256 expectedFee = 0.1 ether;
for (uint256 i = 0; i < 50; i++) {
expectedFee = expectedFee + (expectedFee * FEE_INCREASE_PERCENTAGE) / 100;
}
assertEq(game.claimFee(), expectedFee, "Claim fee should match after 50 claims");
}

Recommended Mitigation

To prevent the claimFee from becoming impractically high, implement a cap on the claimFee.

Updates

Appeal created

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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