Snowman Merkle Airdrop

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

Double Payment Vulnerability: Users Lose Both ETH and WETH When Payment Amount Mismatches in `snow:buySno`

Root: The buySnow function contains flawed payment logic where the else branch executes safeTransferFrom for WETH regardless of whether ETH was sent, without refunding any sent ETH that doesn't match the exact required amount.

Impact: Users who send incorrect ETH amounts lose both their sent ETH (permanently trapped in contract) and their WETH tokens, effectively paying double the intended price while only receiving tokens equivalent to a single payment.


Risk

Likelihood: High

  • Users commonly send incorrect ETH amounts due to calculation errors, UI bugs, or misunderstanding of exact fee requirements

  • The function accepts any ETH amount without validation, making accidental incorrect payments inevitable during normal usage

  • No warning or protection mechanism exists to prevent users from losing funds when they send wrong amounts

Impact: High

  • Financial Loss: Users lose 100% of sent ETH plus additional WETH tokens, potentially doubling their intended payment

  • Permanent Fund Lock: Sent ETH becomes permanently trapped in the contract with no recovery mechanism

  • Protocol Trust Damage: Users experiencing unexpected double charges will lose confidence in the protocol's reliability and security

Proof of Concept

Add the following test to TestSnow.t.sol and Run

function testDoublePaymentVulnerability() public {
uint256 tokenAmount = 1;
uint256 correctFee = FEE * tokenAmount;
uint256 incorrectEthSent = correctFee + 0.1 ether; // Send more ETH than required
// Setup: Give user WETH and approve it
weth.mint(jerry, correctFee);
vm.startPrank(jerry);
weth.approve(address(snow), correctFee);
// Record balances before transaction
uint256 jerryWethBefore = weth.balanceOf(jerry);
uint256 contractEthBefore = address(snow).balance;
uint256 contractWethBefore = weth.balanceOf(address(snow));
// Give jerry enough ETH to send incorrect amount
deal(jerry, incorrectEthSent);
// User sends incorrect ETH amount (more than required)
snow.buySnow{value: incorrectEthSent}(tokenAmount);
vm.stopPrank();
// Verify double payment occurred
assert(jerry.balance == 0); // All ETH taken
assert(weth.balanceOf(jerry) == jerryWethBefore - correctFee); // WETH also taken
assert(address(snow).balance == contractEthBefore + incorrectEthSent); // Contract keeps ETH
assert(weth.balanceOf(address(snow)) == contractWethBefore + correctFee); // Contract gets WETH
assert(snow.balanceOf(jerry) == tokenAmount); // User only gets single amount of tokens
}

Recommended Mitigation

Add better check to the function.

function buySnow(uint256 amount) external payable canFarmSnow {
+ uint256 totalCost = s_buyFee * amount;
+
+ if (msg.value > 0) {
+ // ETH payment path
+ if (msg.value != totalCost) {
+ revert S__IncorrectEthAmount();
+ }
+ _mint(msg.sender, amount);
+ } else {
+ // WETH payment path
+ i_weth.safeTransferFrom(msg.sender, address(this), totalCost);
+ _mint(msg.sender, amount);
+ }
- 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);
- }
s_earnTimer = block.timestamp;
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.