Snowman Merkle Airdrop

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

H-1 — `buySnow` overcharges by ~1e18× and mints dust

Root + Impact

The fee is scaled by PRECISION in the constructor and then multiplied by amount again at purchase, while _mint credits amount as raw wei. The payment and mint units disagree by 18 decimals: buying 1 base unit costs 5e18 WETH, and one whole token (1e18 wei) would cost 5e36 WETH. The buy path is economically unusable and buyers overpay by ~1e18.

Description

// src/Snow.sol
36: uint256 constant PRECISION = 10 ** 18;
...
61: constructor(...) {
...
@>73: s_buyFee = _buyFee * PRECISION; // 5 -> 5e18
...
79: function buySnow(uint256 amount) external payable canFarmSnow {
@>80: if (msg.value == (s_buyFee * amount)) { // 5e18 * amount
@>81: _mint(msg.sender, amount); // mints raw wei
82: } else {
@>83: i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
@>84: _mint(msg.sender, amount);
85: }

With _buyFee = 5: s_buyFee = 5e18. buySnow(1) pulls 5e18 WETH and mints 1 wei of Snow.

Risk

Likelihood: High — every buyer hits this path; the existing test itself pays 5e18 for 1 wei.

Impact: High — the sale is mispriced by 18 orders of magnitude; buyers are massively overcharged and the token supply economy is broken.

Proof of Concept

function test_PoC2_BuyOvercharged1e18() public {
_deploy();
address buyer = makeAddr("buyer");
​
uint256 feeWei = snow.s_buyFee();
assertEq(feeWei, 5e18);
​
weth.mint(buyer, feeWei);
vm.startPrank(buyer);
weth.approve(address(snow), feeWei);
snow.buySnow(1);
vm.stopPrank();
​
assertEq(snow.balanceOf(buyer), 1); // 1 wei of Snow
assertEq(weth.balanceOf(buyer), 0); // paid 5e18 WETH
assertEq(feeWei * 1e18, 5e36); // price of one whole token
}

Recommended Mitigation

Make the price and mint units consistent. Treat amount as whole tokens and scale the mint, with the fee as price per token:

// src/Snow.sol
- s_buyFee = _buyFee * PRECISION;
+ s_buyFee = _buyFee; // price per whole token
...
- _mint(msg.sender, amount);
+ _mint(msg.sender, amount * 10 ** decimals());
...
- i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
+ i_weth.safeTransferFrom(msg.sender, address(this), s_buyFee * amount);
- _mint(msg.sender, amount);
+ _mint(msg.sender, amount * 10 ** decimals());

Alternatively, if amount is meant to be base units, remove the * PRECISION scaling entirely. Either way, one convention must be used for both charge and mint.


Updates

Lead Judging Commences

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