Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

[M-1] Inconsistent Daily Reset Semantics Break ERC20 Claims

[M-1] Inconsistent Daily Reset Semantics Break ERC20 Claims

Description

  • Normal behavior: Per-day counters (for Sepolia ETH drips and ERC20 faucet claims) should use a single, consistent definition of "day" (for example a day index computed as block.timestamp / 1 days). Both dailyDrips and dailyClaimCount must reset at the same canonical day boundary so users get the expected allocation each calendar day.

  • Specific issue: the contract mixes two different reset semantics. One part of the contract uses a day index (currentDay = block.timestamp / 24 hours) to reset dailyDrips and lastDripDay, while dailyClaimCount is reset using a timestamp comparison against lastFaucetDripDay + 1 days. The two approaches define "day" differently and can de-synchronize, causing users to reach a new calendar day yet still be blocked by stale dailyClaimCount values (or vice versa).

// Root cause (annotated)
// @> mixed day definitions: day-index vs timestamp + 1 days
uint256 currentDay = block.timestamp / 24 hours; // @> day-index based boundary used for dailyDrips
// ... resets dailyDrips using currentDay > lastDripDay ...
// ... elsewhere, dailyClaimCount reset uses timestamp arithmetic:
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp; // @> storing a timestamp here creates a sliding 24h window
dailyClaimCount = 0; // @> reset time is timestamp + 1 days, inconsistent with day-index logic
}

Risk

Likelihood:High

  • A normal user who attempts to claim shortly after a calendar day boundary will encounter inconsistent counter states because one counter has reset (day index logic) while the other has not (timestamp+1 days logic). This will occur during routine daily usage around day boundaries.

  • Coordinated callers or automated agents may amplify the discrepancy by issuing transactions timed around the different reset behaviors, making it more likely that honest users see unexpected failures.

Impact:Middle

  • Users may fail to receive the expected ERC20 faucet tokens even after the start of a new day, degrading UX and trust in the faucet.

  • Users who are blocked by an unreleased dailyClaimCount may still be subject to cooldowns, potentially preventing legitimate use for extended periods and concentrating claims by more active/bot accounts.

Proof of Concept

// Conceptual reproducer (pseudocode):
// 1) At time T (just before UTC midnight), dailyClaimCount is high (>= dailyClaimLimit) and lastFaucetDripDay is some recent timestamp.
// 2) At time T+1s (just after midnight), code that relies on currentDay (block.timestamp / 24 hours) resets dailyDrips and lastDripDay because currentDay > lastDripDay.
// 3) However, dailyClaimCount remains unchanged until block.timestamp > lastFaucetDripDay + 1 days, so the claimant still receives `DailyClaimLimitReached` reverts for ERC20 claims even though the calendar day advanced.
// Minimal sequence in test:
// - set dailyClaimCount = dailyClaimLimit
// - set lastFaucetDripDay = now (so timestamp + 1 days is in the future)
// - advance time to next calendar day (increase block.timestamp so currentDay increments)
// - attempt to call claimFaucetTokens() -> revert RaiseBoxFaucet_DailyClaimLimitReached() unexpectedly

Recommended Mitigation

- // store the exact timestamp when the last reset occurred
- uint256 public lastFaucetDripDay;
+ // store a day index (e.g. days since unix epoch). Use the same day-index everywhere.
+ uint256 public lastFaucetDripDayIndex;
- uint256 currentDay = block.timestamp / 24 hours;
- if (block.timestamp > lastFaucetDripDay + 1 days) {
- lastFaucetDripDay = block.timestamp;
- dailyClaimCount = 0;
- }
+ uint256 currentDay = block.timestamp / 1 days; // explicit day index
+ if (currentDay > lastFaucetDripDayIndex) {
+ // reset per-day counters for new calendar/day bucket
+ dailyDrips = 0;
+ dailyClaimCount = 0;
+ lastFaucetDripDayIndex = currentDay;
+ }
// Also: ensure any other per-day counters use the same day index logic,
// e.g. dailyClaimCount must reset when currentDay > lastClaimDayIndex (not via timestamp+1 days).
// Also: use the same `currentDay` logic everywhere (dailyDrips, dailyClaimCount, etc.)
// so "day" is a single, consistent unit across the contract.
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Inconsistent day calculation methods cause desynchronization between ETH and token daily resets.

Support

FAQs

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