buySnow infers the payment asset from an exact-equality check, so a mismatched msg.value is silently swallowed on top of the full WETH priceSnow can be bought with either native ETH or WETH — the README states the token "can be bought with either WETH or native ETH". A buyer picks one asset, pays the price once, and receives their tokens.
buySnow decides which asset was used by testing whether msg.value is exactly the price. Any other value falls through to the WETH branch, which pulls the full price in WETH while the ETH the user attached stays in the contract. The function is payable, has no msg.value == 0 requirement on that branch, and contains no refund path anywhere, so the user is charged twice and cannot recover the difference.
The stranded ETH is not merely stuck — it is later swept to the fee collector, so the protocol keeps the user's mistake:
There is no receive, no withdraw, and no rescue function in Snow, so once the ETH is in the contract the payer has no route back.
The magnitude follows from the deployment parameters. script/DeploySnow.s.sol:13 passes FEE = 5, and the constructor scales it: s_buyFee = _buyFee * PRECISION (src/Snow.sol:73), giving 5 ETH per unit. A single-unit off-by-one therefore costs the buyer 5 ETH on top of the 5 WETH they meant to pay.
Likelihood:
This triggers whenever a buyer attaches ETH while intending a WETH purchase, or miscalculates the amount by any margin — for example when a wallet attaches value by default, when the buyer reads s_buyFee and computes the product off-chain with a stale or rounded value, or when they simply pass the wrong amount.
Nothing in the contract warns or reverts. The transaction succeeds, tokens are minted, and an event is emitted, so the buyer receives every signal that the purchase went correctly.
No project test covers a mismatched msg.value, so this path has never been exercised.
An attacker cannot force the condition, which is why Likelihood is set to Low rather than higher. s_buyFee is written only in the constructor (src/Snow.sol:73) and has no setter, so there is no fee-change front-run that could push a correctly-computed transaction into the wrong branch. The victim must independently hold a live WETH allowance and send a wrong msg.value.
Impact:
Direct and unrecoverable loss of user funds — with the deployed parameters, 5 ETH per unit of error, on top of the WETH the user correctly paid.
The loss is silent: no revert, no refund, no event distinguishing the double-charge from a normal purchase.
The lost ETH is credited to the fee collector through collectFee, so the protocol profits from the user's error rather than merely stranding it.
The buyer sends one wei less than the exact price and pays roughly ten ETH for one unit of Snow. Save as test/PoCM03.t.sol and run forge test --match-contract PoCM03 -vv. The fixture uses the same constructor arguments as the project's own script/DeploySnow.s.sol (FEE = 5).
Result:
Make the payment path explicit rather than inferred, and refund any excess instead of retaining it.
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.