Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

Missing Per-Round Claim Tracking in Game.sol

Root + Impact

Description

Currently, the game contract tracks only the total number of throne claims across all game rounds using totalClaims.

However, there is no visibility into how many claims occurred in a specific round, which limits fine-grained analytics, round-based reward logic, or insights into game engagement over time.

// Current problematic implementation
uint256 public totalClaims; // Total number of throne claims across all rounds
function claimThrone() external payable gameNotEnded nonReentrant {
// ... claim logic
totalClaims = totalClaims + 1; // @> Increments across all rounds
// ... rest of function
}
function resetGame() external onlyOwner gameEndedOnly {
// ... reset logic
gameRound = gameRound + 1;
// @> totalClaims is NOT reset, continues accumulating
emit GameReset(gameRound, block.timestamp);
}

Risk

Likelihood:

  • Occurs every time the game is reset and a new round begins, as the totalClaims counter continues accumulating from previous rounds.

Impact:

  • Loss of historical analytics - unable to determine which rounds were most popular or competitive

  • Misleading statistics for current round performance (shows cumulative claims instead of round-specific claims)

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Game.sol";
contract GameRoundClaimCountTest is Test {
Game public game;
address alice = address(0x1);
address bob = address(0x2);
address charlie = address(0x3);
function setUp() public {
game = new Game(1 ether, 24 hours, 10, 5);
// Fund all addresses with enough ETH
vm.deal(alice, 10 ether);
vm.deal(bob, 10 ether);
vm.deal(charlie, 10 ether);
}
function testInaccurateRoundAnalytics() public {
// Round 1: 3 claims
vm.prank(alice);
game.claimThrone{value: 1 ether}();
vm.prank(bob);
game.claimThrone{value: 1.1 ether}();
vm.prank(charlie);
game.claimThrone{value: 1.21 ether}();
// Fast forward past grace period and declare winner
vm.warp(block.timestamp + 25 hours);
game.declareWinner();
uint256 round1Claims = game.totalClaims(); // Should be 3
assertEq(round1Claims, 3);
// Reset to Round 2
vm.prank(game.owner());
game.resetGame();
// Round 2: 2 claims
vm.prank(alice);
game.claimThrone{value: 1 ether}();
vm.prank(bob);
game.claimThrone{value: 1.1 ether}();
// BUG PROOF: Cannot distinguish round 2 claims from total
uint256 totalAfterRound2 = game.totalClaims(); // Shows 5 (3+2)
// PROBLEM: No way to get round 2 claims specifically
// Expected: Round 2 should show 2 claims
// Actual: Shows 5 claims (misleading)
assertEq(totalAfterRound2, 5); // Proves the bug
console.log("Total claims (should be 2 for current round):", totalAfterRound2);
console.log("Cannot determine Round 2 performance independently");
console.log("Current round:", game.gameRound());
console.log("But totalClaims shows cumulative across all rounds");
}
}

POC explanation:

This test proves that the contract can't track claims per individual round:

What it does:

  1. Round 1: Alice, Bob, and Charlie each claim the throne (3 total claims)

  2. End Round 1: Game ends and gets reset to start Round 2

  3. Round 2: Alice and Bob claim the throne (2 claims in this round)

The Bug it proves:

  • After Round 2, totalClaims shows 5 (3+2 from both rounds combined)

  • But there's no way to see that Round 2 only had 2 claims

  • The contract can't tell you "how active was Round 2 specifically?"

Recommended Mitigation

@@ Add to state variables:
+ mapping(uint256 => uint256) public roundClaimCount;
@@ Inside claimThrone():
totalClaims = totalClaims + 1;
+ roundClaimCount[gameRound] = roundClaimCount[gameRound] + 1;
  • This introduces a roundClaimCount[gameRound] counter, which increments alongside totalClaims , to track the total claims per round.

Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.