Snowman Merkle Airdrop

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

SnowmanAirdrop claim vulnerability

Root + Impact

Description

  • Should be able to cliam tokens without problems or risk being locked out.

  • The SafeTransfer and order of operations can become a bad state bug.

// Root cause in the codebase with @> marks to highlight the relevant section
// 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);
}

Risk

Likelihood:

  • Reason 1 // safeTransferFrom will fail if receiver doesn't approve contract

  • Reason 2// Marked user as claimed before minting

Impact:

  • Impact 1// ERC-20 doesn't support safeTransferFrom

  • Impact 2// User will be locked out

Proof of Concept

i_snow.transferFrom(receiver, address(this), amount);
i_snowman.mintSnowman(receiver, amount);
s_hasClaimedSnowman[receiver] = true;
emit SnowmanClaimedSuccessfully(receiver, amount);
//Will allow proper transfer functions and gives proper state

Recommended Mitigation

- remove this code
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);
+ add this code
i_snow.transferFrom(receiver, address(this), amount);
i_snowman.mintSnowman(receiver, amount);
s_hasClaimedSnowman[receiver] = true;
emit SnowmanClaimedSuccessfully(receiver, amount);
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours 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!