Snowman Merkle Airdrop

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

buySnow keeps mismatched ETH and also pulls the full price in WETH, charging the buyer twice

Root + Impact

When `msg.value` does not exactly equal `s_buyFee * amount`, `buySnow` falls into the WETH branch without reverting or refunding the ETH that was sent. The buyer pays the full price in WETH and loses the ETH, which `collectFee` later sweeps to the collector.

Description

if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
// @> msg.value can be non-zero here and is silently kept
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}

Risk

Likelihood:

* The user sends a slightly wrong ETH amount (rounding, frontend bug) while holding a WETH approval to the contract.

Impact:

* The user loses all the ETH sent and is charged the full price again in WETH.

Proof of Concept

function test_M1_buySnowKeepsEthAndAlsoChargesWeth() public {
uint256 price = snow.s_buyFee() * 2;
address buyer = makeAddr("buyer");
// WETH actually used by Snow (i_weth, storage slot 9)
MockWETH weth = MockWETH(address(uint160(uint256(vm.load(address(snow), bytes32(uint256(9)))))));
vm.deal(buyer, price);
weth.mint(buyer, price);
​
vm.startPrank(buyer);
weth.approve(address(snow), price);
snow.buySnow{value: price - 1}(2); // 1 wei short
vm.stopPrank();
​
assertEq(snow.balanceOf(buyer), 2);
assertEq(buyer.balance, 1); // ETH taken
assertEq(weth.balanceOf(buyer), 0); // WETH taken too
}

Recommended Mitigation

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

Lead Judging Commences

ai-first-flight-judge Lead Judge 9 minutes 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!