Last Man Standing

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

Payout to Previous King is Not Implemented

Intended Payout to the Previous King in claimThrone is Not Implemented, Breaking a Core Economic Incentive

Description

  • Normal Behavior: The code includes comments and a variable (previousKingPayout) that strongly suggest an intention to pay a portion of the claimFee to the king who was just dethroned. The comment in the function description reads: "If there's a previous king, a small portion of the new claim fee is sent to them."

  • The Issue: This logic is completely absent from the implementation. The variable previousKingPayout is initialized to 0 and is never updated or used to send funds. The entire claimFee (less platform fees) goes directly to the pot.

/**
* @dev Allows a player to claim the throne by sending the required claim fee.
* If there's a previous king, a small portion of the new claim fee is sent to them.
* A portion also goes to the platform owner, and the rest adds to the pot.
*/
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;
//@> No logic for previous king
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}

Risk

Likelihood: HIGH

  • The payout logic is missing, so it will never occur.

Impact: MEDIUM

  • This is a failure to implement a specified feature that is part of the game's core economic design.

  • Not rewarding the previous king reduces the incentive to participate and hold the throne, potentially leading to lower engagement than designed.

  • It misrepresents the game's mechanics to anyone reading the code comments or documentation.

Proof of Concept

  • Have account_A become the currentKing.

  • Have account_B call claimThrone() to become the new king.

  • Observe: Check the ETH balance of account_A. It has not increased. All funds from account_B's claim have gone to the platformFeesBalance and the pot. The previousKingPayout logic was not executed because it doesn't exist.

Add this code to the test file.

function testPreviousKingPayout() public {
vm.prank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.startPrank(player2);
game.claimThrone{value: INITIAL_CLAIM_FEE * 2}();
assertEq(uint256(game.pendingWinnings(player1)), 0);
}

Recommended Mitigation

Implement the payout logic. This requires adding a new configuration variable and modifying the claimThrone function.

  1. Add a new state variable for the payout percentage:

    + uint256 public previousKingPayoutPercentage; // e.g., 5 for 5%
  2. Update the constructor and add a setter function for this new variable.

  3. Refactor claimThrone to calculate and send the payout:

    Solidity

    function claimThrone() external payable gameNotEnded nonReentrant {
    // ... (existing require checks)
    // Store previous king before changing state
    + address previousKing = currentKing;
    uint256 feeForDistribution = claimFee; // Use claimFee, not msg.value
    uint256 amountToPreviousKing = 0;
    uint256 amountToPlatform = (feeForDistribution * platformFeePercentage) / 100;
    // Only pay previous king if one exists
    + if (previousKing != address(0)) {
    + amountToPreviousKing = (feeForDistribution * previousKingPayoutPercentage) / 100;
    + // Ensure payouts don't exceed the fee
    + if (amountToPreviousKing + amountToPlatform > feeForDistribution) {
    + // Handle edge case, e.g., prioritize platform fee
    + amountToPreviousKing = feeForDistribution - amountToPlatform;
    + }
    + pendingWinnings[previousKing] += amountToPreviousKing; // Use a pull pattern for safety
    + }
    + uint256 amountToPot = feeForDistribution - amountToPlatform - amountToPreviousKing;
    + platformFeesBalance += amountToPlatform;
    + pot += amountToPot;
    // ... (update king, timestamp, claimFee, etc.)
    // ...
    }
Updates

Appeal created

inallhonesty Lead Judge 4 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.

Give us feedback!