Snowman Merkle Airdrop

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

Snow::buySnow price is scaled by an extra 1e18, one raw unit of Snow costs _buyFee whole ETH

Root + Impact

Description

  • Snow is an ERC20 with 18 decimals that can be bought with ETH or WETH before farming ends, and the fee is meant to be a price per token.

  • The constructor stores s_buyFee = _buyFee * 1e18 and buySnow charges s_buyFee * amount while it mints amount raw units, so one raw unit of Snow (1e-18 token) costs _buyFee whole ETH (5 ETH with the deployment fee of 5). Buying one whole token (1e18 units) would cost 5e36 wei, which nobody can pay, so the purchase path is effectively unusable and the price is wrong by a factor of 1e18.

// src/Snow.sol
s_buyFee = _buyFee * PRECISION; // @> scaled by 1e18 in the constructor
​
function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) { // @> 1e18-scaled fee multiplied by raw units
_mint(msg.sender, amount); // @> mints raw units
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
...
}// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Affects every purchase, the formula is evaluated on every buySnow call.

  • The project tests only buy 1 raw unit for FEE = 5e18, which hides the problem.

Impact:

  • Buying Snow is economically impossible except for dust amounts, so the documented purchase path does not work.

  • Anyone who buys a tiny amount overpays by a factor of 1e18 compared with a per-token price.

Proof of Concept

The test uses the project deployment with fee 5. Buying 1 raw unit of Snow requires exactly 5 ETH. The test passes on the unmodified code.

function test_H5_buyPriceScaling() public {
assertEq(snow.s_buyFee(), 5 * 1e18);
vm.deal(attacker, 5 ether);
vm.prank(attacker);
snow.buySnow{value: 5 ether}(1); // 1 raw unit of Snow
assertEq(snow.balanceOf(attacker), 1);
assertEq(address(snow).balance, 5 ether); // paid 5 ETH for 1e-18 Snow
}
// Result: PASS

Recommended Mitigation

Divide by PRECISION when computing the price, so that the fee is a price per whole token.

- if (msg.value == (s_buyFee * amount)) {
+ uint256 cost = (s_buyFee * amount) / PRECISION;
+ if (msg.value == cost) {
_mint(msg.sender, amount);
} else {
- i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
+ i_weth.safeTransferFrom(msg.sender, address(this), cost);
_mint(msg.sender, amount);
}- remove this code
+ add this code
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!