Snowman Merkle Airdrop

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

## SnowmanAirdrop.sol ## [ Duplicate claims ]

Root + Impact

Description

The claimSnowman function allows repeated claims because it does not check whether the user has already claimed.

Although the flag s_hasClaimedSnowman[receiver] is set at the end of the function, there is no require or revert at the beginning to prevent duplicate claims.

function claimSnowman(address receiver, bytes32[] calldata merkleProof, uint8 v, bytes32 r, bytes32 s)
external
nonReentrant
{
if (receiver == address(0)) {
revert SA__ZeroAddress();
}
if (i_snow.balanceOf(receiver) == 0) {
revert SA__ZeroAmount();
}
if (!_isValidSignature(receiver, getMessageHash(receiver), v, r, s)) {
revert SA__InvalidSignature();
}
uint256 amount = i_snow.balanceOf(receiver);
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(receiver, amount))));
if (!MerkleProof.verify(merkleProof, i_merkleRoot, leaf)) {
revert SA__InvalidProof();
}
i_snow.safeTransferFrom(receiver, address(this), amount);
// send tokens to contract... akin to burning
s_hasClaimedSnowman[receiver] = true;
emit SnowmanClaimedSuccessfully(receiver, amount);
i_snowman.mintSnowman(receiver, amount);
}
// >>> INTERNAL FUNCTIONS
function _isValidSignature(address receiver, bytes32 digest, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (bool)
{
(address actualSigner,,) = ECDSA.tryRecover(digest, v, r, s);
return actualSigner == receiver;
}

Risk

If the claimSnowman function does not check if an address has already claimed, the same user can repeatedly call the function and receive Snowman NFTs multiple times.


Likelihood: High

Impact:

Users can claim multiple times, draining the system

Proof of Concept

1. Bob calls `claimSnowman()` — this works and sets `s_hasClaimedSnowman[Bob] = true`
2. Since there's no check at the beginning, Bob can call it again and:
- Pass Merkle proof again
- Pass valid signature again
- Claim again

Recommended Mitigation

Add a guard clause.

Place it at the top of the function before any state-changing logic.

+ require(!s_hasClaimedSnowman[receiver], "Snowman already claimed");
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 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.