Snowman Merkle Airdrop

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

Incorrect fee scaling multiplies buyFee by 1e18 causing impossible Snow purchases

Root + Impact

Description

  • The protocol allows users to buy Snow tokens by paying a predefined buyFee per token using ETH or WETH.

  • However, during initialization, the buyFee is incorrectly multiplied by an additional 1e18, even though it is already denominated in wei. This results in an astronomically inflated fee, making token purchases economically infeasible.

// Root cause in the codebase with @> marks to highlight the relevant section
uint256 constant PRECISION = 10 ** 18;
constructor(address _weth, uint256 _buyFee, address _collector) ERC20("Snow", "S") Ownable(msg.sender) {
...
@> s_buyFee = _buyFee * PRECISION;
}
function buySnow(uint256 amount) external payable {
@> uint256 cost = s_buyFee * amount;
if (msg.value == cost) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), cost);
_mint(msg.sender, amount);
}
}

Risk

Likelihood:

  • Occurs on every deployment since constructor logic always applies the incorrect scaling

  • Triggered on every call to buySnow, affecting all users attempting to purchase tokens

Impact:

  • Users are required to pay amounts exceeding realistic ETH/WETH supply

  • Core functionality (buying Snow tokens) becomes unusable, effectively causing a denial of service


Proof of Concept

The test below compares the expected fee (based on the input `BUY_FEE`) with the actual fee used by the contract (`s_buyFee`).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Snow.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockWETH is ERC20 {
constructor() ERC20("Wrapped Ether", "WETH") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract SnowTest is Test {
Snow snow;
MockWETH weth;
address alice = address(0xA11CE);
address bob = address(0xB0B);
address collector = address(0xC011E);
uint256 BUY_FEE = 0.01 ether;
function setUp() public {
weth = new MockWETH();
vm.deal(alice, 100 ether);
vm.deal(bob, 100 ether);
weth.mint(alice, 50 ether);
weth.mint(bob, 50 ether);
snow = new Snow(address(weth), BUY_FEE, collector);
}
function test_exploit() external {
vm.startPrank(alice);
uint256 amount = 1;
uint256 actual = snow.s_buyFee() * amount;
uint256 expected = BUY_FEE * amount;
console.log("Expected:", expected);
console.log("Actual:", actual);
console.log("Ratio:", actual / expected);
}
}

Recommended Mitigation

- s_buyFee = _buyFee * PRECISION;
+ s_buyFee = _buyFee;

If additional precision is required, it should be consistently applied during calculations rather than during storage.

Updates

Lead Judging Commences

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