Snowman Merkle Airdrop

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

Snow.buySnow silently keeps excess ETH when WETH payment path is taken — user funds permanently lost

Snow.buySnow silently keeps excess ETH when WETH payment path is taken

Impact

High Impact

Likelihood

Medium Likelihood

Root + Impact

Description

  • Snow.buySnow is a payable function that accepts either ETH or WETH as payment for Snow tokens.

  • When msg.value != s_buyFee * amount, the contract takes the WETH path — it calls safeTransferFrom to pull WETH from the caller and retains whatever ETH was accidentally sent. There is no refund and no revert. The stranded ETH can only be recovered by the collector via collectFee, so any user who sends even 1 wei alongside a WETH purchase suffers a permanent loss.

// src/Snow.sol
function buySnow(uint256 amount) external payable canFarmSnow {
@> if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
// WETH path — msg.value is silently absorbed, no refund
@> i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
}

Risk

Likelihood:

  • Front-end bugs, price slippage, or user error can trivially result in a non-zero msg.value alongside a WETH transaction.

  • Smart-contract integrators that forward ETH trigger this silently.

Impact:

  • Users lose the excess ETH permanently — it is collected by the fee collector, not returned.

  • Users are double-charged (ETH trapped + WETH pulled).

Proof of Concept

function test_BuySnow_ExcessEthStuck() public {
vm.prank(alice);
snow.buySnow{value: 0.5 ether}(10); // msg.value != buyFee*10 -> WETH path
assertEq(address(snow).balance, 0.5 ether); // ETH stuck
assertEq(snow.balanceOf(alice), 10); // Snow received, ETH lost
}

Recommended Mitigation

} else {
+ require(msg.value == 0, "Do not send ETH for WETH purchase");
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 hours 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!