Last Man Standing

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

Uncapped 100% Platform Fee Leads to Zero Pot Growth, Rendering Game Unprofitable

Root + Impact

Root Cause: No prohibition against setting platform fee to 100% → Impact: All incoming deposits go to platform, pot never grows, participants cannot profit and may even lose ETH.

WHY WOULD ANYONE TRUST THE OWNER: It's a Game something similar to a gamble. So we can't expect the owner to be fair. The owner can change the rules at any time, so we can't expect the game to be fair.

Description

  • The claimThrone() and updatePlatformFeePercentage() functions lack safeguards against a 100% platform fee. When the owner sets platformFeePercentage = 100, the calculation:

currentPlatformFee = (sentAmount * platformFeePercentage) / 100; // = sentAmount
amountToPot = sentAmount - currentPlatformFee; // = 0
pot = pot + amountToPot; // unchanged
function claimThrone() external payable gameNotEnded nonReentrant {
uint256 sentAmount = msg.value;
@> uint256 currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// ...
platformFeesBalance += currentPlatformFee;
uint256 amountToPot = sentAmount - currentPlatformFee;
pot += amountToPot; // amountToPot == 0 when platformFeePercentage == 100
// ...
emit ThroneClaimed(msg.sender, sentAmount, claimFee, pot, block.timestamp);
}
function updatePlatformFeePercentage(uint256 _newPlatformFeePercentage)
@> external onlyOwner isValidPercentage(_newPlatformFeePercentage)
-------------------------------^
{
@> // No check that _newPlatformFeePercentage < 100
// isValidPercentage is inefficiently checking <= 100
// @danger: 100% platform fee leads to zero pot growth
platformFeePercentage = _newPlatformFeePercentage;
emit PlatformFeePercentageUpdated(_newPlatformFeePercentage);
}

Risk

Likelihood: Medium

  • causes all of each claimant’s ETH to be siphoned into platformFeesBalance, leaving the pot untouched. Subsequent winners receive nothing beyond their original stake (if even that), breaking the game’s economic incentive. Participants pay ETH to play but see zero return, effectively losing 100% of their stake.

  • isValidPercentage only checks ≤ 100, not < 100.

  • At 100%, amountToPot is always zero.

  • Pot stagnates, making the game unprofitable or even a loss for participants.

Impact: Medium

  • Zero Incentive: No growth of the pot, participants cannot win anything beyond their stake (and in fact lose net ETH when fee increases).

  • Loss of Funds: Players pay full claim fee into platformFeesBalance and receive nothing on withdrawal.

  • Game Breakdown: The core economic model collapses—no one will join if the pot never grows.

  • Griefing Risk: Malicious or mistaken owner can cripple the game at any time by setting fee to 100%.

Tools Used:

  • Foundry Test Suite

  • Chat-GPT AI Assistance (Report Grammar Check & Improvements)

  • Manual Review

Proof of Concept

function test_platformFeeCanBeSetTo100Percent() public {
vm.startPrank(player2);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
// try for both scenarios
// 1. not 100% // comment out below prank
// 2. 100% // comment in below prank
vm.startPrank(deployer);
game.updatePlatformFeePercentage(100);
vm.stopPrank();
uint256 player1BalanceBeforeClaim = player1.balance;
vm.startPrank(player1);
game.claimThrone{value: game.claimFee()}();
vm.warp(block.timestamp + 1 days + 1);
game.declareWinner();
game.withdrawWinnings();
vm.stopPrank();
// would fail if fee is 100%
assertGe(player1.balance, player1BalanceBeforeClaim);
}

step 1: go to test/Game.t.sol file

step 2: paste the above code ⬆️

step 3: run the test suite

forge test --mt test_platformFeeCanBeSetTo100Percent

step 4: See the Output

Scenario:

  1. Initial Round: Player2 claims throne with 1 ETH. Platform collects 100% fee -> platformFeesBalance += 1 ETH, pot += 0.

  2. Subsequent Round: Player1 attempts to claim with the then-increased fee (originally 1 ETH -> say 1.1 ETH); again 100% goes to platform, pot remains empty.

  3. Declare Winner: After grace period, Player1 calls declareWinner(). Even though they are last king, pendingWinnings[currentKing] += pot yields zero. Player1 withdraws nothing - 100% loss of their 1.1 ETH stake.

Recommended Mitigation

Prevent 0-Payout Scenarios
Add a runtime check in claimThrone():

uint256 amountToPot = sentAmount - currentPlatformFee;
+ require(amountToPot > 0, "Game: Platform fee too high, no pot growth");

Adjust Percentage Validator
Change isValidPercentage to enforce < 100 instead of ≤ 100.

modifier isValidPercentage(uint256 _percentage) {
- require(_percentage <= 100, "Game: Percentage must be 0-100.");
+ require(_percentage < 100, "Game: Percentage must be 0-100.");
_;
}

Don't allow to update platform fee in the middle of the game

- Add a modifier to prevent changing the platform fee while the game is active:

```solidity
// or use existing one
```
Updates

Appeal created

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
theirrationalone Submitter
about 1 month ago
inallhonesty Lead Judge
29 days ago
inallhonesty Lead Judge 26 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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