Root: The claimSnowman
function lacks an alreadyClaimed
verification before external calls;
Impact: Attackers can claim the airdrop multiple times, exhausting the supply.
The claimSnowman
function is designed to allow eligible addresses to claim Snowman NFTs by providing a valid Merkle proof, a signature, and meeting certain balance requirements. However, the current implementation does not enforce a one-claim-per-address policy. The function proceeds with external calls to transfer Snow tokens and mint NFTs before updating the s_hasClaimedSnowman
mapping, which serves as the claim status tracker. This ordering creates a vulnerability where an attacker can resubmit the claim transaction multiple times for the same receiver address, as long as the initial conditions (e.g., valid proof and signature) are met. The lack of a pre-check against s_hasClaimedSnowman[receiver]
means the function does not revert on subsequent calls, allowing the attacker to accumulate multiple NFTs or drain the contract’s token reserves.
Likelihood:
The likelihood of exploitation is high. The vulnerability can be triggered by any user with a valid Merkle proof and signature, which are intended to be widely distributed to eligible participants.
An attacker could easily generate multiple transactions using the same receiver address, especially if they control it, making this a feasible attack vector during the airdrop period
Impact:
The impact is severe, classified as high. Repeated claims could exhaust the Snowman NFT supply or the Snow token balance required for the airdrop, potentially rendering the contract unusable for legitimate users.
This could result in significant economic damage to the project, reputational harm, and loss of trust among the community. The lack of a cap on claims amplifies the potential scale of the issue.
Demonstration: The vulnerability can be demonstrated by executing the claimSnowman
function multiple times with the same receiver address.
Setup: Deploy the SnowmanAirdrop
contract with a valid ROOT, Snow, and Snowman instance. Fund a test address (e.g., alice) with 1 Snow token and approve the airdrop contract to transfer it. Generate a valid Merkle proof (AL_PROOF) and signature for alice
.
Steps:
Call claimSnowman(alice, AL_PROOF, v, r, s)
as alice
, verifying the first claim succeeds and mints 1 NFT.
Immediately call claimSnowman(alice, AL_PROOF, v, r, s)
again without modifying the state.
Observe that the second call also succeeds, minting an additional NFT to alice
, as s_hasClaimedSnowman[alice]
is not checked beforehand.
Expected Outcome: nft.balanceOf(alice)
should exceed 1, confirming multiple claims are possible. This behavior validates the vulnerability, as the contract allows unlimited claims per address.
To address this critical vulnerability, the claimSnowman
function must be updated to include a pre-check against the s_hasClaimedSnowman
mapping and introduce a new SA__AlreadyClaimed
error. The state update should also be prioritized before external calls to align with the Checks-Effects-Interactions (CEI) pattern. The revised implementation is as follows:
Explanation: The addition of the if (s_hasClaimedSnowman[receiver])
check ensures that only the first claim per address succeeds, reverting with SA__AlreadyClaimed
on subsequent attempts.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.