Snowman Merkle Airdrop

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

buySnow accepts a non-exact msg.value yet still pulls the full WETH fee, double-charging the user and locking the ETH

Strict ETH equality in buySnow double-charges users who do not send the exact fee

Description

buySnow is payable and lets a buyer pay either in ETH or in WETH. It branches on a strict equality of msg.value to the fee; any non-exact ETH value falls into the else branch, which pulls the full fee in WETH while the contract silently keeps the sent ETH (only collectFee can later sweep it).

function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) { // @> strict equality; any mismatch -> WETH branch keeps the ETH
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
s_earnTimer = block.timestamp;
emit SnowBought(msg.sender, amount);
}

Risk

Likelihood:

Any overpayment by even 1 wei, or ETH accidentally co-sent with a WETH-intended call, triggers the else branch. Exact-wei payment is fragile and easy to get wrong.

Impact:

The user is charged twice: their sent ETH is stranded in the contract with no refund path, and the full WETH fee is also pulled. Funds are lost to the buyer until a privileged collectFee sweep.

Proof of Concept

Overpaying ETH by 1 wei still pulls the WETH fee and keeps the ETH.

function test_overpayDoubleCharges() public {
uint256 fee = snow.s_buyFee();
vm.deal(user, fee + 1);
vm.prank(user);
snow.buySnow{value: fee + 1}(1); // ETH stuck AND weth pulled
assertEq(address(snow).balance, fee + 1);
}

Recommended Mitigation

Reject inexact ETH and require zero msg.value on the WETH path.

- if (msg.value == (s_buyFee * amount)) {
+ if (msg.value != 0 && msg.value != s_buyFee * amount) revert S__IncorrectEthAmount();
+ if (msg.value == s_buyFee * amount) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 5 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!