Snowman Merkle Airdrop

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

Missing Zero Amount Validation in Token Purchase Function

Summary

The buySnow() function lacks validation that prevents users from purchasing zero tokens, allowing transactions that serve no practical purpose but still update the global timer affecting other users.

Description

The Snow contract does not validate that the amount parameter in buySnow() is greater than zero. This allows users to execute token purchases for zero tokens, which:

  1. Makes no logical sense from a business perspective

  2. Wastes gas on meaningless transactions

  3. Updates the global earning timer, preventing other users from earning tokens

  4. Creates vectors for griefing attacks, where malicious users can intentionally call the function with zero amounts to disrupt the protocol

This missing validation is particularly problematic when combined with the global timer vulnerability, as it provides an easy way for attackers to reset the timer without having to spend any funds on actual token purchases.

Severity Classification

Impact: Low

  • Allows meaningless transactions that waste gas

  • Contributes to the global timer vulnerability by providing an easy reset method

  • Could confuse analytics and monitoring systems with zero-value purchases

Likelihood: Medium

  • Could occur by user error or be intentionally exploited

  • Requires minimal technical knowledge to exploit

  • No special conditions needed to trigger

File Name

src/Snow.sol

Code with Issue

function buySnow(uint256 amount) external payable canFarmSnow {
// Missing validation: 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);
}

Recommendation

Add zero amount validation:

function buySnow(uint256 amount) external payable canFarmSnow {
if (amount == 0) revert S__ZeroValue();
if (msg.value >= (s_buyFee * amount)) {
// Handle ETH payment logic
_mint(msg.sender, amount);
} else {
// Handle WETH payment logic
_mint(msg.sender, amount);
}
emit SnowBought(msg.sender, amount);
}

This simple check prevents zero-amount purchases, ensuring that all transactions serve a meaningful purpose and preventing users from easily manipulating the global timer without acquiring tokens.

Proof of Concept

function test_POC_ZeroAmountPurchase() public {
console2.log("=== POC 3: Zero Amount Purchase ===");
// Alice earns a token first
vm.prank(alice);
snow.earnSnow();
assertEq(snow.balanceOf(alice), 1);
console2.log("Alice earned 1 Snow token");
// Fast forward almost a week
vm.warp(block.timestamp + 6 days + 23 hours);
// Bob makes a zero purchase with exact ETH (0 * fee = 0 ETH)
vm.prank(bob);
snow.buySnow{value: 0}(0);
assertEq(snow.balanceOf(bob), 0); // Bob gets 0 tokens
console2.log("Bob bought 0 tokens for 0 ETH");
// Charlie tries to earn after the full week from Alice should have passed
vm.warp(block.timestamp + 1 hours); // Now full week from Alice
vm.prank(charlie);
vm.expectRevert(); // Fails because Bob's zero purchase reset the timer!
snow.earnSnow();
console2.log("BUG DEMONSTRATED: Zero purchase by Bob prevented Charlie from earning");
console2.log("The global timer was reset by a meaningless transaction");
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 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.