Raisebox Faucet

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

[H-1] State variable `RaiseBoxFaucet::dailyClaimCount` never resets, breaking the faucet indefinitely as soon as the daily claim limit has been reached

[H-1] State variable RaiseBoxFaucet::dailyClaimCount never resets, breaking the faucet indefinitely as soon as the daily claim limit has been reached

Description

  • Expected behaviour
    The state variable RaiseBoxFaucet::dailyClaimCount stores the number of token claims that have been processed in a given 24 hour window. It should reset to zero once the 24 hours from lastFaucetDripDay have passed so that the faucet can accept new token claims.

  • Current bahaviour
    The dailyClaimCount never resets. It keeps incrementing and once it reaches the maximum allowed number of daily claims, it stops working and rejects all incoming claims, regardless of the elapsed time window since the last claim.

Root Cause
The root cause is that the following if block,

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

is placed after the check

if (dailyClaimCount >= dailyClaimLimit) {
revert RaiseBoxFaucet_DailyClaimLimitReached();
}

and as a consequence the call is reverted before the dailyClaimCount can reset to 0.

Problematic function:

function claimFaucetTokens() public {
.
.
.
.
@> if (block.timestamp > lastFaucetDripDay + 1 days) {
@> lastFaucetDripDay = block.timestamp;
@> dailyClaimCount = 0;
@> }
// Effects
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
// Interactions
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}

Risk

Likelihood: High

  • This bug occurs as soon as enough token claims have been made to reach the dailyClaimLimit. Given that the contract is deployed with the relatively low token claim limit of 100, this bug will occur as soon as the faucet has processed 100 claims, unless the owner significantly increases the limit.

Impact: High

  • The faucet stops working entirely, both for first-time and non-first time users, breaking the faucet's core functionality.

Proof of Concept

To reproduce the bug add the following test in the Foundry test suite and run with forge test --mt test_dailyClaimCount_NeverResets.

Hypothetical scenario:

  1. Day 1: 100 first time users call claimFaucetTokens. The value of dailyClaimCount is now 100.

  2. Day 2: no claims.

  3. Day 3: no claims.

  4. Day 4: no claims.

  5. Day 5: A second-time user calls claimFaucetTokens. The call reverts with the error RaiseBoxFaucet_DailyClaimLimitReached.

  6. Day 5: A first time user calls claimFaucetTokens. The call reverts with the error RaiseBoxFaucet_DailyClaimLimitReached.

  7. The value of dailyClaimCount is still 100 and no user can submit token claims.

function test_dailyClaimCount_NeverResets() public {
uint256 cumDailyDrips;
// 1. Simulate 100 first time users calling claimFaucetTokens
for(uint256 i=0; i < 100; i++) {
string memory claimer = string.concat("user",vm.toString(i));
address user = makeAddr(claimer);
vm.prank(user);
raiseBoxFaucet.claimFaucetTokens();
cumDailyDrips += raiseBoxFaucet.sepEthAmountToDrip();
// Assert that dailyClaimCount increments with every claim
assertEq(raiseBoxFaucet.dailyClaimCount(), i+1);
// Assert that dailyDrips tracks the total sep ETH amount dripped
assertEq(raiseBoxFaucet.dailyDrips(), cumDailyDrips);
}
// dailyClaimCount has now reached the dailyClaimLimit, ie., 100 claims
// dailyDrips has reached the limit of 0.5 ETH
// 2. Advance time so that we skip the cooldown period
vm.warp(block.timestamp + 4 days);
// 3. Assert that user1 is not a first time user
assertTrue(raiseBoxFaucet.getHasClaimedEth(user1), "User1 is a first time user");
// 4. User1 submits a claim again (should normally pass as cooldown is over)
// but call reverts with error RaiseBoxFaucet_DailyClaimLimitReached:
vm.expectRevert(
abi.encodeWithSelector(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector)
);
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
// 4. First-time user claims tokens
address newUser = makeAddr("newUser");
assertTrue(!raiseBoxFaucet.getHasClaimedEth(newUser), "newUser is not a first time user");
// Call reverts with the same error:
vm.expectRevert(
abi.encodeWithSelector(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector)
);
vm.prank(newUser);
raiseBoxFaucet.claimFaucetTokens();
// Assert that the dailyClaimCount is still 100 (== dailyClaimLimit) and has not reset
assertTrue(raiseBoxFaucet.dailyClaimCount() == 100, "Daily claim count is not 100");
}

Recommended Mitigation

To mitigate the issue, move the if block resetting the dailyClaimCount to the top of the function. This way the dailyClaimCount can be reset before it is checked against the dailyClaimLimit causing the function to revert.

function claimFaucetTokens() public {
+ if (block.timestamp > lastFaucetDripDay + 1 days) {
+ lastFaucetDripDay = block.timestamp;
+ dailyClaimCount = 0;
+ }
// Checks
faucetClaimer = msg.sender;
.
.
.
.
- if (block.timestamp > lastFaucetDripDay + 1 days) {
- lastFaucetDripDay = block.timestamp;
- dailyClaimCount = 0;
- }
// Effects
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
// Interactions
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months 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.

Give us feedback!