Snowman Merkle Airdrop

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

[M] The payment method in buySnow should be a choice of one of two options; the payment logic is flawed.

Root + Impact

Description

The contract does not enforce mutually exclusive ETH/WETH payment modes in buySnow, which can cause users to be charged full WETH while also losing sent ETH when msg.value is non-zero but not exactly equal to price.

// Root cause in the codebase with @> marks to highlight the relevant section
function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
@> } else {
//...
}
}

Risk

Likelihood: Medium

  • Although the payment logic flaw is straightforward at contract level, most users interact through the official dApp flow, which typically constrains payment inputs and reduces accidental misuse. Exploitation is still possible via direct contract calls, integrator bugs, or non-standard frontends, but less likely in normal user paths.

Impact: Medium

  • Users may overpay (ETH + full WETH) due to ambiguous payment-path handling, causing direct but bounded financial loss.

Proof of Concept

victory simultaneously uses ETH and WETH to execute buySnow, verifying that the native token balance and the WETH balance both decreased

function test_Poc_PaymethodError() public {
//
weth.mint(victory, FEE);
uint256 beforeBalanceForETH = victory.balance;
uint256 beforeBalanceForWETH = weth.balanceOf(victory);
console2.log("Before balances - ETH:", beforeBalanceForETH, "WETH:", beforeBalanceForWETH);
vm.startPrank(victory);
weth.approve(address(snow), FEE);
snow.buySnow{value: FEE - 1}(1);
vm.stopPrank();
uint256 afterBalanceForETH = victory.balance;
uint256 afterBalanceForWETH = weth.balanceOf(victory);
console2.log("After balances - ETH:", afterBalanceForETH, "WETH:", afterBalanceForWETH);
assertLt(afterBalanceForETH, beforeBalanceForETH);
assertLt(afterBalanceForWETH, beforeBalanceForWETH);
}

Recommended Mitigation

The payment methods for WETH and ETH are determined completely separately; if 0 < msg.value < price or msg.value > price, the transaction reverts immediately.

function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
- } else {
+ } else if(msg.value == 0) {
- }
+ } else {revert S__NotAllowed();}
Updates

Lead Judging Commences

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