Snowman Merkle Airdrop

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

Incorrect Fee Calculation Logic Leading to Overpriced Token Purchases

  • The Snow token is designed to be both earned for free weekly and purchased at a reasonable fee during the farming period. Users can buy tokens with either WETH or native ETH.

  • In the constructor, _buyFee is multiplied by PRECISION (10^18), but this assumes _buyFee is passed as a decimal value. When a reasonable small fee (e.g., 0.001 ETH) is passed, it becomes astronomically large after multiplication, making token purchases economically unviable.

  • Root Cause + Impact

    The constructor incorrectly multiplies the buy fee by PRECISION (10^18), causing token purchases to cost significantly more than intended. This breaks the economic model of the token, making purchases prohibitively expensive

  • https://github.com/CodeHawks-Contests/2025-06-snowman-merkle-airdrop/blob/b63f391444e69240f176a14a577c78cb85e4cf71/src/Snow.sol#L73

Risk

Likelihood: High

  • The deployer will likely pass a small decimal value for _buyFee (e.g., 0.001) to make tokens affordable since they can also be earned for free

  • The multiplication by PRECISION occurs unconditionally in the constructor with no validation

  • This affects every token purchase transaction through the buySnow function

Impact: High

  • Token purchases become prohibitively expensive, breaking the intended dual acquisition model (free earning + affordable purchases)

  • Users who attempt to buy tokens will either fail transactions (insufficient funds) or waste excessive funds

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Snow.sol";
contract SnowTest is Test {
Snow snow;
address collector = makeAddr("collector");
address buyer = makeAddr("buyer");
MockWETH weth;
function setUp() public {
weth = new MockWETH();
snow = new Snow(address(weth), 1e16, collector);
}
function testBuySnowWithETH_shouldFailDueToPrecisionBug() public {
uint256 amount = 1;
uint256 expectedCost =
uint256 actualRequired = snow.s_buyFee() * amount;
console.log("s_buyFee stored in contract:", actualRequired);
assertGt(actualRequired, 1e30);
vm.deal(buyer, 1 ether);
vm.prank(buyer);
vm.expectRevert();
snow.buySnow{value: expectedCost}(amount);
}
function testBuySnowWithWETH_shouldAlsoFail() public {
uint256 amount = 1;
uint256 expectedCost = 1e16
weth.mint(buyer, 1 ether);
vm.prank(buyer);
weth.approve(address(snow), expectedCost);
Buyer only has 1e18 WETH → but transferFrom will revert due to insufficient balance OR allowance
vm.prank(buyer);
vm.expectRevert();
snow.buySnow(amount);
}
function testWhatUserWouldNeedToPay() public {
uint256 impossibleAmount = snow.s_buyFee(); ~1e34
console.log("Required ETH to buy 1 Snow token:", impossibleAmount / 1e18, "ETH");
}
}
contract MockWETH is ERC20("WETH", "WETH") {
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
receive() external payable {}
}

Recommended Mitigation

-s_buyFee = _buyFee * PRECISION;
+ s_buyFee = _buyFee; // DO NOT multiply by PRECISION!
is wrong when _buyFee is already in wei (or wei-equivalent for WETH).
And ensure _buyFee is passed in wei (e.g., 0.01 ether = 1e16).
Updates

Lead Judging Commences

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