Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Valid

Signature Replay Enables Duplicate NFT Claims

Root + Impact

Description:
The contract constructs a Merkle leaf and verifies an ECDSA signature based only on the user’s address and token amount. There is no per‑claim nonce or timestamp included. After an initial successful claim, a user can restore their Snow token balance (via transfer or purchase) to the same amount, then reuse the identical Merkle proof and signature to call claimSnowman() a second time, resulting in duplicate NFT mints and supply inflation.

Key code snippet:

bytes32 leaf = keccak256(abi.encodePacked(receiver, amount));
require(_verify(proof, leaf), "Invalid proof");
require(_isValidSignature(receiver, amount, v, r, s), "Invalid signature");
_mintSnowman(receiver, amount);

Risk

Likelihood:

  • A user able to rebuy or transfer tokens back can repeat the claim.

  • No safeguard exists once proof and signature checks pass.

Impact:

  • Multiple NFT airdrops to the same address.

  • Airdrop supply exceeds intended distribution.

  • Unfair advantage and loss of trust in the protocol.


Proof of Concept

  1. User A holds the required amount of Snow tokens.

  2. Call claimSnowman(A, proof, v, r, s) → NFTs minted (first claim).

  3. Transfer or repurchase the same amount of Snow tokens to A.

  4. Call claimSnowman(A, proof, v, r, s) again → NFTs minted a second time.


Recommended Mitigation

  1. Add a per‑user nonce to leaf generation and signature:

    mapping(address => uint256) private s_nonce;
    function _leaf(address receiver, uint256 amount) internal view returns (bytes32) {
    return keccak256(abi.encodePacked(receiver, amount, s_nonce[receiver]));
    }
  2. Increment nonce before verification in claimSnowman():

    require(!s_hasClaimedSnowman[receiver], "Already claimed");
    s_nonce[receiver]++;
  3. Retain the boolean s_hasClaimedSnowman check as an extra guard.

  4. Update off‑chain Merkle tree and signing scripts to include the nonce in both proof generation and EIP‑712 hashing.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of claim check

The claim function of the Snowman Airdrop contract doesn't check that a recipient has already claimed a Snowman. This poses no significant risk as is as farming period must have been long concluded before snapshot, creation of merkle script, and finally claiming.

Support

FAQs

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