SNARKeling Treasure Hunt

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

Incorrect storage variable used for claim validation

Author Revealed upon completion

Root + Impact

Description

The contract is intended to track unique treasure claims by mapping a bytes32 treasureHash to a boolean. This ensures each reward is only paid out once per unique discovery.

The claim function checks the state of _treasureHash (an uninitialized immutable defaulting to 0x0) instead of the treasureHash provided in the function arguments. Consequently, the check claimed[_treasureHash] will always return false (until someone claims hash 0x0), allowing users to potentially replay proofs for the same treasure or bypass the uniqueness constraint entirely.

// Root cause in the codebase
function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant() {
// ... logic ...
@> if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash);
// ... logic ...
_markClaimed(treasureHash);
}

Risk

Likelihood: High

  • The bug is hard-coded into the primary entry point of the contract.

  • Any valid proof submitted will pass this check regardless of whether that specific treasureHash was used before.

Impact: High

  • Double Spending: A user can claim the same treasure multiple times until the contract balance is depleted or MAX_TREASURES is reached.

  • Loss of Funds: The 100 ETH pool can be drained by a single valid discovery.

Proof of Concept

Recommended Mitigation

Remove the uninitialized immutable variable and validate the actual treasureHash provided by the caller.

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

Support

FAQs

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

Give us feedback!