Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Snowman Merkle Airdrop

Snowman Merkle Airdrop Security Audit

Executive Summary

Audit of Snowman Merkle Airdrop contracts revealing 1 High, 3 Medium, and 2 Low severity vulnerabilities.

Scope

  • SnowmanAirdrop.sol, Snow.sol, Snowman.sol

Risk Rating

High Risk

High Severity Issues

H-1: Reentrancy in SnowmanAirdrop.claimSnowman

Description

  • Function sends tokens before updating the s_hasClaimedSnowman mapping.

  • This violates the CEI pattern, permitting reentrancy via token hooks.

@> i_snow.safeTransferFrom(receiver, address(this), amount); // Interaction before effect
s_hasClaimedSnowman[receiver] = true; // Effect after interaction

Risk

Likelihood: Malicious token contract or hook triggers during transferFrom.
Impact: Attacker recursively claims Snowman NFTs, draining the total supply.

Proof of Concept

  1. Attacker deploys contract with a tokensReceived hook (ERC777-style) or similar malicious logic.

  2. Attacker calls claimSnowman.

  3. Inside safeTransferFrom, the attacker's hook re-calls claimSnowman.

  4. Since s_hasClaimedSnowman is still false, the second call succeeds.

  5. Repeat until the NFT supply is exhausted or gas limits are met.

Recommended Mitigation

Update state before external interactions.

- i_snow.safeTransferFrom(receiver, address(this), amount);
- s_hasClaimedSnowman[receiver] = true;
+ s_hasClaimedSnowman[receiver] = true;
+ i_snow.safeTransferFrom(receiver, address(this), amount);

Medium Severity Issues

M-1: Signature Malleability in SnowmanAirdrop

Description

  • _isValidSignature lacks "low-s" enforcement, allowing valid non-canonical signatures.

@> (address actualSigner,,) = ECDSA.tryRecover(digest, v, r, s);

Risk

Likelihood: Attacker modifies s and v to create a valid alternative signature.
Impact: Potential for replay attacks if specific signatures aren't tracked and invalidated.

Proof of Concept

An attacker can calculate a second valid signature (r, -s mod n) for any valid user signature. If s is greater than n/2, it can be flipped to n - s, and v toggled between 27 and 28. Both will recover to the same address but have different bytes.

Recommended Mitigation

Check that s is in the lower half of the curve order.

+ if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) return false;

M-2: Global Timer DoS in Snow.earnSnow

Description

  • earnSnow uses a global s_earnTimer, restricting minting to one user/address per week project-wide.

@> if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) revert S__Timer();

Risk

Likelihood: Multiple addresses controlled by one user or competitive users.
Impact: First user blocks all others for a week; Sybil attacks bypass individual intent.

Proof of Concept

  1. User A calls earnSnow() with Address 1. s_earnTimer is set to now.

  2. User B (or User A with Address 2) tries to call earnSnow().

  3. Transaction reverts because block.timestamp is less than s_earnTimer + 1 week.

  4. Result: Only 1 token total can be earned per week across all users.

Recommended Mitigation

Use a per-user mapping(address => uint256) for timers.

M-3: Unchecked Fee Overflow in buySnow

Description

  • Multiplication of s_buyFee * amount can overflow.

@> if (msg.value == (s_buyFee * amount))

Risk

Likelihood: High s_buyFee or extreme amount values.
Impact: Unexpected transaction reverts or logic failure in contract accounting.

Proof of Concept

If s_buyFee = 1e18 and amount = 2**256 / 1e18 + 1, the multiplication overflows. In Solidity 0.8+, this reverts automatically, but the lack of explicit handling can lead to poor UX or opaque integration bugs.

Recommended Mitigation

Assign to variable and check or rely on Solidity 0.8+ reverts explicitly.

Low Severity Issues

L-1: Typo in MESSAGE_TYPEHASH

  • Description: Typo "addres" in EIP-712 string.

  • Mitigation: Correct "addres" to "address".

L-2: Magic Number in Snow

  • Description: Hardcoded 1 in _mint.

  • Mitigation: Define uint256 private constant EARN_AMOUNT = 1;.

Conclusion

The protocol requires immediate fixes for reentrancy and distribution logic to ensure security.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!