claim() checks _treasureHash (uninitialized immutable) instead of treasureHash (parameter), breaking the double-claim guard and allowing full fund drainThe claim() function is supposed to prevent the same treasure from being claimed twice. But at line 88, it checks the wrong variable:
_treasureHash here is the immutable storage variable declared on line 35, which is never assigned anywhere in the constructor. So it defaults to bytes32(0). The function then marks the correct parameter on line 104 via _markClaimed(treasureHash), but since the check and the mark operate on different keys, the guard never actually blocks a repeat claim.
Basically: the check reads claimed[bytes32(0)] (always false), while the mark writes claimed[actualHash] (irrelevant to the check). So any valid proof can be replayed over and over.
The only thing that stops it eventually is claimsCount >= MAX_TREASURES on line 87, but by that point an attacker has already called claim() 10 times with one proof and drained all 100 ETH.
Likelihood: This will happen every time someone claims a treasure. There's no special condition needed — the bug is in the normal execution path. Any participant who has one valid proof can just call claim() in a loop.
Impact: Complete loss of contract funds (100 ETH). A single participant with one valid ZK proof drains the entire reward pool, leaving nothing for the other 9 treasure finders.
Note: the existing test testClaimDoubleSpendReverts in the repo has vm.expectRevert() commented out on line 144, which masks this bug — the second claim silently succeeds.
Also consider removing the unused _treasureHash immutable variable entirely to avoid confusion.
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.