## Description
In `Snow.sol`, `buySnow()` uses a strict equality check `msg.value == (s_buyFee * amount)` to branch between
native ETH and WETH payments:
```solidity
// Root cause in src/Snow.sol:
@> 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);
}
If a user sends ETH that does not exactly equal s_buyFee * amount (e.g., sending partial ETH or overpaying):
The condition msg.value == (s_buyFee * amount) evaluates to false.
Execution falls into the else branch, pulling the full fee in WETH via i_weth.safeTransferFrom().
The native ETH sent in msg.value is never refunded, trapping the user's ETH in the contract and resulting in
double payment.
Likelihood: High
Impact: High
A user sends 1 ETH along with an active WETH allowance to buy 1 token (cost: 5 WETH). The contract deducts 5 WETH
and keeps the 1 ETH sent in msg.value.
Add to test/TestSnow.t.sol:
function test_buySnow_traps_eth_and_double_charges_weth() public {
// Jerry approves 5 WETH
vm.startPrank(jerry);
weth.approve(address(snow), FEE);
// Jerry accidentally sends 1 ETH with the call
vm.deal(jerry, 1 ether);
snow.buySnow{value: 1 ether}(1);
vm.stopPrank();
// Jerry paid full WETH AND lost the 1 ETH sent in msg.value
assertEq(weth.balanceOf(address(snow)), FEE);
assertEq(address(snow).balance, 1 ether);
assertEq(jerry.balance, 0);
}
Explicitly separate the payment paths or require msg.value == 0 when paying with WETH, refunding any excess ETH
when paying with native ETH.
--- a/src/Snow.sol
+++ b/src/Snow.sol
@@ -79,8 +79,16 @@ contract Snow is ERC20, Ownable {
function buySnow(uint256 amount) external payable canFarmSnow {
- if (msg.value == (s_buyFee * amount)) {
+ uint256 cost = s_buyFee * amount;
+ if (msg.value > 0) {
+ if (msg.value < cost) revert S__ZeroValue();
_mint(msg.sender, amount);
+ if (msg.value > cost) {
+ (bool refund,) = payable(msg.sender).call{value: msg.value - cost}("");
+ require(refund, "Refund failed");
+ }
} else {
- i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
+ i_weth.safeTransferFrom(msg.sender, address(this), cost);
_mint(msg.sender, amount);
}
---
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.