Last Man Standing

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

[H-4] Grace Period Can Be Bypassed Indefinitely Throne Claims — DoS Vulnerability in declareWinner

Root + Impact

[H-4] Grace Period Can Be Bypassed Indefinitely Throne Claims — DoS Vulnerability in declareWinner

Description

The declareWinner() function checks if block.timestamp > lastClaimTime + gracePeriod to determine
if the game should end.However, since claimThrone() updates lastClaimTime every time a new user claims the throne,
any actor can continuously re-claim the throne right before the gracePeriod expires, indefinitely resetting the timer.

This creates a denial-of-service vector where a malicious actor (or bot) can grief the game and prevent the winner from ever being declared, unless the attacker stops or runs out of gas/ETH.

Impact:

1.The declareWinner() function can be permanently blocked by malicious users.

2.Honest players can never win the game despite waiting through the grace period.

3.The entire game can be held hostage by a spammer.

Proof of Concept

1.Bob calls claim throne-lastclaimtime=1

2.alice calls claim throne-lastclaimtime=36001

3.malicious actor calls claimthrone-lastclaimtime=72001

4.Since declareWinner() requires current time > lastClaimTime + TIMEOUT, the game can be
indefinitely stalled by malicious actors.

function test_dos() public {
vm.startPrank(bob);
game.claimThrone{value: 1 ether}();
vm.stopPrank();
vm.warp(36000);
vm.startPrank(alice);
game.claimThrone{value: 1.1 ether}();
vm.stopPrank();
vm.warp(36000);
vm.startPrank(maliciousActor);
game.claimThrone{value: 1.5 ether}();
vm.stopPrank();
vm.expectRevert();
game.declareWinner();
}
Traces:
[302401] GameTest::test_dos()
├─ [0] VM::startPrank(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e])
│ └─ ← [Return]
├─ [152621] Game::claimThrone{value: 1000000000000000000}()
│ ├─ emit ThroneClaimed(newKing: bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], claimAmount: 1000000000000000000 [1e18], newClaimFee: 1010000000000000000 [1.01e18], newPot: 990000000000000000 [9.9e17], timestamp: 1)
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::warp(36000 [3.6e4])
│ └─ ← [Return]
├─ [0] VM::startPrank(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ └─ ← [Return]
├─ [52921] Game::claimThrone{value: 1100000000000000000}()
│ ├─ emit ThroneClaimed(newKing: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], claimAmount: 1100000000000000000 [1.1e18], newClaimFee: 1020100000000000000 [1.02e18], newPot: 2079000000000000000 [2.079e18], timestamp: 36000 [3.6e4])
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::warp(36000 [3.6e4])
│ └─ ← [Return]
├─ [0] VM::startPrank(maliciousActor: [0x195Ef46F233F37FF15b37c022c293753Dc04A8C3])
│ └─ ← [Return]
├─ [50121] Game::claimThrone{value: 1500000000000000000}()
│ ├─ emit ThroneClaimed(newKing: maliciousActor: [0x195Ef46F233F37FF15b37c022c293753Dc04A8C3], claimAmount: 1500000000000000000 [1.5e18], newClaimFee: 1030301000000000000 [1.03e18], newPot: 3564000000000000000 [3.564e18], timestamp: 36000 [3.6e4])
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error 0xf4844814)
│ └─ ← [Return]
├─ [6786] Game::declareWinner()
│ ├─ [0] console::log("in contract:", 36000 [3.6e4]) [staticcall]
│ │ └─ ← [Stop]
│ └─ ← [Revert] revert: Game: Grace period has not expired yet.
└─ ← [Stop]

Recommended Mitigation

Lock the throne after grace period starts:

function claimThrone() external payable gameNotEnded nonReentrant {
+require(block.timestamp <=+ gracePeriod, "Game: Grace period started, no more claims allowed.");
...
}
Updates

Appeal created

inallhonesty Lead Judge 15 days ago
Submission Judgement Published
Validated
Assigned finding tags:

declareWinner time check is not properly done

wolf_kalp Submitter
15 days ago
inallhonesty Lead Judge
12 days ago
inallhonesty Lead Judge 11 days ago
Submission Judgement Published
Validated
Assigned finding tags:

declareWinner time check is not properly done

Support

FAQs

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