Snowman Merkle Airdrop

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

Snow.buySnow() traps ETH permanently when msg.value is non-zero but incorrect, causing user fund loss

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

`buySnow()` accepts payment in either ETH or WETH. The routing logic uses `if (msg.value == fee)` for ETH and `else` for WETH. If a user sends any non-zero `msg.value` that does not exactly match the
required fee, the `else` branch executes: WETH is pulled from the user **and** the sent ETH remains permanently trapped in the contract with no refund mechanism.
```solidity
function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
// @> If msg.value > 0 but != fee: WETH is pulled AND ETH is trapped
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
}
```
### Risk
**Likelihood:**
- Users who accidentally attach any ETH while intending to use the WETH payment path
- Users who miscalculate the exact ETH amount required (`s_buyFee * amount`)
**Impact:**
- Sent ETH is permanently locked in the Snow contractno refund function exists for users
- The ETH is only recoverable by the `collector` via `collectFee()`, not by the original sender
- User effectively pays double: WETH for the Snow tokens plus ETH lost to the contract

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

```solidity
function test_poc_ETHStuck() public {
address user = makeAddr("user");
deal(user, 10 ether);
weth.mint(user, FEE);
vm.startPrank(user);
weth.approve(address(snow), FEE);
// User sends 1 wei (wrong amount) while WETH is approved
// else branch executes: WETH pulled + 1 wei trapped
snow.buySnow{value: 1 wei}(1);
vm.stopPrank();
assertEq(snow.balanceOf(user), 1); // Snow minted via WETH
assertEq(address(snow).balance, 1 wei); // ETH permanently stuck
}
```
Run with: `forge test --match-test test_poc_ETHStuck -vvv`

Recommended Mitigation

```diff
function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
+ if (msg.value > 0) revert S__WrongPaymentMethod();
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!