Description
The README documentation explicitly states that the current king "Receives a small payout from the next player's claimFee
(if applicable)" when dethroned. However, the contract implementation completely lacks this feature. The previousKingPayout
variable is hardcoded to 0 and never used, meaning previous kings receive no compensation when overthrown, contrary to the documented game mechanics.
Root Cause
The implementation shows a placeholder for the payout logic that was never completed:
function claimThrone() external payable gameNotEnded nonReentrant {
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
if (currentPlatformFee > (sentAmount - previousKingPayout)) {
currentPlatformFee = sentAmount - previousKingPayout;
}
}
Key issues:
previousKingPayout
is hardcoded to 0
No ETH transfer to the previous king
Defensive check becomes meaningless (always false)
Documentation promises a feature that doesn't exist
Risk
Likelihood: High - Every single throne claim fails to pay the previous king as documented.
Impact: Medium - Players join expecting payouts when dethroned but receive nothing, affecting game economics and player trust.
Impact
Medium severity because:
Proof of Concept
This test demonstrates that previous kings receive no payout when dethroned:
function test_MissingPreviousKingPayout() public {
vm.startPrank(deployer);
game = new Game(0.1 ether, 1 hours, 10, 3);
vm.stopPrank();
address player1 = address(0x1);
vm.deal(player1, 1 ether);
vm.prank(player1);
game.claimThrone{value: 0.1 ether}();
uint256 player1BalanceBefore = player1.balance;
assertEq(game.currentKing(), player1);
address player2 = address(0x2);
vm.deal(player2, 1 ether);
vm.prank(player2);
game.claimThrone{value: 0.11 ether}();
assertEq(player1.balance, player1BalanceBefore, "No payout received");
assertEq(game.currentKing(), player2, "Player2 is new king");
}
Recommended Mitigation
Implement the promised payout feature:
function claimThrone() external payable gameNotEnded nonReentrant {
require(msg.value >= claimFee, "Game: Insufficient ETH sent");
require(msg.sender != currentKing, "Game: Already king");
uint256 sentAmount = msg.value;
uint256 previousKingPayout = 0;
+ // Calculate payout to previous king (e.g., 10% of claim fee)
+ if (currentKing != address(0)) {
+ previousKingPayout = (claimFee * 10) / 100; // 10% of required fee
+
+ // Send payout to previous king
+ (bool success, ) = payable(currentKing).call{value: previousKingPayout}("");
+ require(success, "Failed to pay previous king");
+ }
uint256 currentPlatformFee = 0;
uint256 amountToPot = 0;
// Calculate platform fee on remaining amount
- currentPlatformFee = (sentAmount * platformFeePercentage) / 100;
+ currentPlatformFee = ((sentAmount - previousKingPayout) * platformFeePercentage) / 100;
- // Defensive check becomes meaningful
- if (currentPlatformFee > (sentAmount - previousKingPayout)) {
- currentPlatformFee = sentAmount - previousKingPayout;
- }
platformFeesBalance = platformFeesBalance + currentPlatformFee;
// Remaining goes to pot
- amountToPot = sentAmount - currentPlatformFee;
+ amountToPot = sentAmount - currentPlatformFee - previousKingPayout;
pot = pot + amountToPot;
// Update game state
currentKing = msg.sender;
// ... rest of function
}
Alternative: Update README to remove the false claim about king payouts if the feature is not intended.