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.
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.