Snowman Merkle Airdrop

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

Micro Reward Vulnerability

Root + Impact

Description

  • Normal behavior: The earnSnow() function is intended to reward users with 1 SNOW token for participating in the farming mechanism, after a cooldown period. Since SNOW is an ERC20 token with 18 decimals, minting 1 full token should actually mint 1 * 10**18 wei units.

  • problem: The function mints 1 wei instead of 1 * 10**18 wei. This happens because the developer mistakenly used the literal 1 without multiplying by the PRECISION constant (which equals 10**18). As a result, users receive an insignificant amount—only one‑billionth of a billionth of a token—making the reward effectively worthless.

// Root cause in the codebase with @> marks to highlight the relevant section
function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1); // @> mints only 1 wei instead of 1 full token
s_earnTimer = block.timestamp;
}

Risk

Likelihood:

  • The function is callable by any user once per week during the farming period (12 weeks after deployment).

  • Every user who calls earnSnow() will receive exactly 1 wei of SNOW tokens.

  • The condition block.timestamp < s_earnTimer + 1 weeks ensures it can be called at most once per week, but the amount minted is always the same negligible value.

Impact:

  • Users are misled into believing they earn a full token, while in reality they obtain a fraction so small that it has no economic value (less than 10⁻¹⁸ of a token).

  • The protocol’s intended incentive mechanism is broken, leading to loss of user trust and potential abandonment of the farming feature.

  • If the token is traded on a DEX, 1 wei is essentially worthless and cannot be used for any meaningful transaction (e.g., swapping, providing liquidity).

Proof of Concept

The following Foundry test demonstrates that after calling earnSnow(), the user’s balance is exactly 1 wei, and the number of full tokens (balance / 1e18) is zero.

// 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 weth = address(0x123);
address collector = address(0x456);
uint256 buyFee = 1;
function setUp() public {
snow = new Snow(weth, buyFee, collector);
}
function testEarnSnowMintsOneWei() public {
address user = address(0x789);
vm.prank(user);
snow.earnSnow();
uint256 balance = snow.balanceOf(user);
console.log("User balance after earnSnow (in wei):", balance);
assertEq(balance, 1, "Balance should be exactly 1 wei");
uint256 fullTokens = balance / 10**18;
console.log("Full tokens (balance / 1e18):", fullTokens);
assertEq(fullTokens, 0, "Full tokens should be 0");
}
}

Test output:

Ran 1 test for test/Snow4.t.sol:SnowTest
[PASS] testEarnSnowMintsOneWei() (gas: 83913)
Logs:
User balance after earnSnow (in wei): 1
Full tokens (balance / 1e18): 0
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 27.28ms (3.03ms CPU time)
Ran 1 test suite in 49.38ms (27.28ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Recommended Mitigation

Replace the hardcoded 1 with 1 * PRECISION (or simply PRECISION if one full token is intended) to mint a meaningful amount.

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); // mints 1 full SNOW token (1e18 wei)
s_earnTimer = block.timestamp;
}
Updates

Lead Judging Commences

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