Snowman Merkle Airdrop

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

Double Payment and ETH Theft Vulnerability in Snow::buySnow Function

Author Revealed upon completion

Description:

The buySnow function contains a critical flaw in its payment handling logic. When users send ETH in an amount that doesn't exactly match s_buyFee * amount but have sufficient WETH balance/approval:

  • The contract falls back to using WETH for payment

  • The originally sent ETH remains trapped in the contract

  • User pays both the WETH fee AND loses their ETH
    This violates core security principles by enabling silent fund theft through normal protocol usage.

Impact:

  • Direct Financial Loss: Users permanently lose ETH sent to the contract

  • Double Payment: Users pay full price in WETH while losing ETH

  • Reputation Damage: Protocol appears to steal user funds

  • Permanent Fund Lock: Trapped ETH can only be recovered by privileged roles


Risk:

Likelihood:
• Common user error (incorrect ETH amount calculations)
• Wallet UIs may auto-populate gas + value confusion
• No frontend validation guarantees

Impact:
• Permanent ETH loss to victims
• No recovery mechanism for users
• Undermines trust in entire protocol

Proof of Concept

Test Scenario:

  1. Required fee for 100 tokens = 1 ETH

  2. User accidentally sends 0.5 ETH

  3. User has 1 WETH balance + approval

// Attack Sequence:
buySnow(100){value: 0.5 ether} // Calls vulnerable function
// Execution Path:
1. msg.value (0.5 ETH) != 1 ETH → enter else branch
2. WETH transfer succeeds (1 WETH deducted)
3. Tokens minted to user
4. 0.5 ETH remains locked in contract
Result:
  • User receives 100 Snow tokens

  • User loses 0.5 ETH permanently

  • User loses 1 WETH (converted to 1 ETH value)

  • Net loss: 1.5 ETH value for 100 tokens (50% overpayment)

##Recommended Mitigation

function buySnow(uint256 amount) external payable canFarmSnow {
+ uint256 totalFee = s_buyFee * amount;
- if (msg.value == (s_buyFee * amount)) {
- _mint(msg.sender, amount);
- } else {
+ if (msg.value > 0) {
+ // Strict ETH payment requirements
+ if (msg.value != totalFee) {
+ payable(msg.sender).transfer(msg.value);
+ revert S__IncorrectETHAmount();
+ }
+ _mint(msg.sender, amount);
+ } else {
i_weth.safeTransferFrom(msg.sender, address(this), totalFee);
_mint(msg.sender, amount);
}
emit SnowBought(msg.sender, amount);
}

Document Payment Rules:
"ETH payments must be exact - any over/under payment will be refunded and transaction reverted. WETH payments require exact token approval."

Support

FAQs

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