Snow, Snowman and the in-scope scripts. Impact: buyers silently lose ETH to the collector, fees can be stranded permanently, large claims are unexecutable, and contract wallets can never claimFive low severity issues. None lets anyone steal staked funds, but in each case value is lost or a function becomes permanently unusable for someone.
L-1, buySnow keeps ETH it does not credit. The payment path is chosen by a strict equality on msg.value, and the WETH branch never refunds.
Being payable, it accepts ETH it does not credit, takes the full fee in WETH too, and does not revert. collectFee then sweeps that ETH to the collector.
L-2, a collector that cannot receive ETH bricks fee collection. The WETH transfer and the ETH sweep share one function and the ETH leg runs even at a zero balance, so a collector with no payable fallback cannot withdraw the WETH either, and changeCollector being onlyCollector leaves it unable to pass the role on.
L-3, the mint loop is linear. mintSnowman loops once per token, and a claim mints the claimant's whole Snow balance, which buySnow lets anyone accumulate without limit.
L-4, Helper.s.sol returns a WETH the token never uses. Helper deploys its own MockWETH at line 31 and returns it, while DeploySnow.s.sol deploys another at line 18 and passes that to Snow, so anyone building on Helper as the README directs sees purchases revert for no visible reason.
L-5, contract wallets can never claim. _isValidSignature uses raw ECDSA.tryRecover with no EIP-1271 fallback, so a Safe or ERC-4337 account has no key to recover from and is permanently excluded.
Likelihood:
L-1 triggers on any purchase carrying ETH that is not exactly the fee, which the contract accepts silently, and L-4 hits anyone following the README.
L-2 depends on the collector set at deployment, L-3 on the claimant's balance, L-5 on whether an eligible address is a contract.
Impact:
A buyer loses real ETH on a transaction that reports success, and it ends up with the fee collector.
Fees can become unwithdrawable, a large claim can exceed any block gas limit, and a contract wallet holding Snow can never exchange it for the NFT.
No privileged role fixes any of this: there is no refund function and the owner cannot reassign the collector.
The affected parties are buyers, the fee collector, and holders on contract wallets. There is no attacker here.
The rest are covered by passing tests too. A payable-less collector made collectFee revert with 5e18 WETH held and no ETH present. The mint loop measured 3,364,091 gas for 100 tokens and 6,648,566 for 200, so roughly 33.2M for 1000. Buying through the WETH Helper.run() returns reverts, and a contract wallet holding Snow still hits SA__InvalidSignature.
Pick the payment path explicitly instead of inferring it, and reject empty purchases:
For L-2 split collectFee into separate WETH and ETH withdrawals, skipping the ETH leg at a zero balance. For L-3 take a bounded quantity argument. For L-4 return DeploySnow's WETH from Helper. For L-5 use OpenZeppelin's SignatureChecker, which falls back to EIP-1271.
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.