SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

[H-03] Logic error in claim() prevents any sucessful reward distribution

Description

  • The claim function is intended to mark a specific treasureHash as claimed in the contract's storage to prevent it from being reused.

  • The contract accidentally attempts to update a global state variable _treasureHash (which is always empty/zero) instead of the actual treasureHash passed as an argument. Because the contract then checks if the provided hash is claimed, the logic fails to track the correct treasures, eventually bricking the claim process for valid users.

Solidity

function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant() {
// ...
@> claimed[_treasureHash] = true; // Root cause: Updates the wrong variable (global instead of local)
}

Risk

Likelihood:

  • This bug triggers every single time a user calls the claim function, making it a 100% certainty upon the first interaction.

Impact:

  • Contract Inoperability: The mapping fails to record the actual treasures found, leading to inconsistent state and preventing users from successfully completing the hunt logic as intended.

Proof of Concept

Solidity

function test_WrongVariableUpdated() public {
bytes32 realHash = keccak256("treasure_1");
hunt.claim(mockProof, realHash, payable(user));
// The mapping for the actual hash remains false
assertEq(hunt.isClaimed(realHash), false);
// The empty global variable was updated instead
assertTrue(hunt.isClaimed(bytes32(0)));
}

Recommended Mitigation

Diff

- claimed[_treasureHash] = true;
+ claimed[treasureHash] = true;
Updates

Lead Judging Commences

s3mvl4d Lead Judge 18 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

state corruption

The bug in `claim()` does not corrupt contract state or brick future claims; it simply checks the wrong mapping key when enforcing uniqueness. The problematic line reads `if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash);`, where `_treasureHash` is a separate immutable state variable, while the actual state update later uses the caller-supplied argument through `_markClaimed(treasureHash)`, which sets `claimed[treasureHash] = true`. Since the read and the write are performed against different keys, the effect is a broken duplicate-claim guard, not a destructive state transition. After one treasure is claimed, the mapping entry for that specific `treasureHash` is updated correctly, and other participants can still claim different treasures because the contract has not globally invalidated the claimed mapping or otherwise poisoned shared state. The real consequence of the bug is that previously claimed treasures are not properly blocked from being claimed again, not that the contract becomes unusable for everyone else.

Support

FAQs

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

Give us feedback!