Snowman Merkle Airdrop

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

ETH/WETH Handling in buySnow

Author Revealed upon completion

Root + Impact

Description

  • Expected : The buySnow function should enforce strict payment requirements — either ETH or WETH — to ensure liquidity is properly managed and avoid fund loss.

  • Bug : The function checks msg.value == (s_buyFee * amount) but does not validate whether WETH was transferred when msg.value == 0. This creates a discrepancy where users can pay ETH while the contract expects WETH, leading to frozen ETH balances in the contract.


// ❌ Vulnerable Code
function buySnow(uint256 amount) external payable canFarmSnow {
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);
}

Risk

Likelihood :

  • Medium : Users may accidentally send ETH instead of WETH, especially if interface instructions are unclear.

  • Medium : Contracts interacting with buySnow may misconfigure payment types, leading to unintended ETH deposits.

Impact :

  • Medium : ETH sent to the contract cannot be processed or withdrawn, locking liquidity.

  • Low : Users lose access to their ETH, but WETH payments remain functional.

Proof of Concept

// Exploit contract demonstrating ETH/WETH handling flaw
contract Exploit {
address public snowContract = 0xDeployedSnowAddress;
function attack() external payable {
// Call buySnow with ETH payment (contract expects WETH)
(bool success,) = snowContract.call{value: 1 ether}(
abi.encodeWithSignature("buySnow(uint256)", 1)
);
require(success, "Buy failed");
}
}

Explanation :
By sending ETH directly to buySnow, the contract accepts the payment (msg.value == s_buyFee * amount) but does not verify if WETH is intended . The ETH remains stuck in the contract, as there is no logic to handle or refund it.

Recommended Mitigation

- function buySnow(uint256 amount) external payable canFarmSnow {
+ function buySnow(uint256 amount, bool isEth) external payable canFarmSnow {
if (isEth) {
require(msg.value == (s_buyFee * amount), "Invalid ETH amount");
_mint(msg.sender, amount);
} else {
require(msg.value == 0, "ETH sent for WETH payment");
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
s_earnTimer = block.timestamp;
emit SnowBought(msg.sender, amount);
}

Steps :

Add Payment Type Flag : Introduce a bool isEth parameter to explicitly distinguish between ETH and WETH payments.
Validate ETH/WETH Separately : Ensure msg.value == 0 for WETH payments and reject mismatched transfers.
Rationale :
This prevents accidental ETH deposits when WETH is required, ensuring liquidity is handled correctly and avoiding fund loss.

Support

FAQs

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