Snowman Merkle Airdrop

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

Claimable More Than Once (Missing Claimed Check) in `SnowmanAirdrop.sol::claimSnowman()`

[Medium] Claimable More Than Once (Missing Claimed Check) in SnowmanAirdrop.sol::claimSnowman()


Description

The claimSnowman() function in SnowmanAirdrop.sol utilizes a mapping s_hasClaimedSnowman[receiver] to record whether a recipient has successfully claimed. However, there is a critical missing require or if condition at the beginning of the function that would check this state variable before allowing the claim process to proceed. The mapping is only set to true at the very end of the function after all other logic has executed.


Risk

Without an initial check, a user may be able to replay claim transactions with the same Merkle proof and signature (especially if the signature replay vulnerabilities [H-6] and [H-7] are not fully mitigated). This oversight could lead to double claims, where a single eligible user can mint multiple NFTs from a single entitlement, resulting in an inflated NFT supply and an unfair distribution.


Proof of Concept

The provided pseudo-code highlights the missing check:
```javascript
if (!s_hasClaimedSnowman[receiver]) { // <- This essential check is missing at the start!
... // claim again
}
```
In the current contract state, if a valid signed message and Merkle proof are used, and the `nonReentrant` modifier were somehow bypassed or if a new transaction is sent after the previous one completed but before `s_hasClaimedSnowman[receiver]` is updated (though it's at the end), a user could attempt to claim multiple times.

Recommended Mitigation

Add a require statement at the very beginning of the claimSnowman function to ensure that a receiver can only execute the claim process if they have not already claimed.

```diff
function claimSnowman(address receiver, bytes32[] calldata merkleProof, uint8 v, bytes32 r, bytes32 s)
external
nonReentrant
{
+ require(!s_hasClaimedSnowman[receiver], "SA__AlreadyClaimed: Snowman already claimed for this address."); // New: Check at the start
if (receiver == address(0)) {
revert SA__ZeroAddress();
}
// ... rest of the function logic ...
- s_hasClaimedSnowman[receiver] = true; // This line should remain where it is to mark after successful execution
}
```
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.