Last Man Standing

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

Small payout to previous king is not implemented - Need to add to logic to pay previous king a portion of the claim fee

Root + Impact

Description

  • In the current code, the new king doesn't pay out the previous king a portion of the claim fee, which is the desired behaviour of the game.

  • As a result, the previous king doesn't currently receive a small payout from the new king that claims the throne.

// There appropriate code has not been added to implement this feature
// The previousKingPayout remains unset
uint256 previousKingPayout = 0;

Risk

Likelihood:

  • The issue will occur during game play when a new throne is claimed.

  • Reason: Code to payout the previous king is not fully implemented.

Impact:

  • The full game with all its features is not able to be played according to the desired features of specification.

  • Previous kings do not receive a payout from the new king.


Proof of Concept

This Proof of Concept will describe how to add code to enable the feature to allow a portion of the claim fee to be given to the previous king (if a previous king exists).

  1. We will assign a desired percentage of the claim fee that must go to the new king and set this in the constructor as an additional parameter named _previousKingFeePercentage, and store it in a variable called previousKingFeePercentage which we will declare inside the Game class. If for example, we set previousKingFeePercentage to 10, it will mean the new king must pay 10 % of the claim fee to the previous king.

  2. In the claimThrone function we will check if there is a previous king. If there is a previous king we will calculate the fee to pay and store it in the variable previousKingPayout.

  3. We will payout the previous king by assigning the previousKingPayout to their winnings by means of adding to the pendingWinnings map for the previous king. These payout amounts can be withdrawn later using the withdrawWinnings function.

    Alternatively, a different approach might be to achieve a payout to a previous king by directly transfering ether to the previous king each time during the claimThrone function, but since other parts of the game allow a player to choose when to withdraw funds from the game, the approach was taken to rather save the amount by adding the previous king payouts to their pending winnings, which can be withdrawn.

  4. When calculating the amount to add to the pot we it will then need to be the sentAmount minus the currentPlatform fee, minus the previousKingPayout as both these amounts have been assigned elsewhere now and must be subtracted from what can be added to the pot.

The code below shows the source the new variable, for the constructor, and claimThrone function to make the new feature of paying out kings work properly, as well as an optional new event that is triggered when a payout is assigned to a previous king.

/* This code shows the key changes that must be made to the constructor, and claimThrone to make the new feature
* work as described in the specification. An optional event is also added to show that the
* previous king is paid out.
*/
/**
* @dev Initializes the game contract.
* @param _initialClaimFee The starting fee to claim the throne.
* @param _gracePeriod The initial grace period in seconds (e.g., 86400 for 24 hours).
* @param _feeIncreasePercentage The percentage increase for the claim fee (0-100).
* @param _platformFeePercentage The percentage of claim fee for the owner (0-100).
* @param _previousKingFeePercentage The percentage of claim fee to give to the previous king (0-100).
*/
constructor(
uint256 _initialClaimFee,
uint256 _gracePeriod,
uint256 _feeIncreasePercentage,
uint256 _platformFeePercentage,
uint256 _previousKingFeePercentage //R-Fix-Added
) Ownable(msg.sender) { // Set deployer as owner
require(_initialClaimFee > 0, "Game: Initial claim fee must be greater than zero.");
require(_gracePeriod > 0, "Game: Grace period must be greater than zero.");
require(_feeIncreasePercentage <= 100, "Game: Fee increase percentage must be 0-100.");
require(_platformFeePercentage <= 100, "Game: Platform fee percentage must be 0-100.");
require(_previousKingFeePercentage <= 100, "Game: Previous king fee percentage must be 0-100."); //R-Fix-Added
initialClaimFee = _initialClaimFee;
initialGracePeriod = _gracePeriod;
feeIncreasePercentage = _feeIncreasePercentage;
platformFeePercentage = _platformFeePercentage;
previousKingFeePercentage = _previousKingFeePercentage; //R-Fix-Added
// Initialize game state for the first round
claimFee = initialClaimFee;
gracePeriod = initialGracePeriod;
lastClaimTime = block.timestamp; // Game starts immediately upon deployment
gameRound = 1;
gameEnded = false;
// currentKing starts as address(0) until first claim
}
/*
* OPTIONAL - Event to show that a previous king was rewarded
* @dev Emitted when there was a previous king and they get rewarded part of the claim amount.
* @param previousKing The address of the previous king to be rewarded.
* @param claimAmount The ETH amount sent by the new king.
* @param previousKingPayout The ETH amount rewarded to the previous king
* @param timestamp The block timestamp when the claim occurred.
*/
event RewardPreviousKing(
address indexed previousKing,
uint256 claimAmount,
uint256 previousKingPayout,
uint256 timestamp
);
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."); //Fix-Current-King changed
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0; //R: Why is this a local variable - Possible-Issue: It appears this needs to be set and calculated before being used!
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
if (currentKing != address(0)) //If there is a previous king we must pay them a portion of the claim (sentAmount)
{
// Calculate amount to pay previous king based on a previousKingFeePercentage
// initally set in the constructor
previousKingPayout = (sentAmount * previousKingFeePercentage) / 100;
// Payout the previous king by adding to their winnings
pendingWinnings[currentKing] = pendingWinnings[currentKing] + previousKingPayout;
// Optional: Emit an event which is useful to see that the correct payout is rewarded to previous King
emit RewardPreviousKing(
currentKing, // address of soon to be previous king
sentAmount, // claim amount,
previousKingPayout, //previous king payout
block.timestamp // time stamp
);
}
// Calculate platform fee
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
// Defensive check to ensure platformFee doesn't exceed available amount after previousKingPayout
if (currentPlatformFee > (sentAmount - previousKingPayout)) { //if (currentPlatformFee > (sendAmountRemaining))
currentPlatformFee = sentAmount - previousKingPayout; // then set currentPlatformFee = sendAmountRemaining
}
platformFeesBalance = platformFeesBalance + currentPlatformFee;
// Remaining amount goes to the pot
amountToPot = sentAmount - currentPlatformFee - previousKingPayout;
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 using the actual current claim fee (sentAmount)
claimFee = sentAmount + (sentAmount * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender, //new king address
sentAmount, //amount sent by the new king
claimFee, //new claim free for the next claim
pot, //updated pot for the winner
block.timestamp
);
}

Recommended Mitigation

The Mitigation involves changing the code to allow a previous king to receive a portion of the claim fee from a new king.

+ uint256 public previousKingFeePercentage; // Percentage of the claimFee that the new king will pay previous king
constructor(
uint256 _initialClaimFee,
uint256 _gracePeriod,
uint256 _feeIncreasePercentage,
- uint256 _platformFeePercentage
+ uint256 _platformFeePercentage,
+ uint256 _previousKingFeePercentage
) Ownable(msg.sender) { // Set deployer as owner
require(_initialClaimFee > 0, "Game: Initial claim fee must be greater than zero.");
require(_gracePeriod > 0, "Game: Grace period must be greater than zero.");
require(_feeIncreasePercentage <= 100, "Game: Fee increase percentage must be 0-100.");
require(_platformFeePercentage <= 100, "Game: Platform fee percentage must be 0-100.");
+ require(_previousKingFeePercentage <= 100, "Game: Previous king fee percentage must be 0-100.");
initialClaimFee = _initialClaimFee;
initialGracePeriod = _gracePeriod;
feeIncreasePercentage = _feeIncreasePercentage;
platformFeePercentage = _platformFeePercentage;
+ previousKingFeePercentage = _previousKingFeePercentage;
// Initialize game state for the first round
claimFee = initialClaimFee;
gracePeriod = initialGracePeriod;
lastClaimTime = block.timestamp; // Game starts immediately upon deployment
gameRound = 1;
gameEnded = false;
// currentKing starts as address(0) until first claim
}
+ /*
+ * @dev Emitted when there was a previous king and they get rewarded part of the claim amount.
+ * @param previousKing The address of the previous king to be rewarded.
+ * @param claimAmount The ETH amount sent by the new king.
+ * @param previousKingPayout The ETH amount rewarded to the previous king
+ * @param timestamp The block timestamp when the claim occurred.
+ */
+ event RewardPreviousKing(
+ address indexed previousKing,
+ uint256 claimAmount,
+ uint256 previousKingPayout,
+ uint256 timestamp
+ );
/**
* @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;
+ //If there is a previous king we must pay them a portion of the claim (sentAmount)
+ if (currentKing != address(0))
+ {
+ // Calculate amount to pay previous king based on a previousKingFeePercentage set in the constructor
+ previousKingPayout = (sentAmount * previousKingFeePercentage) / 100; //R-Fix-Added
+
+ // Payout the previous king by adding to their winnings
+ pendingWinnings[currentKing] = pendingWinnings[currentKing] + previousKingPayout;
+ // Optional: Emit a event which is useful to see that the correct payout is paid to the previous king
+ emit RewardPreviousKing(
+ currentKing, // address of soon to be previous king
+ sentAmount, // claim amount,
+ previousKingPayout, //previous king payout
+ block.timestamp // time stamp
+ );
+ }
// 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;
+ /* Both the current platform fee and the previous king payout have been used from the sent amount.
+ * The amount that can be added to the pot is the sent amount after subtracting
+ * the platform fee and previous king payout, which have been assigned to others.
+ */
+ amountToPot = sentAmount - currentPlatformFee - previousKingPayout;
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;
+ /*
+ * sentAmount is the true current current claim amount and should be used to calculate
+ * the next claim fee minimum amount that must be paid when claiming the throne
+ */
+
+ claimFee = sentAmount + (sentAmount * feeIncreasePercentage) / 100;
emit ThroneClaimed(
msg.sender,
sentAmount,
claimFee,
pot,
block.timestamp
);
}
- remove this code
+ add this code
Updates

Appeal created

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