Last Man Standing

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

Front-Running Vulnerability in Game Contract’s claimThrone Function Leading to Player Fund Loss

Root + Impact

Description


  • The game operates as a competitive "king of the hill" style contract where players claim the throne by paying an ETH fee via the claimThrone function.

  • After each successful claim, the claim fee increases, and the latest claimant becomes the current king.

  • The king at the end of the grace period wins the pot.

  • Issue: There is no protection against front-running attacks. A malicious player observing a pending claim transaction in the mempool can send a competing transaction with a higher value and higher gas price, thus becoming the new king before the original claimant.

  • This results in the original player losing their transaction gas fees without winning the throne or receiving any benefit.

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.");

Risk

Likelihood:


  • Any player monitoring the mempool can easily exploit this front-running vulnerability, especially when large rewards are at stake.

  • In networks like Ethereum, where gas price competition is common, the risk of front-running is high.

Impact:

  • Direct financial loss for the player whose transaction was front-run (gas fees spent with no benefit).

  • Player frustration and unfair gameplay experience due to losing the throne despite paying.

  • Potential damage to the game's reputation and reduced player participation.

Proof of Concept

contract FrontRunningLossTest is Test {
Game game;
address alice = address(0xAaA1);
address bob = address(0xBbb2);
function setUp() public {
game = new Game(1 ether, 60, 10, 5);
vm.deal(alice, 10 ether);
vm.deal(bob, 10 ether);
}
function test_FrontRunningLoss() public {
vm.startPrank(alice);
game.claimThrone{value: 1 ether}();
vm.stopPrank();
vm.startPrank(bob);
game.claimThrone{value: game.claimFee()}();
vm.stopPrank();
assertEq(game.currentKing(), bob);
}
}

Recommended Mitigation

- 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.");
- // No front-running protection present.
- }
+ // Possible mitigation approaches:
+ // 1. Use a commit-reveal scheme, where players first commit their claim secretly, then reveal it later.
+ // 2. Implement an auction mechanism to handle claims fairly.
+ // 3. Add a delay or locking period after each claim to prevent instant front-running.
+ // 4. Batch multiple claims or handle competing claims within the same block with custom logic.
Updates

Appeal created

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

MEV/Frontrunning/Sniping

Support

FAQs

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