TreasureHunt.claim() is designed to mark each treasure as claimed after the first successful proof submission, preventing a valid proof from being reused to collect multiple rewards.
The replay-protection check reads from claimed[_treasureHash] where _treasureHash is a bytes32 immutable declared at the contract level but never assigned in the constructor, so its value is always bytes32(0). The write that follows the check correctly targets claimed[treasureHash] (the function parameter). Because the guard reads from a permanently-false key, it never reverts and the same proof can be submitted repeatedly.
Likelihood:
A participant who has generated one valid ZK proof immediately has the ability to repeat the call, with no special conditions required beyond a valid proof.
The test suite already demonstrates the bug: testClaimDoubleSpendReverts has its vm.expectRevert() commented out, confirming the second claim succeeds.
Impact:
A single valid proof can drain the entire 100 ETH contract balance by calling claim() ten times before AllTreasuresClaimed fires.
All other treasure finders are denied their rewards as the balance is exhausted.
The existing test in TreasureHunt.t.sol directly demonstrates the vulnerability. The vm.expectRevert() line that should catch the second claim has been commented out, confirming that the duplicate call succeeds. An attacker who has obtained a single valid proof can loop this call up to ten times, collecting 10 ETH per iteration and fully draining the 100 ETH prize pool before any other participant can collect their reward.
The root cause is a one-character typo: the replay guard reads from _treasureHash (the unassigned immutable, always bytes32(0)) instead of treasureHash (the caller-supplied parameter). Fixing the variable name in the guard expression closes the attack vector with a minimal, low-risk change. The immutable declaration should also be removed to eliminate the confusingly-named dead variable that enabled the bug.
Also remove the unused declaration:
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.
The contest is complete and the rewards are being distributed.