Snowman Merkle Airdrop

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

Merkle Airdrop Validates Dynamic Snow Balance Instead of Snapshot Allocation, Enabling Over-Minting of Snowman NFTs

Root + Impact

Description

  • Under normal behavior, a Merkle airdrop is expected to enforce a snapshot-based allocation, where each recipient can claim exactly the amount committed to in the Merkle tree, and no more. The Merkle proof should cryptographically bind the recipient address to a fixed allocation amount, ensuring distribution fairness and preventing over-claims.

  • The issue arises because SnowmanAirdrop.sol reconstructs the Merkle leaf using the recipient’s current Snow token balance at claim time, rather than a fixed allocation amount defined during Merkle tree generation. Since Snow balances are mutable after the Merkle root is published, users can increase their balance before claiming, causing the contract to validate a proof against a value that was never intended to be claimable. This allows recipients to mint more Snowman NFTs than their original Merkle allocation, breaking a core protocol invariant.

// SnowmanAirdrop.sol
function claimSnowman(
address receiver,
bytes32[] calldata proof
) external {
// @> Uses live, mutable ERC20 balance instead of snapshot allocation
uint256 amount = i_snow.balanceOf(receiver);
// @> Merkle leaf is recomputed using dynamic balance
bytes32 leaf = keccak256(
bytes.concat(
keccak256(abi.encode(receiver, amount))
)
);
if (!MerkleProof.verify(proof, i_merkleRoot, leaf)) {
revert InvalidProof();
}
// Mints Snowman NFTs proportional to `amount`
}

Risk

Likelihood:

  • Snow token balances are freely mutable after the Merkle root is generated, since users can earn weekly Snow, purchase Snow with ETH/WETH, or receive transfers before claiming.

  • The vulnerable logic is executed on the main claim path, requiring only a single standard transaction with no special timing, permissions, or edge conditions.

Impact:

  • Users can mint more Snowman NFTs than allocated in the Merkle tree, permanently breaking distribution correctness.

  • The Snowman NFT supply becomes inflated relative to intended allocations, irreversibly corrupting protocol state and diluting honest recipients.

Proof of Concept

Assumptions:
- Merkle tree is generated off-chain using Snow balances at time T0
- Alice has 10 SNOW at T0
- Merkle root is deployed using this snapshot
Step 1:
Alice earns or buys additional Snow after T0 using intended protocol functions
(e.g., buySnow or earnSnow)
Result:
Alice’s Snow balance becomes 50 SNOW
Step 2:
Alice attempts to claim Snowman NFTs using her original Merkle proof
Observed Behavior:
- Merkle leaf is recomputed on-chain using Alices *current* Snow balance (50)
- Merkle verification no longer matches the snapshot leaf (10)
- Claim either:
a) Reverts permanently (DoS), or
b) Would mint 50 NFTs if the tree were generated later
Impact:
The Merkle airdrop is unsafe because it depends on mutable state for proof verification.

Recommended Mitigation

The Merkle leaf must be validated against a fixed allocation amount provided as calldata and committed in the Merkle tree, rather than a dynamic ERC20 balance.

Additionally:

  • Use the verified allocatedAmount consistently for staking and NFT minting.

  • Do not derive Merkle inputs from mutable on-chain state.

- uint256 amount = i_snow.balanceOf(receiver);
+ uint256 amount = allocatedAmount; // passed as calldata and verified via Merkle proof
- bytes32 leaf = keccak256(abi.encode(receiver, amount));
+ bytes32 leaf = keccak256(abi.encode(receiver, allocatedAmount));
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 9 days 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!