Snowman Merkle Airdrop

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

Precision Loss in earnSnow Mints 1 Wei Instead of 1 Token

Precision Loss in earnSnow Mints 1 Wei Instead of 1 Token

Description

The Snow contract implements a farming mechanism where users can call earnSnow to receive 1 Snow token as a reward, subject to a 1-week cooldown.

The specific issue is that the function calls _mint(msg.sender, 1). Standard ERC20 tokens (like the OpenZeppelin implementation used here) operate with 18 decimals. Minting the raw integer 1 actually mints 1 wei of the token (0.000000000000000001 Snow). This makes the farmed reward microscopically small, completely breaking the economic intent of the farming function, as the gas spent to claim will always vastly exceed the value of the token received.

function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
revert S__Timer();
}
@> _mint(msg.sender, 1); // Mints 1 wei (10^-18 tokens) instead of 1 full token
s_earnTimer = block.timestamp;
}

Risk

Likelihood:

  • The issue will occur on 100% of successful calls to the earnSnow function.

  • There are no special conditions or prerequisites; the code strictly executes _mint with the value 1.

Impact:

  • The farming reward is effectively zero. Users will waste gas calling the function without receiving a meaningful amount of tokens.

  • The intended token distribution mechanism (farming) is completely broken and will fail to incentivize users.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Snow} from "./Snow.sol";
contract ExploitOP004 {
Snow public snow;
constructor(address _snowAddress) {
snow = Snow(_snowAddress);
}
function verifyBadMint() external {
uint256 balanceBefore = snow.balanceOf(address(this));
// Assume cooldown has passed
snow.earnSnow();
uint256 balanceAfter = snow.balanceOf(address(this));
// balanceAfter - balanceBefore == 1
// In 18-decimal ERC20, 1 == 0.000000000000000001 tokens
require(balanceAfter - balanceBefore == 1, "Minted amount mismatch");
}
}

Recommended Mitigation

Multiply the mint amount by the PRECISION constant (which is already defined in the contract as 10 ** 18) to ensure 1 full token is minted.

function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
revert S__Timer();
}
- _mint(msg.sender, 1);
+ _mint(msg.sender, 1 * PRECISION);
s_earnTimer = block.timestamp;
}

Updates

Lead Judging Commences

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