Snowman Merkle Airdrop

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

[M-2] Unreasonably High Fee Configuration in Snow contact.

Root + Impact


  • Root: The Snow constructor sets s_buyFee = _buyFee * PRECISION without normalizing for percentage, leading to a high per-token fee;

  • Impact: Users pay an excessive 5 WETH per token, deterring usage or causing financial loss.

Description

  • The Snow constructor accepts _buyFee, scales it by PRECISION (10^18) to set s_buyFee, and uses it in buySnow without adjustment, intending a fee per token.

  • With _buyFee = 5, s_buyFee = 5e18 (5 WETH/token), likely unintended as a flat fee rather than a percentage, resulting in impractical costs.

// Root cause in the codebase with @> marks to highlight the relevant section
constructor(address _weth, uint256 _buyFee, address _collector) ERC20("Snow", "S") Ownable(msg.sender) {
if (_weth == address(0)) {
revert S__ZeroAddress();
}
if (_buyFee == 0) {
revert S__ZeroValue();
}
if (_collector == address(0)) {
revert S__ZeroAddress();
}
@> i_weth = IERC20(_weth);
@> s_buyFee = _buyFee * PRECISION;
s_collector = _collector;
i_farmingOver = block.timestamp + FARMING_DURATION;
}

Risk

Likelihood:

  • During deployment with a default or unadjusted _buyFee value.

  • When users call buySnow expecting a reasonable fee.

Impact:

  • Imposes a high fee (5 WETH/token), reducing adoption.

  • Potential financial loss if users mint at this cost.

Proof of Concept

High Fee Demonstration: The test mints 2 tokens with a fee of 10 WETH, confirming the excessive s_buyFee value.

function testBuySnow() public {
address nasar = makeAddr("nasar");
uint256 buyFee = snow.s_buyFee();
uint256 totalFees = buyFee * 2;
if (block.timestamp >= snow.i_farmingOver()) {
revert S__SnowFarmingOver();
}
vm.startPrank(nasar);
weth.mint(address(nasar), buyFee * 2);
weth.approve(address(snow), buyFee * 2);
snow.buySnow(2);
uint256 finalBalance = snow.balanceOf(nasar);
vm.stopPrank();
console2.log("TotalMinted:", finalBalance);
console2.log("Total fees has been given:", totalFees);
}
  • Result: TotalMinted: 2, Total fees has been given: 10000000000000000000 (10 WETH), showing 5 WETH per token.

Recommended Mitigation

constructor(address _weth, uint256 _buyFee, address _collector) ERC20("Snow", "S") Ownable(msg.sender) {
if (_weth == address(0)) {
revert S__ZeroAddress();
}
if (_buyFee == 0) {
revert S__ZeroValue();
}
if (_collector == address(0)) {
revert S__ZeroAddress();
}
i_weth = IERC20(_weth);
- s_buyFee = _buyFee * PRECISION;
+ s_buyFee = (_buyFee * PRECISION) / 10000; // Assume _buyFee as basis points (e.g., 500 for 5%)
s_collector = _collector;
i_farmingOver = block.timestamp + FARMING_DURATION;
}
  • Normalize _buyFee by dividing by 10000 after scaling, treating it as basis points (e.g., 500 for 5%) to set a reasonable s_buyFee.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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