Last Man Standing

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

Unrestricted Contract Participation in `claimThrone` .

Root + Impact

Description

The claimThrone function does not restrict contract addresses from participating. In time-sensitive or competitive games, this allows automated smart contracts and MEV bots to interact with the contract more efficiently than human users. As a result, bots can monitor mempool transactions, front-run claims, or snipe the throne instantly after state changes.

This undermines fair gameplay dynamics and introduces an imbalance between bots and real players.

Risk

Impact Medium.

  • Unrestricted contract participation can degrade user experience, reduce fairness, and allow bots to dominate gameplay—especially in time-sensitive conditions or when block timestamps are involved.

Likelihood High.

  • It is trivial for bots or other contracts to interact with the function since there is no on-chain restriction or verification of human interaction.

Proof of Concept

Any smart contract can call the claimThrone() function directly. For example:

contract BotThroneClaimer {
Game public game;
constructor(address gameAddress) {
game = Game(gameAddress);
}
function snipeThrone() external payable {
game.claimThrone{value: msg.value}();
}
}

This allows the attacker to deploy bots that monitor the mempool and instantly react to favorable state changes.

Recommendation Mitigation

Do not use tx.origin to block contracts — it is unreliable and creates compatibility issues with contract-based wallets.

Instead, consider one or more of the following mitigations:

Option 1: Human Verification via Off-Chain CAPTCHA

  • Users complete a CAPTCHA and receive a signed proof.

  • Smart contract accepts claims only with a valid off-chain signature.

function claimThrone(bytes calldata proof) external {
require(verifyHuman(msg.sender, proof), "Not verified human");
...
}

Option 2: Add On-Chain Cooldown Per Address

Introduce a time-based restriction to limit repeated or automated claims:

mapping(address => uint256) public lastClaim;
modifier rateLimited() {
require(block.timestamp > lastClaim[msg.sender] + 60, "Cooldown active");
_;
}
function claimThrone() external rateLimited {
lastClaim[msg.sender] = block.timestamp;
...
}

Updates

Appeal created

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
thesandf Submitter
about 2 months ago
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.