claim() is supposed to prevent the same treasure from being claimed twice by checking the claimed mapping before processing a submission. After a successful claim, \_markClaimed(treasureHash) correctly writes claimed\[treasureHash] = true using the caller-supplied parameter.
However, the duplicate-claim guard reads from claimed[_treasureHash] instead — where _treasureHash is a private immutable state variable that is never initialized in the constructor, making it permanently bytes32(0). The guard therefore always evaluates to false, and the same treasure can be claimed repeatedly until the contract is drained.
Likelihood:
Every valid proof submission succeeds regardless of whether the treasure was already claimed, because the guard is permanently bypassed.
No special attacker capability is required beyond possessing one valid proof.
Impact:
An attacker with a single valid proof can call claim() repeatedly, draining the full contract balance (up to 100 ETH) in REWARD-sized (10 ETH) increments.
All other treasure finders are deprived of their rewards once the contract balance falls below REWARD.
The root cause is that _treasureHash is an immutable variable declared at the contract level but never assigned in the constructor — Solidity leaves it as bytes32(0). Meanwhile, _markClaimed correctly writes to claimed[treasureHash] (the function parameter), so the mapping entry for the real treasure hash does get set to true after the first claim. The guard, however, never reads that entry: it always checks claimed[bytes32(0)], which remains false forever. This means every subsequent call with the same proof passes the guard and proceeds to transfer another REWARD.
Remove the unused immutable variable entirely:
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.