Raisebox Faucet

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

Inconsistent daily claim reset logic (timestamp drift)

Root + Impact

Description

Expected behavior:
Daily claim limits should reset every 24 hours consistently.

Actual behavior:

The reset logic relies on block.timestamp comparisons that can drift.

if (block.timestamp > lastFaucetDripDay + 1 days) {
@> lastFaucetDripDay = block.timestamp;
@> dailyClaimCount = 0;
}

Risk

Likelihood:

  • timestamp variance is common.

Impact:

  • Users can claim multiple times if timestamp drifts forward slightly.


Others may be delayed by more than 24 hours.

Proof of Concept

The following Solidity contract simulates two rapid claims from the same address in different blocks that trigger a reset improperly — even though they occur within a real-world 24-hour window.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract VulnerableFaucet {
uint256 public lastFaucetDripDay; // stored as raw timestamp (vulnerable)
uint256 public dailyClaimCount;
uint256 public constant MAX_DAILY = 1; // allow 1 claim per day per contract for demo
uint256 public dripAmount = 0.1 ether;
event Drip(address indexed who, uint256 amount, uint256 ts);
constructor() {
// initialize to zero so first call will reset
lastFaucetDripDay = 0;
dailyClaimCount = 0;
}
// deposit ETH into the faucet
receive() external payable {}
function claimFaucetTokens() external {
// Inconsistent reset: storing raw timestamp causes drift effects
if (block.timestamp > lastFaucetDripDay + 1 days) {
// store the raw timestamp (problem)
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}
require(dailyClaimCount < MAX_DAILY, "Daily cap reached");
dailyClaimCount += 1;
// send drip
(bool ok, ) = msg.sender.call{value: dripAmount}("");
require(ok, "Transfer failed");
emit Drip(msg.sender, dripAmount, block.timestamp);
}
}

Recommended Mitigation

block.timestamp / 1 days gives you the number of full days since the Unix epoch, which increases only once every 24 hours.

Comparing day counters (currentDay > lastFaucetDripDay) ensures daily resets happen strictly once per day, regardless of slight timestamp drift.

- if (block.timestamp > lastFaucetDripDay + 1 days)
+ uint256 currentDay = block.timestamp / 1 days;
+ if (currentDay > lastFaucetDripDay)
Updates

Lead Judging Commences

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