Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Uncapped Minting and Inflation Risk via Public Functions

Root + Impact

Description

Describe the normal behavior

Under expected ERC20 standards, minting should be restricted to approved parties, with caps, emission controls, or governed inflation schedules to protect token value and prevent abuse. These controls ensure fairness and long-term sustainability of the token economy.

// 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)

Whenever a user calls buySnow() or earnSnow(), tokens are minted without supply caps or rate-limiting logic, allowing indefinite inflation.

  • Reason 2

Since buySnow() and earnSnow() are externally accessible and not bound to specific user roles or caps, any user can repeatedly trigger them, intentionally or unintentionally.

Impact:

  • Impact 1

Uncapped inflation can devalue the Snow token, harming investor trust and disrupting protocol economics.

  • Impact 2

Abuse of the minting path can result in griefing, denial of rewards, or exploitation of downstream DeFi integrations that rely on a stable token supply.

Proof of Concept

Minimal Solidity PoC: Uncapped Minting via buySnow()

solidity

CopyEdit

// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; interface ISnow { function buySnow(uint256 amount) external payable; function balanceOf(address account) external view returns (uint256); } interface IWETH { function approve(address spender, uint256 amount) external returns (bool); function transfer(address to, uint256 amount) external returns (bool); } contract SnowMintPoC { ISnow public snow; IWETH public weth; constructor(address _snow, address _weth) { snow = ISnow(_snow); weth = IWETH(_weth); } function attackWithWETH() external { // Approve Snow contract to pull WETH weth.approve(address(snow), type(uint256).max); // Buy 1,000,000 SNOW tokens uint256 mintAmount = 1_000_000 ether; snow.buySnow(mintAmount); // No cap, no access control } function viewBalance(address attacker) external view returns (uint256) { return snow.balanceOf(attacker); } }


Explanation

  • This contract simulates an attacker using buySnow() to mint 1,000,000 SNOW tokens.

  • It assumes the attacker has enough WETH and has pre-approved the Snow contract.

  • The Snow contract has no mint limit, so the call succeeds every time.


Result

After deploying and calling attackWithWETH():

  • Attacker receives 1,000,000 SNOW tokens in a single transaction

  • Repeating this call mints more and more SNOW with no restriction

  • Total supply grows infinitely, breaking tokenomics

Recommended Mitigation

1. Introduce a Maximum Token Supply Cap

You must enforce a hard cap to prevent infinite minting.
Add this to the contract:

solidity

CopyEdit

uint256 public constant MAX_SUPPLY = 1_000_000 ether; // Example cap

Then update both minting functions to check the cap before minting:

solidity

CopyEdit

function buySnow(uint256 amount) external payable canFarmSnow { if (msg.value == (s_buyFee * amount)) { _enforceCap(amount); _mint(msg.sender, amount); } else { i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount)); _enforceCap(amount); _mint(msg.sender, amount); } s_earnTimer = block.timestamp; emit SnowBought(msg.sender, amount); } function earnSnow() external canFarmSnow { if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) { revert S__Timer(); } _enforceCap(1); _mint(msg.sender, 1); s_earnTimer = block.timestamp; emit SnowEarned(msg.sender, 1); } function _enforceCap(uint256 amountToMint) internal view { if (totalSupply() + amountToMint > MAX_SUPPLY) { revert("Mint would exceed max supply"); } }


2. Add Access Control if Minting Should Be Privileged

If you don’t want public minting at all, restrict it with onlyOwner or AccessControl:

solidity

CopyEdit

function mint(address to, uint256 amount) external onlyOwner { _enforceCap(amount); _mint(to, amount); }

You can also use MinterRole if integrating with a DAO or emissions scheduler.


3. (Optional) Add Anti-Sybil & Bot Protections

  • Prevent multiple wallets from calling earnSnow() by tracking per-user earn history (mapping address => timestamp)

  • Add cooldown periods and non-reentrancy to public minting functions

  • Consider Merkle Tree claims or staking models to earn tokens


Why This Is Critical

Without a cap:

  • Total supply can balloon indefinitely

  • Liquidity pools can be drained

  • Token value will collapse (zero-trust economy)

- remove this code
+ add this code
Updates

Lead Judging Commences

yeahchibyke Lead Judge 27 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.