Snowman Merkle Airdrop

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

Incorrect token minting due to missing decimal scaling

Incorrect token minting due to missing decimal scaling

Description

Under normal behavior, users paying for SNOW tokens should receive the corresponding number of tokens denominated in the ERC20’s base units (typically 18 decimals). For example, paying for 5 SNOW should mint 5 * 10^18 base units.

However, the buySnow function mints amount directly without scaling by decimals(). As a result, if a user enters amount = 5, they are minted only 5 base units (wei) of SNOW instead of 5 * 10^18. Users are therefore charged for full tokens but receive nearly zero on-chain balance.

// Root cause
@> if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
}
// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • This occurs whenever a user enters a human-readable token quantity instead of already-scaled base units.

  • The contract consistently under-mints because it does not multiply by 10^decimals before calling _mint.

Impact:

  • Users overpay for tokens and receive negligible amounts on-chain.

  • Protocol behavior does not match economic intent and can lead to confusion, complaints, or loss of trust.

Proof of Concept

function testMintingDecimalsBug() public {
// Jerry wants 5 SNOW tokens
uint256 snowAmount = 5;
// Record balances before purchase
uint256 jerrySnowBefore = snow.balanceOf(jerry);
uint256 snowContractEthBefore = address(snow).balance;
// Jerry approves WETH for snow purchase
vm.startPrank(jerry);
weth.approve(address(snow), FEE * snowAmount);
// Buy Snow using WETH
snow.buySnow(snowAmount);
vm.stopPrank();
uint256 jerrySnowAfter = snow.balanceOf(jerry);
console2.log("SNOW balance minted:", jerrySnowAfter - jerrySnowBefore);
// Expectation check
// Ideally Jerry should have received 5*1e18, but due to missing decimal scaling, he only receives 5 wei
assert(jerrySnowAfter - jerrySnowBefore == snowAmount); // Shows the under-mint
assert(address(snow).balance == snowContractEthBefore); // ETH not used here
assert(weth.balanceOf(address(snow)) == FEE * snowAmount); // WETH collected
}

Recommended Mitigation

## Recommended Mitigation
The `_mint` call should scale the human-readable token `amount` by the token’s decimals (usually 1e18 for 18-decimal ERC20s) so that users receive the correct number of base units corresponding to the tokens they paid for.
```diff
- _mint(msg.sender, amount);
+ _mint(msg.sender, amount * 1e18);
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 12 days 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!