Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
Submission Details
Impact: high
Likelihood: high

No Replay Protection / Idempotency

Author Revealed upon completion

Root + Impact

Description

  • Normal Behavior: Each eligible address should only be able to claim their Snowman NFT once using a valid Merkle proof and signature.

  • Issue: The contract does not check if a user has already claimed. The s_hasClaimedSnowman mapping is updated only after state changes and is never read before execution. This allows any valid claimant to repeatedly call claimSnowman() and mint multiple NFTs.

function claimSnowman(...) external nonReentrant {
...
@> s_hasClaimedSnowman[receiver] = true;
...
@> i_snowman.mintSnowman(receiver, amount);
}

Risk

Likelihood:

  • The Merkle tree and signature are static; users can re-submit them any time.

  • There's no guard preventing repeated calls to the claim function

Impact:

  • Any eligible user can infinitely repeat the claim and mint function.

  • Token distribution becomes exploitable, leading to over-minting and broken economics.


Proof of Concept

// First claim (valid)
airdrop.claimSnowman(receiver, proof, v, r, s); // mints 1 Snowman
// Replay
airdrop.claimSnowman(receiver, proof, v, r, s); // mints again — unlimited!
// Front-run others' claims if proofs are leaked or shared

Recommended Mitigation

function claimSnowman(...) external nonReentrant {
+ if (s_hasClaimedSnowman[receiver]) {
+ revert AlreadyClaimed();
+ }
...
s_hasClaimedSnowman[receiver] = true;
...
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.