Snowman Merkle Airdrop

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

buySnow Accepts Invalid ETH Amounts and Can Lock User Funds

Root + Impact

Description

  • The buySnow function is designed to allow users to purchase Snow tokens using either ETH or WETH.

  • However, the function does not validate that msg.value is either zero or exactly equal to the required purchase cost. As a result, any non-zero but incorrect ETH amount causes the function to fall back to the WETH payment path while silently retaining the sent ETH inside the contract.

@>
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);
}

Risk

Likelihood:

  • Users frequently submit incorrect ETH values due to UI or rounding errors

  • The issue can occur on every call to buySnow

Impact:

  • User ETH becomes permanently locked in the contract

  • Users may unintentionally pay both ETH and WETH for a single purchase

Proof of Concept

  • Scenario

The buySnow function allows users to purchase Snow tokens using either ETH or WETH.
The intended behavior is that users either:

Pay exact ETH amount equal to s_buyFee * amount, or

Pay zero ETH and transfer the equivalent amount of WETH.

However, the function does not validate incorrect non-zero msg.value.
As a result, when a user sends an invalid ETH amount, the function silently falls back to the WETH payment path while permanently locking the sent ETH in the contract.

  • Step-by-Step Attack / Failure Flow

A user attempts to buy Snow using ETH.

Due to UI rounding, fee miscalculation, or manual interaction, the user sends a non-zero msg.value that is not exactly equal to s_buyFee * amount.

The if condition fails.

Execution enters the else branch:

WETH is transferred from the user.

Snow tokens are minted.

The sent ETH is not refunded, reverted, or accounted for.

The ETH remains permanently locked in the Snow contract.

function test_BuySnowWithInvalidETH_LocksFunds() public {
// Setup
Snow snow = new Snow(address(weth), buyFee, collector);
// Give user WETH and approve
weth.mint(address(this), 10 ether);
weth.approve(address(snow), type(uint256).max);
uint256 snowPrice = snow.s_buyFee();
// User mistakenly sends an incorrect ETH amount
// (non-zero but not equal to required price)
snow.buySnow{value: 0.5 ether}(1);
// Snow tokens are minted
assertEq(snow.balanceOf(address(this)), 1);
// ETH is stuck in the Snow contract
assertEq(address(snow).balance, 0.5 ether);
// WETH is also transferred
assertEq(weth.balanceOf(address(snow)), snowPrice);
}

Recommended Mitigation

+ require(
+ msg.value == 0 || msg.value == s_buyFee * amount,
+ "Invalid ETH amount"
+ );

Additionally, consider separating ETH and WETH purchase flows into distinct functions.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 12 days 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!