Location: contracts/src/TreasureHunt.sol:35, 83, 88, 104
The TreasureHunt.claim function is supposed to stop a user from claiming the same treasure twice, but the duplicate check reads the wrong storage key. It reads claimed[_treasureHash], where _treasureHash is an immutable that is never assigned in the constructor, so its value is always the type default bytes32(0).
Every call reads claimed[bytes32(0)], which is always false. The "already claimed" guard never triggers. Meanwhile, _markClaimed(treasureHash) correctly writes the real hash into the mapping, but that slot is never consulted during claim.
Likelihood: High. Anyone with a single valid proof (i.e. any treasure finder) can trigger this. There is no edge case, no race condition, no precondition beyond having one valid claim tuple.
Impact: High . A participant with one valid proof can call claim up to MAX_TREASURES = 10 times with the same (proof, treasureHash, recipient). Each call sends REWARD = 10 ether to the recipient. One valid proof therefore drains the full 100 ETH prize pool to a single address. The rest of the finders get nothing.
Put the following test :
Run:
Expected output:
Use the function parameter treasureHash in the duplicate check. Delete the unused _treasureHash immutable.
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.