The contract tracks claimed treasures via mapping(bytes32 => bool) public claimed. When a participant submits a valid proof, _markClaimed(treasureHash) writes claimed[treasureHash] = true using the caller-supplied treasureHash parameter.
However, the duplicate-claim guard on line 88 reads from claimed[_treasureHash] — the immutable storage variable — which is declared but never assigned in the constructor, meaning it is permanently bytes32(0).
These two keys are completely independent. Unless a claimant submits treasureHash == bytes32(0), the guard claimed[_treasureHash] always evaluates to false, and the same treasure can be claimed again on every subsequent call.
Likelihood:
Any participant who obtains one valid proof for one treasure can re-submit it with the same treasureHash on every call; the guard never fires because the read key (_treasureHash) and the write key (treasureHash param) are always different.
No special tooling is needed — a simple loop of claim() calls suffices.
Impact:
A single valid proof drains the entire contract balance in up to 10 sequential transactions (bounded only by MAX_TREASURES), defrauding all other treasure finders of their rewards.
Legitimate treasures are never recorded as claimed, so the hunt's state is completely corrupted.
Attacker has one valid proof for treasureHash = X.
claimsCount starts at 0, contract holds 100 ETH.
Replace _treasureHash (the immutable) with treasureHash (the parameter) in the duplicate-claim guard, and remove the unused immutable variable:
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.