Raisebox Faucet

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

Daily Reset Logic Inconsistency

Author Revealed upon completion

Daily Reset Logic Inconsistency

Description

The contract should use consistent logic for all daily reset mechanisms to ensure synchronization between different daily limits.

The contract uses two different calculation methods for daily resets - division-based calculation for ETH drips and addition-based calculation for token claims, creating potential desynchronization where users can exploit timing differences between the two systems.

// ETH drip reset logic
@> uint256 currentDay = block.timestamp / 24 hours;
@> if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
}
// Token claim reset logic (different method)
@> if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}

Risk

Likelihood: Medium

  • The two reset mechanisms will naturally drift apart over time due to different calculation methods

  • Different calculation methods create predictable timing windows where limits are inconsistent

  • Users can observe and exploit the gap between reset timings

Impact: Medium

  • Users may claim more tokens or ETH than intended daily limits by timing claims during desynchronized periods

  • Protocol economics are disrupted through inconsistent limit enforcement

  • Unpredictable faucet behavior confuses legitimate users and reduces trust

Proof of Concept

// Demonstrate the desynchronization issue
contract DailyResetExploit {
// Simulate the two different reset mechanisms
function demonstrateDesynchronization() external pure returns (
bool ethResetTriggered,
bool tokenResetTriggered,
uint256 timingGap
) {
// Contract deployed at: January 1, 2024 12:30:00 (timestamp 1704110200)
uint256 deployTime = 1704110200;
// Set initial states as they would be at deployment
uint256 lastDripDay = deployTime / 86400; // ETH reset day = 19734
uint256 lastFaucetDripDay = deployTime; // Token reset time = 1704110200
// Check status at: January 2, 2024 00:00:00 (timestamp 1704067200)
uint256 checkTime = 1704067200; // Midnight next day
// ETH reset check (division method)
uint256 currentDay = checkTime / 86400; // = 19734
ethResetTriggered = (currentDay > lastDripDay); // false - same day number
// Token reset check (addition method)
tokenResetTriggered = (checkTime > lastFaucetDripDay + 86400); // false - not 24h yet
// The real issue: ETH resets at midnight, tokens reset at 12:30
uint256 nextMidnight = 1704153600; // Jan 2 midnight
uint256 nextTokenReset = deployTime + 86400; // Jan 2 12:30
timingGap = nextTokenReset - nextMidnight; // 12.5 hours gap
return (ethResetTriggered, tokenResetTriggered, timingGap);
}
// Demonstrate exploitation during desync window
function simulateExploitWindow() external pure returns (
uint256 exploitWindowStart,
uint256 exploitWindowEnd,
uint256 windowDuration
) {
uint256 deployTime = 1704110200; // Jan 1, 12:30:00
// Day 2: ETH resets at midnight, tokens don't reset until 12:30
exploitWindowStart = 1704153600; // Jan 2, 00:00:00 (ETH reset)
exploitWindowEnd = 1704196800; // Jan 2, 12:30:00 (Token reset)
windowDuration = exploitWindowEnd - exploitWindowStart; // 12.5 hours
// During this window:
// - ETH daily limit is reset (can get ETH again)
// - Token daily limit is NOT reset (but ETH is available)
// - User can claim with fresh ETH allowance but old token count
return (exploitWindowStart, exploitWindowEnd, windowDuration);
}
}

Attack scenario:

  1. Contract deployed Jan 1 at 12:30 PM

  2. User claims once on Jan 1 (uses up daily allowances)

  3. Jan 2 at 00:01 AM: ETH limit resets but token limit doesn't

  4. User can potentially get ETH again during 12.5 hour window

  5. Jan 2 at 12:31 PM: Token limit finally resets

  6. Pattern repeats daily, creating exploitable windows

Recommended Mitigation

The mitigation standardizes both reset mechanisms to use the same calculation method, ensuring perfect synchronization between ETH and token daily limits. This eliminates exploitable timing windows.

+ // Use single reset tracking for both ETH and tokens
+ uint256 public lastResetDay;
function claimFaucetTokens() public nonReentrant {
// ... all checks first ...
- // Remove inconsistent ETH reset logic
- uint256 currentDay = block.timestamp / 24 hours;
- if (currentDay > lastDripDay) {
- lastDripDay = currentDay;
- dailyDrips = 0;
- }
-
- // Remove inconsistent token reset logic
- if (block.timestamp > lastFaucetDripDay + 1 days) {
- lastFaucetDripDay = block.timestamp;
- dailyClaimCount = 0;
- }
+ // Unified daily reset logic - both limits reset simultaneously
+ uint256 currentDay = block.timestamp / 1 days;
+ if (currentDay > lastResetDay) {
+ lastResetDay = currentDay;
+ dailyDrips = 0; // Reset ETH daily limit
+ dailyClaimCount = 0; // Reset token daily limit
+ emit DailyLimitsReset(currentDay, block.timestamp);
+ }
// ... rest of function logic remains the same ...
}
// Update constructor to initialize unified reset tracking
constructor(
string memory name_,
string memory symbol_,
uint256 faucetDrip_,
uint256 sepEthDrip_,
uint256 dailySepEthCap_
) ERC20(name_, symbol_) Ownable(msg.sender) {
faucetDrip = faucetDrip_;
sepEthAmountToDrip = sepEthDrip_;
dailySepEthCap = dailySepEthCap_;
+ lastResetDay = block.timestamp / 1 days; // Initialize to deployment day
_mint(address(this), INITIAL_SUPPLY);
}
// Add event for transparency
+ event DailyLimitsReset(uint256 indexed dayNumber, uint256 timestamp);
// Remove old state variables that are no longer needed
- uint256 public lastDripDay;
- uint256 public lastFaucetDripDay;
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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.