Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

[H-3] `Claim::claimThrone()` missing payout to previous king

Root Cause

In the claimThrone() function, a comment states:

// If there's a previous king, a small portion of the new claim fee is sent to them.

But this logic is not implemented. Instead, the full msg.value - platformFee is added to the pot, and the previous king never receives any payout.


Description

The Game::claimThrone() function includes a comment indicating that a portion of the claim fee should be sent to the previous king as a reward. However, this logic is completely missing in the actual implementation. Instead, the full msg.value - platformFee is added to the pot, and the dethroned king receives nothing, breaking the intended game mechanics.

This introduces a mismatch between expectations and reality, especially if users were led to believe (via UI/comments/announcements) that dethroned kings will be rewarded. The lack of such reward removes any economic incentive to compete, discouraging participation and possibly leading to centralization or inactivity.

function claimThrone() external payable gameNotEnded nonReentrant {
require(msg.value >= claimFee, "Game: Insufficient ETH sent to claim the throne.");
// @audit Corrected the require statement logic for testing further.
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
);
}

Impact

Economic incentive flaw: Being dethroned has no reward, which can discourage users from participating.

Creates a mismatch between expected reward flow (as hinted in the comment or UI) and actual smart contract logic.

Reduces game attractiveness and can break trust if off-chain frontends claim there's a payout.

Can be considered loss of user funds if users were expecting a return on getting dethroned.


Proof of Concept (PoC)

function test_NoPayoutToPreviousKing() public {
// player1 becomes king
vm.startPrank(player1);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
// player2 becomes new king
uint256 player1BalanceBefore = player1.balance;
vm.startPrank(player2);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
uint256 player1BalanceAfter = player1.balance;
// Assert no payout occurred to player1
assertEq(player1BalanceAfter, player1BalanceBefore);
}

Recommended Mitigation:

Implement logic to pay a small reward to the previous king

address previousKing = currentKing;
// Handle payout to previous king
if (previousKing != address(0)) {
uint256 rewardToPrevious = pot / 10; // 10% reward
pot -= rewardToPrevious; // Important: subtract reward from pot
(bool sent, ) = payable(previousKing).call{value: rewardToPrevious}("");
require(sent, "Reward payout failed");
}

Severity: High

  • Loss of incentive: Users may avoid participation once they realize there's no payout after losing the throne.

  • Trust issue: Off-chain users and UIs expecting a reward will find the protocol misleading.

  • Potential loss of funds: Players may assume they’ll get some reward and spend ETH accordingly.

  • Game disruption: The competitive loop of the game (claim → dethrone → reward) breaks, turning the game into a one-way ETH sink.

Updates

Appeal created

inallhonesty Lead Judge 9 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Missing Previous King Payout Functionality

Support

FAQs

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