Snow.buySnow() is meant to charge the buyer in either native ETH (when msg.value matches the price) or WETH (otherwise).
The else branch fires whenever msg.value != s_buyFee * amount and pulls the full WETH price from the caller without refunding any ETH that was attached. A buyer who sends a non-zero amount of ETH that does not exactly equal the expected fee is therefore charged twice: once in WETH and once in ETH (the ETH stays in the contract).
Likelihood:
Any buyer that overshoots, undershoots, or simply forwards a stale gas estimate ends up with msg.value that is not equal to s_buyFee * amount (even by 1 wei), which routes them into the WETH branch.
Common UX flows (front-end rounding, slippage on s_buyFee updates, sending ETH while planning to pay in WETH) all reach this branch on real users.
Impact:
The user pays the full price in WETH and additionally loses the entire msg.value they attached.
The lost ETH ends up under the control of s_collector via collectFee(), so the funds are not just frozen — they are redirected to the protocol/collector at the user's expense.
forge test --match-contract PoC_BuySnowEthLost -vv → [PASS].
Make ETH and WETH paths mutually exclusive based on msg.value: charge ETH only when msg.value > 0 (and refund any excess), and only fall back to WETH when no ETH was attached. This removes the double-charge and gives buyers deterministic settlement.
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.