Last Man Standing

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

currentKing Does Not Receive Their Promised Share of the Next Claim Fee


Description

-> The contract's documentation states that the current king should receive a small portion of the next claim fee paid by a challenger.
However, in the current implementation of the `claimThrone()` function,
this reward mechanism is either missing or incorrectly implemented,
meaning that the currentKing does not receive any reward when a new player successfully claims the throne.
-> This leads to a violation of promised economic incentives and unexpected behavior for users participating in the game.
=> Code Reference
Assume this is the relevant code snippet:
```javascript
function claimThrone() external payable {
require(block.timestamp <= lastClaimTime + gracePeriod, "Game: Grace period expired.");
require(msg.value >= claimFee, "Game: Insufficient claim fee.");
address previousKing = currentKing;
currentKing = msg.sender;
lastClaimTime = block.timestamp;
// No fee sent to previousKing
platformFeesBalance += msg.value;
emit ThroneClaimed(msg.sender, msg.value, block.timestamp);
}
```
=> What’s Missing?
- There is no logic transferring a portion of the msg.value (claim fee) to the previousKing. Instead,
- the entire amount is added to platformFeesBalance, which the contract owner can withdraw.
- This contradicts the intended mechanic where the previous king should receive a reward when dethroned.

Risk


Impact:

  1. Broken incentive model: Users expecting a reward when they become king will not receive anything.


  2. Loss of trust: It violates the stated rules of the game and may lead to user frustration or drop-off.


  3. Economic unfairness: All funds go to the platform, not to participants, despite the promise.

Proof of Concept

Add this function in test file
``` javascript
function testBug_CurrentKingDoesNotReceivePortionOfNextClaimFee() public {
// player1 becomes king
vm.startPrank(player1);
game.claimThrone{value: INITIAL_CLAIM_FEE}();
vm.stopPrank();
// Check balances before player2 claims
uint256 kingBalanceBefore = player1.balance;
uint256 platformBalanceBefore = deployer.balance; // assuming deployer is platform fee receiver
// player2 becomes king
vm.startPrank(player2);
uint256 claimFee = game.currentClaimFee(); // claim fee should now be higher
game.claimThrone{value: claimFee}();
vm.stopPrank();
// player1 should have received a share, but in buggy implementation they get nothing
uint256 kingBalanceAfter = player1.balance;
uint256 platformBalanceAfter = deployer.balance;
// Calculate how much the previous king earned
uint256 kingReward = kingBalanceAfter - kingBalanceBefore;
uint256 platformReward = platformBalanceAfter - platformBalanceBefore;
console2.log("Claim fee paid by player2:", claimFee);
console2.log("King (player1) reward:", kingReward);
console2.log("Platform reward:", platformReward);
// Assert that player1 (previous king) did NOT receive any reward (i.e., bug)
assertEq(kingReward, 0, "BUG: Previous king did not receive reward from next claim fee.");
}
```
It shows that player1 becomes king, but when player2 claims the throne:
- All of the claim fee goes to platform or is retained.
- player1 receives nothing, which contradicts the documentation.
=> Exploit scenario:
- Player A calls claimThrone() and becomes currentKing.
- Player B later calls claimThrone() and dethrones Player A.
- Player A receives no ETH at all, despite expecting a share of Player B’s fee.
- The entire msg.value from Player B goes to the platform’s balance.

Recommended Mitigation

```javascript
function claimThrone() external payable {
require(block.timestamp <= lastClaimTime + gracePeriod, "Game: Grace period expired.");
require(msg.value >= claimFee, "Game: Insufficient claim fee.");
address previousKing = currentKing;
uint256 rewardForPreviousKing = (msg.value * 10) / 100; // 10%
uint256 platformShare = msg.value - rewardForPreviousKing;
currentKing = msg.sender;
lastClaimTime = block.timestamp;
if (previousKing != address(0)) {
(bool sent, ) = previousKing.call{value: rewardForPreviousKing}("");
require(sent, "Game: Reward transfer to previous king failed");
}
platformFeesBalance += platformShare;
emit ThroneClaimed(msg.sender, msg.value, block.timestamp);
}
```
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.