Normal behavior: Each treasureHash should be claimable at most once. The claimed mapping should block a second successful claim for the same hash after a valid proof pays out once.
Problem: The replay check reads claimed[_treasureHash] where _treasureHash is an immutable that is never set in the constructor, while _markClaimed writes claimed[treasureHash]. The guard slot never tracks real claims, so the same valid proof and hash can pay out repeatedly until claimsCount reaches MAX_TREASURES.
Likelihood:
A participant with one valid proof and matching public inputs calls claim in a loop until the contract balance is exhausted or claimsCount hits the cap.
The mistaken guard slot stays false for user-supplied hashes, so every repeat passes the replay check after proof verification succeeds.
Impact:
Up to MAX_TREASURES * REWARD ETH can be sent to the same recipient using one proof and one hash.
Honest hunters lose intended per-treasure fairness because one hash can consume the full claim budget.
The replay guard reads claimed[_treasureHash] (immutable never set) while _markClaimed sets claimed[treasureHash], so the guard slot never flips for a real treasureHash. The Foundry test below loads a valid fixture proof once, then calls claim ten times with the same proof, treasureHash, and recipient. Prerequisites: from the contest repo root, run circuits/scripts/build.sh once so contracts/test/fixtures/proof.bin and public_inputs.json exist. How to run: forge test --match-test testPoC_RepeatedClaimDrainsAllRewards -vv. Expected result: test passes; claimsCount reaches 10, contract balance goes to zero, recipient receives 10 * REWARD.
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.