Root + Impact
## Description: buySnow lets a user pay either in native ETH (exact msg.value) or in WETH. The intent is: if the user sent the exact ETH price, mint against that ETH; otherwise pull the price in WETH. The problem is the else branch does not account for any ETH the user still sent. If msg.value is non-zero but not exactly equal to the price, execution falls into the else branch, charges the full price in WETH, and the ETH the user sent stays locked in the contract with no refund path for the user.
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);
}
}
Risk
Likelihood:
* Occurs whenever a user sends any ETH that does not exactly match s_buyFee * amount while also having WETH approved — an easy mistake given gas/price variance.
Impact:
* The user pays twice: the full price in WETH plus the ETH they sent, which becomes trapped in the contract. * The trapped ETH can only be swept by the collector via collectFee(); the user has no way to recover it. Direct loss of user funds.
Proof of Concept
This test passes. The user approves WETH, calls buySnow(1) with 0.1 ETH (not the exact price), receives the token, is charged full WETH, and loses the 0.1 ETH to the contract:
function test_PoC_TrappedETH() public {
vm.startPrank(user);
weth.approve(address(snow), FEE);
snow.buySnow{value: 0.1 ether}(1);
vm.stopPrank();
assertEq(snow.balanceOf(user), 1);
assertEq(weth.balanceOf(user), 0);
assertEq(address(snow).balance, 0.1 ether);
}
Recommended Mitigation
Reject the call if the user sent ETH that does not match the price, so they cannot accidentally pay in WETH while also losing native ETH. Alternatively, refund any non-matching msg.value at the end of the function.
function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
+ require(msg.value == 0, "send exact ETH price or no ETH");
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
}