Snowman Merkle Airdrop

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

Low: Zero-Value Mint Vulnerability in buySnow() - Log Pollution & State Corruption

Root + Impact

Description

  • Normal behavior:
    The buySnow() function should mint tokens in exchange for ETH or WETH provided by the caller, with a positive amount.

  • Issue:
    There is no check that the amount passed to buySnow() is greater than zero. As a result, a user can call buySnow(0) and trigger a transfer of 0 ETH or WETH, and emit a SnowBought event. This pollutes logs and introduces minor overhead. If additional logic is added later, it may be abused for gas griefing or triggering time-based restrictions unnecessarily.

// >>> Root cause: No validation on zero-amount purchases allows log/event pollution @>
function buySnow(uint256 amount) external payable canFarmSnow {
// @> No validation on `amount > 0`
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
s_earnTimer = block.timestamp;
emit SnowBought(msg.sender, amount); // @> Emits event even when amount = 0
}

Risk

Likelihood:

  • Low — This occurs when a user intentionally (or mistakenly) calls buySnow(0) to pollute logs or trigger state changes.

  • Reproducibility: This behavior can be consistently reproduced in every environment.

  • Ease of exploitation: The cost is minimal (<0.001 ETH), and requires no special permissions or conditions.

Impact:

  • Log pollution: Emits events without meaningful token minting.

  • Unnecessary state updates: s_earnTimer is updated pointlessly.

  • Potential griefing: Future logic tied to cooldowns or analytics can be gamed.

  • Misleading analytics: Off-chain systems may misrepresent minting activity.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract MintZeroPoC {
Snow public snow;
constructor(address _snow) {
snow = Snow(_snow);
}
function testZeroMint() external {
// Expect this to succeed and emit SnowBought event with amount = 0
snow.buySnow{value: 0}(0);
}
}

Explanation:

  • User calls buySnow(0) with 0 ETH.

  • Event SnowBought is emitted even though nothing meaningful happened.

  • State s_earnTimer is reset unnecessarily.

  • Impact accumulates if this action is spammed to pollute logs or reset timers.


Recommended Mitigation

Add a check to revert if the passed amount is zero, ensuring that every mint action corresponds to an actual value transfer.

function buySnow(uint256 amount) external payable canFarmSnow {
+ if (amount == 0) revert S__ZeroValue();
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
s_earnTimer = block.timestamp;
emit SnowBought(msg.sender, amount);
}

Explanation:

  • Solution: Enforces that zero-value transactions are rejected.

  • Security: Prevents event pollution and gas griefing attacks.

  • Efficiency: Saves gas by avoiding pointless state writes and logs.

  • Compatibility: Non-breaking change that improves consistency.

Severity Note:

This fix addresses a low-severity vulnerability. While the issue does not directly affect token balances or user funds, it weakens the protocol’s consistency and exposes surfaces for griefing, spam, or future misbehavior. Ensuring amount > 0 upholds clean logs, proper state transitions, and aligns behavior with the principle of meaningful value exchange.

Verification confirms proper functionality:

function testBuySnowRejectsZero() public {
vm.expectRevert(S__ZeroValue.selector);
fixedSnow.buySnow{value: 0}(0); // Reverts as expected
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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