Last Man Standing

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

Missing Previous King Payout Logic in claimThrone Function

Description

The Last Man Standing game has a vulnerability in its claimThrone() function where the previous king payout logic is declared but never implemented. According to the function's documentation comment, a portion of the new claim fee should be sent to the previous king, but this functionality is completely missing from the implementation.

Expected Behavior

When a new player claims the throne, a portion of their claim fee should be sent to the previous king as a consolation prize, as described in the function's comment: "If there's a previous king, a small portion of the new claim fee is sent to them."

Actual Behavior

The variable previousKingPayout is declared and initialized to 0, but it's never updated or used to transfer any ETH to the previous king. Instead, the entire claim fee (minus the platform fee) goes directly to the pot.

Root Cause

The vulnerability exists in the claimThrone() function where the payout logic is missing:

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; // Initialized but never updated
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
);
}

The code declares previousKingPayout but never calculates or transfers any ETH to the previous king. Additionally, there are references to this variable in the defensive check for platform fees, suggesting that the payout logic was intended but not implemented.

Risk Assessment

Impact

The impact is medium because:

  1. It breaks a promised feature of the game (compensating previous kings)

  2. It changes the economic incentives for players

  3. It could reduce player engagement as there's no consolation prize for being dethroned

Likelihood

The likelihood is high as this issue affects every throne claim after the first one. It's not an edge case but a core feature that's completely missing.

Proof of Concept

The issue can be verified by examining the claimThrone() function in Game.sol. The variable previousKingPayout is declared but never assigned any value other than 0, and there's no logic to transfer ETH to the previous king.

Recommended Mitigation

Implement the missing previous king payout logic:

// Before: Vulnerable code
function claimThrone() external payable gameNotEnded nonReentrant {
// ... existing code ...
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0; // Never updated
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// Calculate platform fee
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// ... rest of function ...
}
// After: Fixed code
function claimThrone() external payable gameNotEnded nonReentrant {
// ... existing code ...
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// Calculate previous king payout (if there is a previous king)
if (currentKing != address(0)) {
// Pay 10% to previous king (or another appropriate percentage)
previousKingPayout = (sentAmount * 10) / 100;
// Transfer ETH to previous king
(bool success, ) = payable(currentKing).call{value: previousKingPayout}("");
require(success, "Game: Failed to pay previous king");
}
// Calculate platform fee
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// ... rest of function with adjusted calculations ...
// Remaining amount goes to the pot (adjusted for previousKingPayout)
amountToPot = sentAmount - currentPlatformFee - previousKingPayout;
pot = pot + amountToPot;
// ... rest of function ...
}

Explanation

The fix adds logic to:

  1. Calculate a payout for the previous king (e.g., 10% of the claim amount)

  2. Transfer this amount to the previous king's address

  3. Adjust the pot calculation to account for this payout

This ensures that previous kings receive a consolation prize as described in the function's documentation, creating a more balanced economic model for the game.

Updates

Appeal created

inallhonesty Lead Judge about 2 months 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.