Raisebox Faucet

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

# Commented Out dailyClaimCount Reset Leading to Denial of Service for Token Claims

Description

In the claimFaucetTokens function of the RaiseBoxFaucet contract, the daily reset logic for the dailyClaimCount state variable is incomplete. Specifically, in the ETH drip section, the check for a new day (if (currentDay > lastDripDay)) resets lastDripDay and dailyDrips but has a commented-out line (// dailyClaimCount = 0;), which prevents the dailyClaimCount from resetting.

The dailyClaimCount tracks the number of token claims per day and enforces the dailyClaimLimit (e.g., 100 claims). Without resetting dailyClaimCount alongside the ETH drip counters, token claims may be blocked on new days when a first-time claimer triggers the ETH drip logic. This happens because dailyClaimCount retains its previous day's value, potentially exceeding dailyClaimLimit, causing the function to revert with RaiseBoxFaucet_DailyClaimLimitReached.

Additionally, the token claim counter reset relies on a separate check (if (block.timestamp > lastFaucetDripDay + 1 days)), which uses a different time calculation than the ETH drip's block.timestamp / 24 hours. This misalignment can further exacerbate the issue, as the two reset mechanisms may not trigger simultaneously, leading to inconsistent state updates.

This logic error violates the intended behavior of the faucet, which should allow dailyClaimLimit token claims every 24 hours, as implied by the protocol documentation. The result is a denial of service (DoS) for users attempting to claim tokens on subsequent days.

Severity

Medium

Risk

The bug introduces a denial of service (DoS) risk where legitimate users are unable to claim faucet tokens on new days if dailyClaimCount is not reset, even though ETH drips for first-time claimers are allowed (due to the reset of dailyDrips). In a testnet faucet context, this degrades usability and frustrates users. In a production environment, such a logic error could lead to temporary or permanent lockouts of critical functionality.

Impact

  • Users (claimers) are prevented from claiming faucet tokens on new days, even if the 3-day cooldown period (CLAIM_COOLDOWN) has passed and the contract has sufficient token balance.

  • The dailyClaimLimit effectively becomes a permanent limit across days, rendering the faucet unusable for token claims after the first day's limit is reached.

  • Reduced adoption of the testnet protocol, as faucet tokens are essential for testing interactions.

  • No direct fund loss occurs, but the protocol's usability is significantly impacted, affecting the user experience and ecosystem.

Tools Used

  • Manual code review using VS Code with Solidity extensions.

  • Foundry for Proof of Concept testing (simulating time warps and claim scenarios).

Recommended Mitigation

To fix the issue, uncomment the dailyClaimCount = 0; line in the ETH drip reset block to ensure synchronized resetting of all daily counters:

if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
dailyClaimCount = 0; // Uncomment this line
}

For a more robust solution, consolidate the daily reset logic for both ETH and token claims into a single internal function to avoid duplication and ensure consistency. For example:

function _resetDailyCounters() internal {
uint256 currentDay = block.timestamp / 1 days; // 86400 seconds
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
dailyClaimCount = 0;
}
}

Call _resetDailyCounters at the start of claimFaucetTokens and remove the separate lastFaucetDripDay check to prevent misalignment. This ensures that both dailyDrips and dailyClaimCount reset consistently on new days.

Proof of Concepts

The following Foundry test demonstrates the bug by simulating a scenario where the daily claim limit is reached on Day 1, and on Day 2, a new user is unable to claim tokens due to the unre reset dailyClaimCount. The test sets dailyClaimLimit to 1 for simplicity, ensuring the limit is reached after one claim.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {RaiseBoxFaucet} from "../src/RaiseBoxFaucet.sol";
contract RaiseBoxFaucetTest is Test {
RaiseBoxFaucet public faucet;
address public owner = address(0x123);
address public user1 = address(0x456);
address public user2 = address(0x789);
function setUp() public {
vm.startPrank(owner);
faucet = new RaiseBoxFaucet("TestToken", "TTK", 1000e18, 0.005 ether, 1 ether);
faucet.adjustDailyClaimLimit(99, false); // Sets dailyClaimLimit to 1
vm.deal(address(faucet), 1 ether); // Fund contract with ETH
vm.stopPrank();
vm.deal(user1, 1 ether);
vm.deal(user2, 1 ether);
vm.warp(3 days + 1); // 259201 secs to bypass initial cooldown
}
function test_DailyClaimCountNotResetOnNewDay() public {
// Day 1: User1 claims successfully, fills daily limit (1)
vm.startPrank(user1);
faucet.claimFaucetTokens();
vm.stopPrank();
assertEq(faucet.dailyClaimCount(), 1); // Limit reached
assertEq(faucet.getHasClaimedEth(user1), true);
// Save old drip day before warp
uint256 oldDripDay = faucet.lastDripDay();
// Warp to Day 2
vm.warp(block.timestamp + 1 days + 1);
// User2 tries to claim: expect revert due to unre reset dailyClaimCount
vm.startPrank(user2);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector);
faucet.claimFaucetTokens();
vm.stopPrank();
// Verify: dailyClaimCount not reset (still 1)
assertEq(faucet.dailyClaimCount(), 1);
// Verify: lastDripDay not updated (reset logic not reached due to revert)
assertEq(faucet.lastDripDay(), oldDripDay);
}
}

Test Explanation:

  • The test sets dailyClaimLimit to 1 and funds the contract with ETH.

  • vm.warp(3 days + 1) bypasses the initial 3-day cooldown for the first claim.

  • On Day 1, user1 claims tokens and ETH, setting dailyClaimCount to 1 (limit reached).

  • On Day 2, user2 (a first-time claimer) attempts to claim. The ETH drip reset logic (currentDay > lastDripDay) is triggered, resetting dailyDrips, but dailyClaimCount remains 1 due to the commented-out line.

  • The claim reverts with RaiseBoxFaucet_DailyClaimLimitReached, proving the DoS bug.

  • Assertions confirm that dailyClaimCount is not reset and lastDripDay remains unchanged (since the revert prevents further execution).

Run the test with: forge test --mt test_DailyClaimCountNotResetOnNewDay -vvv.

Updates

Lead Judging Commences

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

dailyClaimCount Reset Bug

Support

FAQs

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