Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Incorrect Payment Logic in buySnow Function

Root + Impact

Description

The buySnow function uses a confusing if-else logic to determine payment method, which could lead to users accidentally paying with both ETH and WETH, or using the wrong payment method.

Given a user execute the buySnow function with 1 wei overprice, the contract will accpet the msg.value AND do the weth safeTransferFrom. Causing double spend issue

function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
// ...
}

Risk

Likelihood:

  • High as it's hard to calculate the exact price given the gas consumtion and MEV environment.

Impact:

  • Double spend - User spend both native token and weth token on 1 purchase.

Proof of Concept

  1. User wants to buy Snow tokens with WETH

  2. User accidentally sends some ETH with the transaction

  3. If the ETH amount is not exactly s_buyFee * amount, the transaction will:

    • First try to use the ETH (which will be lost)

    • Then try to transfer WETH

  4. User ends up paying with both ETH and WETH

function testAmbiguousPaymentLogic() public {
address user = makeAddr("user");
uint256 amount = 5;
uint256 cost = FEE * amount;
// 給用戶一些 ETH 和 WETH
deal(user, cost * 3);
weth.mint(user, cost * 3);
vm.startPrank(user);
// 1. Should use ETH to buy snow
snow.buySnow{value: cost}(amount);
assertEq(snow.balanceOf(user), amount);
assertEq(address(snow).balance, cost);
assertEq(weth.balanceOf(address(snow)), 0);
// 2. Should use WETH to buy snow
weth.approve(address(snow), cost);
snow.buySnow(amount);
assertEq(snow.balanceOf(user), amount * 2);
assertEq(weth.balanceOf(address(snow)), cost);
// 3. Incorrect - Will use both WETH and ETH to buy snow
uint256 incorrectCost = cost + 1 wei;
vm.expectRevert();
snow.buySnow{value: incorrectCost}(amount);
vm.stopPrank();
}

Recommended Mitigation

Split the function into two separate functions for different payment methods:

function buySnowWithETH(uint256 amount) external payable canFarmSnow {
require(msg.value == (s_buyFee * amount), "Incorrect ETH amount");
_mint(msg.sender, amount);
emit SnowBought(msg.sender, amount);
}
function buySnowWithWETH(uint256 amount) external canFarmSnow {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
emit SnowBought(msg.sender, amount);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
5 months ago
yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.