e TreasureHunt::withdraw function allows the owner to recover any remaining ETH once the hunt is finished. The completion criteria is defined
as claimsCount >= MAX_TREASURES (where MAX_TREASURES is 10)
The issue is a logical deadlock caused by the duplicate hash in the Noir circuit (Finding [H-01]). Since the circuit only allows 9 unique
treasures to be proved, and the contract (assuming the double-spend bug is fixed) only allows each hash to be claimed once, the claimsCount can
never exceed 9. Consequently, the require statement in withdraw will always fail
Likelihood:
This is a guaranteed outcome if the Noir circuit bug is not fixed, as the claimsCount state variable is strictly incremented per successful
claim
Impact:
Locked Funds: The owner will be unable to retrieve the remaining 10 ETH (allocated for the 10th treasure) or any excess funding. This results
in a permanent loss of funds for the organizer
This test simulates the end-state of the hunt.
We simulate 9 unique successful claims (the maximum possible unique claims).
We verify claimsCount is 9 and 10 ETH remains.
We call withdraw() as the owner and confirm it reverts with "HUNT_NOT_OVER", proving that the owner can never recover the final 10 ETH even
after all possible treasures are found.
The primary mitigation is to fix the Noir circuit bug (Finding [H-01]). Additionally, it is recommended to add a time-based recovery mechanism
(e.g., block.timestamp > huntEndTime) to the withdraw function so the owner can recover funds if some treasures are never found physically.
The issue stems from a mismatch between the circuit and the contract’s economic assumptions: the Solidity contract is configured for `MAX_TREASURES = 10` and only allows the owner to call `withdraw()` once `claimsCount >= MAX_TREASURES`, while the Noir circuit’s baked-in `ALLOWED_TREASURE_HASHES` array does not actually contain ten distinct treasures because one hash is duplicated and another expected hash is missing. As a result, under the intended one-claim-per-treasure design described in the README, there are only nine uniquely claimable treasures even though the system is funded and accounted as if ten rewards can be legitimately redeemed. That creates two linked consequences from the same root cause: first, one treasure is effectively unclaimable because no valid proof can ever be generated for the missing allowed hash, and second, the normal “hunt over” withdrawal path becomes bricked because honest participants can never reach ten legitimate unique claims, leaving the post-hunt fund recovery logic via `withdraw` function permanently unreachable. The owner can still intervene through the emergency path.
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.