SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Invalid claimed check

Root + Impact

Description

  • the claimed check depends on a immutable variable which has not been initialized

  • Instead of using the user-supplied treasureHash, it references an unrelated immutable variable _treasureHash

  • _treasureHash variable is not used anywhere, which means its value never changes. As a result, there are two possible scenarios:

    1. This check always fails, creating a risk of denial of service (DoS).

    2. This check always passes, creating a risk that the same treasure can be used repeatedly to claim rewards.

    In the second case, a user could find a single treasure and drain all rewards, instead of requiring all treasures to be found as intended

if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash);
_markClaimed(treasureHash);

Risk

Likelihood:

  • The vulnerability is highly likely to be triggered under normal usage conditions

Impact:

  • Incorrect claim validation

  • Allowing duplicate claims

  • once _treasureHash is marked as claimed ,All future claims will revert

Proof of Concept

function test_DrainFunds() public {
bytes memory fakeProof = "proof";
bytes32 treasure = keccak256("SAME");
for (uint i = 0; i < 10; i++) {
vm.prank(attacker);
hunt.claim(fakeProof, treasure, payable(address(uint160(i+1))));
}
}

Recommended Mitigation

- if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash);
+ - if (claimed[treasureHash]) revert AlreadyClaimed(treasureHash);
Updates

Lead Judging Commences

s3mvl4d Lead Judge 18 days ago
Submission Judgement Published
Validated
Assigned finding tags:

broken double-claim protection

In `claim()`, the guard uses `claimed[_treasureHash]`, where `_treasureHash` is an immutable state variable that is never initialized to the caller-supplied treasure identifier, while the contract later marks `claimed[treasureHash] = true` using the function argument instead. As a result, the duplicate-claim check and the state update are performed against different keys, which means a previously claimed treasure is not actually blocked from being claimed again with the same valid proof and `treasureHash`. This breaks a core invariant of the protocol described in the README, namely, that each treasure can only be redeemed once, and allows one valid treasure/proof pair to be reused to drain rewards repeatedly until either the `MAX_TREASURES` cap or the contract balance is exhausted.

Support

FAQs

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

Give us feedback!