The normal behavior is one reward payout per unique treasureHash. After a successful claim, the same hash must be blocked forever.
The issue is that claim() checks claimed[_treasureHash] where _treasureHash is an uninitialized immutable (bytes32(0)), while the write path marks claimed[treasureHash]. Because the read and write keys differ, the same valid proof/hash can be replayed until MAX_TREASURES is reached.
Likelihood:
Any user with one valid proof can call claim() repeatedly with the same treasureHash and different recipients.
The exploit path is direct and requires no privileged role.
Impact:
Full 100 ETH pool can be drained using one unique treasure proof.
Legitimate winners are blocked once claimsCount reaches MAX_TREASURES.
Standalone reproduction:
Deploy TreasureHunt with 100 ether.
Use a verifier that returns true for a valid (proof, treasureHash, recipient) tuple.
Call claim(proof, HASH_1, recipient1) once.
Call claim(proof, HASH_1, recipient2) again with the same proof and same HASH_1.
Repeat until 10 successful claims. Contract balance reaches zero while only one unique hash was used.
Use the function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient).
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.