Normally, claim() enforces single-use of each treasure hash: once a valid proof has been redeemed for a given treasureHash, the double-spend check claimed[...] should revert any subsequent submission of the same (proof, treasureHash, recipient) triple.
In TreasureHunt.sol, the double-spend check reads an uninitialized immutable _treasureHash (always bytes32(0)) instead of the function argument treasureHash. As a result the check always reads claimed[bytes32(0)], which is never written to, so the same valid proof can be replayed up to MAX_TREASURES (10) times and drain the full 100 ETH reward pool.
Likelihood:
Triggers every time an attacker holds a single valid (proof, treasureHash, recipient) triple and submits it more than once — no special state, no race condition, no privileged role is required.
The existing test testClaimDoubleSpendReverts in the repository already has its vm.expectRevert() commented out, showing that double-spend observably succeeds in the current codebase.
Impact:
A single valid proof drains 100 % of the reward pool (100 ETH by default) because claim() can be called 10 times with the exact same inputs.
Legitimate participants who physically find treasures 2..10 receive nothing, since the contract balance is already depleted.
contracts/test/PoC_C1_DoubleSpend.t.sol runs with forge test --match-contract PoC_C1_DoubleSpend -vv and demonstrates that a single proof drains the full 100 ETH balance.
The root cause is that the check on L88 references the wrong variable: the immutable _treasureHash (always bytes32(0)) instead of the function parameter treasureHash. One might consider fixing this by initializing _treasureHash in the constructor, but this would not resolve the bug — the contract is designed to track MAX_TREASURES = 10 independent claim flags via the claimed mapping, so a single immutable value cannot represent 10 distinct claim states.
The correct fix has two parts:
In the claim() function, replace claimed[_treasureHash] with the function parameter claimed[treasureHash] so the check reads the same key that _markClaimed writes to.
Remove the unused immutable _treasureHash declaration, which is dead code once the check uses the parameter.
After the fix, re-enable the vm.expectRevert() line in testClaimDoubleSpendReverts so any regression is caught by CI.
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.