Wrong variable referenced in AlreadyClaimed check + total reward pool drain
The claim() function is designed to prevent the same treasure from being claimed twice by checking the claimed mapping against a treasure hash. Once a treasure is claimed, subsequent claims for that hash should revert with AlreadyClaimed.
The AlreadyClaimed check queries the immutable state variable _treasureHash (which is never assigned in the constructor and defaults to bytes32(0)) instead of the function parameter treasureHash. Meanwhile, _markClaimed() correctly marks the function parameter treasureHash as claimed. This mismatch means the double-spend check always queries a different key than the one that was set, so it never detects a previously claimed treasure.
Likelihood:
A participant with a single valid ZK proof for any treasure calls claim() repeatedly up to 10 times (until claimsCount >= MAX_TREASURES), since the AlreadyClaimed guard never triggers
The ZK proof remains valid for the same treasureHash + recipient combination on every call, so no new proof generation is needed
Impact:
A single participant drains the entire 100 ETH reward pool by repeatedly claiming the same treasure
9 other legitimate treasure finders receive no reward
The integrity of the treasure hunt is completely broken
Note: The existing test testClaimDoubleSpendReverts at line 134 has vm.expectRevert() commented out, confirming this behavior was observed but not fixed.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.