Raisebox Faucet

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

# Dangerous Use of `block.timestamp` in `claimFaucetTokens`

Description

Contract Reference: RaiseBoxFaucet.sol

The claimFaucetTokens function uses block.timestamp for critical comparisons to enforce cooldowns and reset daily limits, as flagged by Slither. Specifically, it relies on block.timestamp in three places:

  • Checking the user’s claim cooldown: block.timestamp < (lastClaimTime[faucetClaimer] + CLAIM_COOLDOWN) (line 168).

  • Determining if a new day has started for ETH drips: currentDay > lastDripDay where currentDay = block.timestamp / 24 hours (line 189).

  • Resetting the daily claim count: block.timestamp > lastFaucetDripDay + 1 days (line 221).

While block.timestamp is generally reliable in Ethereum, it can be manipulated by miners to a limited extent (typically within a small range, e.g., 15–30 seconds). In the context of a Sepolia testnet faucet, such manipulation could allow attackers to bypass cooldowns or reset daily limits slightly early, potentially claiming more tokens or ETH than intended. However, the risk is low due to the testnet environment and the limited financial value of the assets.

Slither Reference: Dangerous Use of Timestamp

Affected Code:

function claimFaucetTokens() public {
faucetClaimer = msg.sender;
if (block.timestamp < (lastClaimTime[faucetClaimer] + CLAIM_COOLDOWN)) {
revert RaiseBoxFaucet_ClaimCooldownOn();
}
// ... other checks ...
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
}
// ... ETH drip logic ...
} else {
dailyDrips = 0;
}
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}

Severity

Low
{ Impact — Low, Likelihood — Low }

  • Impact: Low, as the potential manipulation of block.timestamp allows only marginal bypasses of cooldowns or daily resets, resulting in minimal additional token/ETH claims. The testnet context (Sepolia) further reduces impact, as assets have no real-world value.

  • Likelihood: Low, as timestamp manipulation requires miner collusion or control, which is less feasible on Sepolia due to its testnet nature and lower incentives for miners to manipulate timestamps compared to mainnet.

Risk

  • Likelihood of Exploitation: Low. Timestamp manipulation requires a malicious miner to adjust block.timestamp within a small window (e.g., 15–30 seconds). On Sepolia, miners have little incentive to collude or manipulate timestamps, as the faucet distributes testnet assets with no financial value. Additionally, the manipulation window is narrow, limiting the feasibility of widespread abuse.

  • Potential Consequences:

    • Cooldown Bypass: An attacker could claim tokens slightly before the CLAIM_COOLDOWN (e.g., 1 day) expires, potentially claiming multiple times in a short period.

    • Early Daily Reset: Manipulating block.timestamp to make currentDay > lastDripDay or block.timestamp > lastFaucetDripDay + 1 days could reset dailyDrips or dailyClaimCount early, allowing additional claims within the daily limit (dailyClaimLimit = 100).

    • Limited Gain: The maximum gain is constrained by the daily claim limit and faucet supply, making large-scale exploitation impractical.

  • Contextual Factors: As a Sepolia testnet faucet, the financial stakes are negligible. The primary risk is operational disruption, where legitimate users might face reduced access if an attacker claims tokens/ETH slightly more frequently. The issue is less critical than in mainnet contracts handling valuable assets.

Impact

  • Operational Disruption: Timestamp manipulation could allow an attacker to bypass the CLAIM_COOLDOWN or reset daily limits early, claiming additional tokens/ETH within a short time frame, potentially exhausting the daily claim limit (100 claims) or token/ETH supply faster.

  • Fairness: Early claims or resets could reduce availability for legitimate users, undermining the faucet’s purpose of fair testnet resource distribution.

  • Limited Scope: The impact is capped by the daily claim limit, faucet supply (1000000e18 tokens, dailySepEthCap = 1 ether), and the testnet context, where assets have no real value.

  • Testnet Context: In Sepolia, the primary concern is developer experience, not financial loss. The issue could frustrate users if claims are unfairly distributed but does not pose a security risk.

Tools Used

  • Slither: Detected dangerous use of block.timestamp for comparisons in claimFaucetTokens (see Slither output for details).

  • Manual Review: Confirmed the reliance on block.timestamp for cooldowns and daily resets.

Recommendations

  • Use Block Number Instead: Replace block.timestamp with block.number for cooldowns and daily resets, as block numbers are less susceptible to manipulation. For example, use an approximate block count for 1 day (e.g., ~5760 blocks on Sepolia, assuming 15-second block times). Update CLAIM_COOLDOWN, lastClaimTime, lastDripDay, and lastFaucetDripDay to use block numbers.

  • Validate Timestamp Manipulation: Add checks to limit the impact of timestamp manipulation, e.g., ensure block.timestamp is not unreasonably far in the future (though this is less critical in testnets).

  • Updated Function Example:

    // Define constants for block-based timing (approximate, adjust for Sepolia)
    uint256 public constant CLAIM_COOLDOWN_BLOCKS = 5760; // ~1 day at 15s/block
    uint256 public constant ONE_DAY_BLOCKS = 5760;
    // Update state variables to use block numbers
    mapping(address => uint256) public lastClaimBlock;
    uint256 public lastDripBlock;
    uint256 public lastFaucetDripBlock;
    function claimFaucetTokens() public {
    faucetClaimer = msg.sender;
    // Use block.number for cooldown
    if (block.number < (lastClaimBlock[faucetClaimer] + CLAIM_COOLDOWN_BLOCKS)) {
    revert RaiseBoxFaucet_ClaimCooldownOn();
    }
    // ... other checks ...
    if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
    uint256 currentDay = block.number / ONE_DAY_BLOCKS;
    if (currentDay > lastDripBlock) {
    lastDripBlock = currentDay;
    dailyDrips = 0;
    }
    // ... ETH drip logic ...
    } else {
    dailyDrips = 0;
    }
    if (block.number > lastFaucetDripBlock + ONE_DAY_BLOCKS) {
    lastFaucetDripBlock = block.number;
    dailyClaimCount = 0;
    }
    lastClaimBlock[faucetClaimer] = block.number;
    dailyClaimCount++;
    _transfer(address(this), faucetClaimer, faucetDrip);
    emit Claimed(msg.sender, faucetDrip);
    }
  • Considerations:

    • Calculate CLAIM_COOLDOWN_BLOCKS and ONE_DAY_BLOCKS based on Sepolia’s average block time. Monitor for chain upgrades affecting block times.

    • Using block numbers increases gas costs slightly due to additional calculations but enhances security.

    • If timestamps are retained, document the acceptable manipulation window (e.g., 30 seconds) and its low impact in a testnet context.

Proof of Concept

The dangerous use of block.timestamp in claimFaucetTokens can be demonstrated conceptually:

  1. Setup: The contract is deployed on Sepolia with dailyClaimLimit = 100, faucetDrip = 100e18, sepEthAmountToDrip = 0.01 ether, dailySepEthCap = 1 ether, and CLAIM_COOLDOWN = 1 day. Assume lastClaimTime[attacker] = 0, lastDripDay = 0, lastFaucetDripDay = 0.

  2. Cooldown Bypass:

    • An attacker calls claimFaucetTokens at block.timestamp = T, receiving 100e18 tokens and 0.01 ether.

    • The contract sets lastClaimTime[attacker] = T. Normally, the attacker must wait CLAIM_COOLDOWN = 1 day to claim again.

    • A malicious miner sets block.timestamp = T + 1 day - 30 seconds in the next block. The check block.timestamp < (lastClaimTime[attacker] + CLAIM_COOLDOWN) passes, allowing the attacker to claim again slightly early, receiving another 100e18 tokens.

  3. Early Daily Reset:

    • Assume lastFaucetDripDay = T / 24 hours. A miner sets block.timestamp = T + 1 day - 30 seconds, making block.timestamp > lastFaucetDripDay + 1 days pass early.

    • The contract resets dailyClaimCount = 0, allowing additional claims within the same 24-hour period (up to dailyClaimLimit).

    • Similarly, currentDay > lastDripDay could reset dailyDrips early, enabling extra ETH claims.

  4. Impact Demonstration:

    • The attacker claims slightly more frequently than intended (e.g., every 23 hours 59 minutes instead of 24 hours), potentially exhausting the daily limit or token/ETH supply faster.

    • In a testnet, this disrupts fair distribution but has no financial impact.

  5. Fix Demonstration:

    • Using block.number with CLAIM_COOLDOWN_BLOCKS = 5760 (1 day ≈ 5760 blocks at 15s/block), the cooldown and reset checks (block.number < (lastClaimBlock[attacker] + CLAIM_COOLDOWN_BLOCKS), block.number > lastFaucetDripBlock + ONE_DAY_BLOCKS) are immune to timestamp manipulation, ensuring claims occur only after the intended block count.

This conceptual PoC illustrates how block.timestamp manipulation could allow early claims or resets, and how switching to block.number mitigates the issue.

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.