Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

claimSnowman mints too many NFTs

Root + Impact

Description

  • claimSnowman should mint NFTs equal to tha amount of SNOW

  • claimSnowman does not account for precision and mints more

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:

  • Happens on all claims

Impact:

  • Inintended impact of high supply of NF

  • Expensive transaction for the user in case of large amount of SNOW resulting in long for loop of minting the NFTs

  • Potentially exceeding gas limit and resulting in lockout of not being able to claim at all

Proof of Concept

As solution we should:

  1. mint only 1 NFT for every 100000 SNOW

  2. check that the user can at least mint 1 NFT otherwise revert

uint256 amount = i_snow.balanceOf(receiver);
uint256 decimals = 1e18; // 18 decimals, preferably set in constructor and manage in deployment to be the same as SNOW
if (amount % decimals != 0) {
revert SA__InvalidAmount();
}
uint256 nftAmount = amount / decimals;
i_snow.safeTransferFrom(receiver, address(this), amount);
s_hasClaimedSnowman[receiver] = true;
emit SnowmanClaimedSuccessfully(receiver, nftAmount);
i_snowman.mintSnowman(receiver, nftAmount);

Recommended Mitigation

See below implementation of the above POC, accounting for decimal places of SNOW.

+ error SA__NotEnoughSnow();
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);
uint256 snowDecimals = 1e18; // 18 decimals;
+ if (amount % decimals != 0) {
+ revert SA__InvalidAmount(); // You may want to define this error
+ }
+ uint256 nftAmount = amount / decimals;
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);
+ emit SnowmanClaimedSuccessfully(receiver, nftAmount);
- i_snowman.mintSnowman(receiver, amount);
+ i_snowman.mintSnowman(receiver, nftAmount);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.