In the claim function, the contract uses an uninitialized immutable variable _treasureHash to check whether a treasure has already been claimed:
Since _treasureHash is never assigned a value in the constructor, its default value is 0x0. However, when marking the claim status, the contract uses the treasureHash provided by the user:
This means that as long as the user-provided treasureHash is not 0x0, the claimed[_treasureHash] check (which always checks claimed[0x0]) will always return false. Consequently, a user can execute multiple claim calls using a single valid treasureHash until the pool is completely drained.
Likelihood: High
Any user who obtains a single valid proof can immediately exploit this vulnerability.
Impact: High
A user can use the same treasure and ZK proof to repeatedly call the claim function until all rewards in the contract are withdrawn.
Alice finds the first treasure.
Alice generates a ZK proof for her own address.
Alice calls claim(proof, hash1, aliceAddress). The contract checks claimed[0x0], finds it is false, and passes the check.
The contract marks claimed[hash1] as true and pays out 10 ETH.
Alice calls claim(proof, hash1, aliceAddress) again. The contract still checks claimed[0x0]; since 0x0 was never marked, the check passes again.
Alice repeats this operation 10 times until claimsCount reaches MAX_TREASURES or the balance is exhausted.
Remove the unnecessary state variable:
Since the state variable treasureHash is never initialized and used except in the function claim and results in misunderstandings easily, it should be removed.
modify the require check in function claim
Change the wrong check towards _treasureHash into that towards treasureHash provided by user.
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.