Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

[L-02] - Duplicate day tracking mappings cause state confusion

Root + Impact

Description

The contract uses two separate day tracking mechanisms for different claim types: lastDripDay for ETH refills and lastFaucetDripDay for token claims. These independent tracking systems can reset at different times and create state inconsistencies.

The specific issue is that having two separate day counters for the same user creates confusion and potential for state desynchronization, as they track the same concept (last claim day) but for different purposes.

// Root cause in the codebase
// Two separate day tracking mappings
mapping(address => uint256) public lastDripDay; // @> For ETH refills
mapping(address => uint256) public lastFaucetDripDay; // @> For token claims
// Used in refillSepEth()
function refillSepEth() external {
uint256 currentDay = block.timestamp / 1 days;
require(
currentDay > lastDripDay[msg.sender], // @> Uses lastDripDay
"Already claimed today"
);
lastDripDay[msg.sender] = currentDay;
}
// Used in claimFaucetTokens()
function claimFaucetTokens() external {
uint256 currentDay = block.timestamp / 1 days;
require(
currentDay > lastFaucetDripDay[msg.sender] + 2, // @> Uses lastFaucetDripDay
"Cooldown period not met"
);
lastFaucetDripDay[msg.sender] = currentDay;
}

Risk

Likelihood:

  • Occurs during normal contract operation

  • Affects all users who use both functions

  • No special conditions required

  • State inconsistency is guaranteed

Impact:

  • Confusing state management

  • Potential for logic errors in future updates

  • Increased gas costs (two storage slots per user)

  • Difficult to reason about user claim history

Proof of Concept

This test demonstrates the state inconsistency:

  1. Setup: We deploy the contract and have a user interact with both functions

  2. Observation: Two separate day counters track similar information

  3. Result: State confusion and potential for errors

The issue occurs because:

  • Two mappings track "last claim day" concept

  • They update independently

  • No synchronization between them

  • Creates unnecessary complexity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {RaiseBoxFaucet} from "../src/RaiseBoxFaucet.sol";
import {RaiseBoxToken} from "../src/RaiseBoxToken.sol";
contract InconsistentDayTrackingTest is Test {
RaiseBoxFaucet faucet;
RaiseBoxToken token;
address owner = makeAddr("owner");
address user = makeAddr("user");
function setUp() public {
vm.startPrank(owner);
token = new RaiseBoxToken();
faucet = new RaiseBoxFaucet(address(token));
token.mintFaucetTokens(address(faucet), 1_000_000 * 10**18);
vm.deal(address(faucet), 100 ether);
vm.stopPrank();
}
function testInconsistentDayTracking() public {
// User claims tokens (updates lastFaucetDripDay)
vm.prank(user);
faucet.claimFaucetTokens();
uint256 lastFaucetDripDay = faucet.lastFaucetDripDay(user);
uint256 lastDripDay = faucet.lastDripDay(user);
// lastFaucetDripDay is updated but lastDripDay is not
assertTrue(lastFaucetDripDay > 0);
assertEq(lastDripDay, 0);
// Wait 1 day
vm.warp(block.timestamp + 1 days);
// User refills ETH (updates lastDripDay)
vm.prank(user);
faucet.refillSepEth();
lastFaucetDripDay = faucet.lastFaucetDripDay(user);
lastDripDay = faucet.lastDripDay(user);
// Now both are set but track different days
assertTrue(lastFaucetDripDay > 0);
assertTrue(lastDripDay > 0);
assertEq(lastDripDay, lastFaucetDripDay + 1); // Different values
// This demonstrates state inconsistency:
// - Two separate counters for same user
// - Track similar concept but different values
// - Can reset at different times
// - Creates confusion and potential bugs
}
function testDayTrackingDesynchronization() public {
uint256 currentDay = block.timestamp / 1 days;
// Claim tokens
vm.prank(user);
faucet.claimFaucetTokens();
assertEq(faucet.lastFaucetDripDay(user), currentDay);
assertEq(faucet.lastDripDay(user), 0); // Not updated
// The two tracking mechanisms are desynchronized
// This can lead to confusion about user's last claim day
}
}

Recommended Mitigation

Consolidate into a single day tracking mechanism:

- mapping(address => uint256) public lastDripDay;
- mapping(address => uint256) public lastFaucetDripDay;
+ mapping(address => uint256) public lastClaimDay;
+ mapping(address => uint256) public lastETHRefillDay;
function refillSepEth() external {
uint256 currentDay = block.timestamp / 1 days;
require(
- currentDay > lastDripDay[msg.sender],
+ currentDay > lastETHRefillDay[msg.sender],
"Already claimed today"
);
- lastDripDay[msg.sender] = currentDay;
+ lastETHRefillDay[msg.sender] = currentDay;
}
function claimFaucetTokens() external {
uint256 currentDay = block.timestamp / 1 days;
require(
- currentDay > lastFaucetDripDay[msg.sender] + 2,
+ currentDay > lastClaimDay[msg.sender] + 2,
"Cooldown period not met"
);
- lastFaucetDripDay[msg.sender] = currentDay;
+ lastClaimDay[msg.sender] = currentDay;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 days ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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