claim() is designed so that an eligible address calls the function to receive their USDC allocation. The intent is that the claimant initiates the transaction themselves.
The function accepts an arbitrary address account parameter. The merkle leaf is constructed from this parameter rather than from msg.sender. This means any third party who holds a valid proof for a target address can call claim(victimAddress, amount, proof) and force a token transfer to victimAddress — without victimAddress ever initiating or consenting to the transaction.
Likelihood:
Merkle proofs are public (they are published off-chain or derivable from tree.json) — any observer can reconstruct a valid proof for any eligible address
Combined with the missing replay protection (Submission 1), a front-running bot watching the mempool can call claim() for all 4 eligible addresses in a single block before any of them act
Impact:
In jurisdictions where receiving tokens is a taxable event, forcing a token transfer to an address creates an unintended tax liability for the recipient without their consent
If account is a contract that cannot handle ERC-20 tokens (e.g. lacks an ERC-20 receiver), the forced transfer could lock the tokens permanently — the legitimate owner then cannot claim what is rightfully theirs
Interacts with replay vulnerability: without the hasClaimed fix, an attacker can use any address's proof to drain the contract to that address on their behalf
Add this test to test/MerkleAirdropTest.t.sol and run forge test --match-test test_AnyoneCanClaimForVictim -vvv:
Replace the address account parameter with msg.sender directly.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.