Snowman Merkle Airdrop

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

ETH Not Refunded

Root + Impact

Snow.buySnow() does not refund native ETH when the WETH purchase path is taken. If a user accidentally sends ETH alongside a WETH transaction, the ETH is permanently stuck in the contract, causing direct loss of user funds.

Description

• Normal behavior: When a user purchases Snow tokens with WETH, any accidentally sent native ETH should be fully refunded to the sender. • Specific issue: In the else branch of buySnow(), the contract pulls WETH via safeTransferFrom but completely ignores msg.value. The ETH is never refunded and remains locked until collectFee() is called by the collector.

Risk

Likelihood: Medium • A user may accidentally send ETH while intending to buy with WETH (e.g., via a frontend bug, copy-paste error, or wallet default behavior). • The function accepts payable ETH, so the compiler allows any msg.value to be sent.

Impact: High • Direct, permanent loss of user funds. The user loses both the stuck ETH and the WETH pulled by safeTransferFrom. • ETH remains in the contract until the collector calls collectFee(), which may happen days or weeks later, leaving the user with no immediate recourse.

Proof of Concept

solidity

function testEthStuckInWethPath() public {
uint256 ethBefore = alice.balance;
weth.approve(address(snow), FEE);
// Alice sends 1 ETH while buying with WETH
snow.buySnow{value: 1 ether}(1);
// ETH is stuck, not refunded
assertEq(alice.balance, ethBefore - 1 ether);
assertEq(address(snow).balance, 1 ether);
}

Recommended Mitigation

solidity

function buySnow(uint256 amount) external payable canFarmSnow {
uint256 totalFee = s_buyFee * amount;
if (msg.value == totalFee) {
_mint(msg.sender, amount);
} else {
if (msg.value > 0) {
(bool success,) = payable(msg.sender).call{value: msg.value}("");
require(success, "ETH refund failed");
}
i_weth.safeTransferFrom(msg.sender, address(this), totalFee);
_mint(msg.sender, amount);
}
s_earnTimer = block.timestamp;
}
Updates

Lead Judging Commences

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