Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: low
Likelihood: medium
Invalid

Missing zero-value check in claim function

Root + Impact

Description

Expected behavior:
Function should revert if faucetDrip == 0.

Actual behavior:

Allows zero-token claims wasting gas.

@> _transfer(address(this), faucetClaimer, faucetDrip);

Risk

Likelihood:

  • While this isn’t a critical vulnerability, it is a common edge case that can occur in real-world use if token allocation logic fails or is dynamically calculated.

Impact:

  • Wastes gas, clogs event logs.

Proof of Concept

The PoC shows that claimFaucetTokens() proceeds when faucetDrip == 0, performing an internal _transfer(address(this), claimer, 0) that succeeds, emits a Transfer event with value 0, wastes gas, and produces misleading logs — instead of reverting or skipping the call.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/// @notice Minimal ERC-20-like token + faucet combined to reproduce Finding 8
contract RaiseBoxFaucetMock {
// ERC20 storage
string public name = "RaiseBoxMock";
string public symbol = "RBM";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 value);
// Faucet-specific
uint256 public faucetDrip; // amount to drip per claim (can be zero)
address public owner;
constructor(uint256 initialContractBalance, uint256 _faucetDrip) {
owner = msg.sender;
faucetDrip = _faucetDrip;
// Mint tokens to this contract (simulate faucet reserve)
_mint(address(this), initialContractBalance);
}
// Minimal internal mint
function _mint(address to, uint256 amount) internal {
require(to != address(0), "mint to zero");
totalSupply += amount;
balanceOf[to] += amount;
emit Transfer(address(0), to, amount);
}
// Minimal _transfer implementation (intentionally mirrors many ERC20 implementations)
function _transfer(address from, address to, uint256 amount) internal {
require(from != address(0), "transfer from zero");
require(to != address(0), "transfer to zero");
// NOTE: no explicit check to revert on amount == 0 — many ERC20 implementations allow 0 transfers
require(balanceOf[from] >= amount, "insufficient balance");
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, amount);
}
/// @notice The function under test — mirrors your finding:
/// it calls `_transfer(address(this), msg.sender, faucetDrip)` without checking faucetDrip > 0
function claimFaucetTokens() public {
address faucetClaimer = msg.sender;
// Some arbitrary checks (kept minimal)
require(faucetClaimer != address(0), "zero addr");
// This is the line from your finding — no zero check:
_transfer(address(this), faucetClaimer, faucetDrip);
// In a real contract there may be more logic here (timestamp updates, eth drips, etc.)
}
// Helper: owner can set faucetDrip (so tests can set it to zero)
function setFaucetDrip(uint256 _drip) external {
require(msg.sender == owner, "only owner");
faucetDrip = _drip;
}
// Helper to top-up faucet for tests
function topUp(uint256 amount) external {
// For brevity, we allow anyone to mint into the contract in this mock.
_mint(address(this), amount);
}
}
/// @notice PoC claimer contract that simply calls claimFaucetTokens()
contract PoCClaimer {
RaiseBoxFaucetMock public faucet;
event Claimed(address indexed claimer, uint256 balanceBefore, uint256 balanceAfter);
constructor(address faucetAddress) {
faucet = RaiseBoxFaucetMock(faucetAddress);
}
/// @notice call faucet.claimFaucetTokens() and emit local balances so off-chain test can assert behavior
function startClaim() external {
uint256 before = faucet.balanceOf(address(this));
faucet.claimFaucetTokens();
uint256 after = faucet.balanceOf(address(this));
emit Claimed(address(this), before, after);
}
}

Recommended Mitigation

Place it before the transfer logic to prevent the function from proceeding when there is no value to transfer.

- mapping(address => bool) hasClaimedEth;
+ mapping(address => uint256) lastEthClaimDay; Reset daily claim per cycle.
Updates

Lead Judging Commences

inallhonesty Lead Judge 9 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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