Snowman Merkle Airdrop

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

buySnow Exact-Match Check Causes Double-Payment and Permanent ETH Loss for Overpaying Users

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • Explain the specific issue or problem in one or more sentences

### Description
The `buySnow` function checks if user-submitted capital matches fees exactly via `msg.value == s_buyFee * amount`.
If a user sends even 1 wei above the exact required amount, the validation condition triggers the `else` logic block. In this fallback branch, the contract pulls the entire fee value in `WETH` directly from the user's wallet anyway. However, the original excess `ETH` sent within `msg.value` remains completely trapped inside the contract state because no refund mechanisms exist.
The user pays twice for a single transaction—once in trapped `ETH` and once in pulled `WETH`—while receiving only one batch of tokens.
### Risk
Medium. Users lose their entire `ETH` payment permanently due to simple rounding or overpayment mistakes. The asset loss occurs silently without triggering a transaction revert or recovery path, allowing the contract controller to draw an unintended windfall profit at the user's expense.
### Likelihood
Medium. Web3 frontends or manual transaction execution tools often over-estimate or round gas/value pairs slightly, making accidental overpayment a common real-world occurrence.
### Proof of Concept
Add this test instance to your suite to witness the silent asset drain:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/Snow.sol";
import "../src/WETH.sol";
contract SnowDoublePaymentTest is Test {
Snow public snow;
WETH public weth;
address public user = address(0x3);
address public collector = address(0x4);
function test_BuySnowDoublePayment() public {
// User approves WETH spending boundaries
vm.prank(user);
weth.approve(address(snow), type(uint256).max);
// User accidentally overpays by 1 wei
uint256 initialWeth = weth.balanceOf(user);
vm.prank(user);
snow.buySnow{value: 10 ether + 1}(10);
// User gets Snow tokens but paid twice
assertEq(snow.balanceOf(user), 10);
assertEq(weth.balanceOf(user), initialWeth - 10 ether);
assertEq(address(snow).balance, 10 ether + 1);
// Collector sweeps the user's trapped ETH
vm.prank(collector);
snow.collectFee();
assertEq(address(collector).balance, 10 ether + 1);
}
}
```
Run verification via command line:
```bash
forge test --match-test test_BuySnowDoublePayment -vvv
```
### Tools Used
Manual Review, Foundry Unit Testing.
### Recommended Mitigation
Remove the exact-match requirement condition. Accept loose `msg.value` submissions and calculate and return excess balances directly to the sender.
Modify the fee gate inside `Snow.sol` as shown below:
```solidity
uint256 requiredFee = s_buyFee * amount;
if (msg.value >= requiredFee) {
uint256 excess = msg.value - requiredFee;
if (excess > 0) {
(bool success, ) = msg.sender.call{value: excess}("");
require(success, "Refund failed");
}
_mint(msg.sender, amount);
} else {
// WETH fallback routing logic remains unchanged
}
```
// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

  • Impact 1

  • Impact 2

Proof of Concept

Recommended Mitigation

- remove this code
+ add this code
Updates

Lead Judging Commences

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