Raisebox Faucet

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

# Missing Event Emission in `adjustDailyClaimLimit`

Description

Contract Reference: RaiseBoxFaucet.sol

The adjustDailyClaimLimit function modifies the critical state variable dailyClaimLimit by either increasing or decreasing it based on the increaseClaimLimit parameter. However, it does not emit an event to log these changes, as flagged by Slither. Emitting events for state changes is a best practice in Solidity to ensure transparency, auditability, and ease of tracking by off-chain applications (e.g., frontends, monitoring tools). The absence of an event for dailyClaimLimit updates can obscure changes to this parameter, which controls the faucet’s daily claim capacity, potentially affecting user trust and system monitoring.

Slither Reference: Missing Events Arithmetic

Affected Code:

function adjustDailyClaimLimit(uint256 by, bool increaseClaimLimit) public onlyOwner {
if (increaseClaimLimit) {
dailyClaimLimit += by;
} else {
if (by > dailyClaimLimit) {
revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
}
dailyClaimLimit -= by;
}
}

Severity

Low
{ Impact — Low, Likelihood — High }

  • Impact: Low, as the lack of event emission does not directly lead to financial loss or exploit but reduces transparency and auditability. It may complicate off-chain monitoring and user trust in a testnet faucet context. This aligns with CodeHawks’ low-severity classification for non-exploitable issues affecting transparency in a testnet context.

  • Likelihood: High, as the function is callable by the owner, and any change to dailyClaimLimit (a critical parameter) goes unlogged, affecting all interactions with the faucet.

Risk

  • Likelihood of Issue: High. The function is accessible to the contract owner, and changes to dailyClaimLimit are likely during faucet operation (e.g., to adjust capacity based on demand). Without an event, these changes are not easily trackable by users or monitoring systems.

  • Potential Consequences:

    • Transparency Loss: Off-chain applications (e.g., faucet dashboards, analytics tools) cannot easily detect changes to dailyClaimLimit, potentially leading to outdated information or user confusion.

    • Auditability Issues: Auditors or users cannot efficiently verify historical changes to the claim limit, which is critical for ensuring the faucet operates fairly.

    • Operational Impact: In a public Sepolia faucet, users may rely on predictable claim limits. Unlogged changes could erode trust if users perceive the faucet as unpredictable or manipulated.

  • Contextual Factors: As a testnet faucet, the financial stakes are low (tokens and ETH have no real-world value), but transparency is crucial for developer trust and usability. The issue is less severe than vulnerabilities causing asset loss but still significant for operational integrity.

Impact

  • Transparency and Monitoring: Without an event, external tools and users cannot track changes to dailyClaimLimit, which governs the faucet’s daily token/ETH distribution capacity.

  • User Trust: Unlogged changes may lead users to question the faucet’s fairness, especially if the limit is reduced unexpectedly, limiting access.

  • Limited Scope: The issue does not cause direct financial loss or security exploits, as dailyClaimLimit changes are restricted to the owner via the onlyOwner modifier. It primarily affects operational transparency.

  • Testnet Context: In a Sepolia faucet, the impact is confined to testnet operations, but it could disrupt developer workflows if claim limits change without notice.

Tools Used

  • Slither: Detected missing event emission for arithmetic operations on dailyClaimLimit (see Slither output for details).

  • Foundry: Used to develop and test the PoC, verifying the issue and fix.

  • Manual Review: Confirmed the absence of events in the function.

Recommended Mitigation

  • Emit an Event for State Changes: Add an event to log changes to dailyClaimLimit, including the old value, new value, and whether it was increased or decreased.

  • Event Definition: Define a new event in the contract, e.g., DailyClaimLimitAdjusted(address indexed owner, uint256 oldLimit, uint256 newLimit, bool increased).

  • Updated Function Example:

    event DailyClaimLimitAdjusted(address indexed owner, uint256 oldLimit, uint256 newLimit, bool increased);
    function adjustDailyClaimLimit(uint256 by, bool increaseClaimLimit) public onlyOwner {
    uint256 oldLimit = dailyClaimLimit;
    if (increaseClaimLimit) {
    dailyClaimLimit += by;
    } else {
    if (by > dailyClaimLimit) {
    revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
    }
    dailyClaimLimit -= by;
    }
    emit DailyClaimLimitAdjusted(msg.sender, oldLimit, dailyClaimLimit, increaseClaimLimit);
    }
  • Additional Consideration: Ensure the event is indexed for the owner address to facilitate filtering by monitoring tools. Test the event emission to confirm it logs correctly in off-chain systems.

Proof of Concept

The lack of event emission in adjustDailyClaimLimit can be demonstrated as follows:

  1. Setup: The contract is deployed with an initial dailyClaimLimit = 100. The owner address is set to 0x123.

  2. Action: The owner calls adjustDailyClaimLimit(50, true) to increase dailyClaimLimit to 150.

    • Expected Behavior: The state variable dailyClaimLimit updates to 150, but no event is emitted, making the change invisible to off-chain observers.

    • Issue: External monitoring tools (e.g., a faucet UI) or users cannot detect the change without querying the contract state directly, reducing transparency.

  3. Action: The owner calls adjustDailyClaimLimit(30, false) to decrease dailyClaimLimit to 70.

    • Expected Behavior: The state variable updates to 70, but again, no event is emitted, obscuring the change.

  4. Action: The owner calls adjustDailyClaimLimit(150, false), which reverts with RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy since dailyClaimLimit (100) is less than 150.

    • Expected Behavior: The state remains unchanged, and no event is emitted (correctly, as no state change occurs).

  5. Fix Demonstration: In the recommended implementation, calling adjustDailyClaimLimit(50, true) emits DailyClaimLimitAdjusted(0x123, 100, 150, true), and calling adjustDailyClaimLimit(30, false) emits DailyClaimLimitAdjusted(0x123, 150, 120, false), making changes trackable.

  6. Verification: Auditors can verify this by deploying the contract on a Sepolia testnet, calling adjustDailyClaimLimit, and checking for emitted events via a block explorer or event logs.

Note: The PoC assumes dailyClaimLimit = 100 initially. Adjust values if the actual contract differs.

Executable PoC:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
// Interface to define the custom error
interface IRaiseBoxFaucet {
error RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
}
contract RaiseBoxFaucet is Ownable, IRaiseBoxFaucet {
uint256 public dailyClaimLimit = 100;
constructor() Ownable(msg.sender) {}
function adjustDailyClaimLimit(uint256 by, bool increaseClaimLimit) public onlyOwner {
if (increaseClaimLimit) {
dailyClaimLimit += by;
} else {
if (by > dailyClaimLimit) {
revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
}
dailyClaimLimit -= by;
}
}
}
contract RaiseBoxFaucetWithEvent is Ownable, IRaiseBoxFaucet {
uint256 public dailyClaimLimit = 100;
event DailyClaimLimitAdjusted(address indexed owner, uint256 oldLimit, uint256 newLimit, bool increased);
constructor() Ownable(msg.sender) {}
function adjustDailyClaimLimit(uint256 by, bool increaseClaimLimit) public onlyOwner {
uint256 oldLimit = dailyClaimLimit;
if (increaseClaimLimit) {
dailyClaimLimit += by;
} else {
if (by > dailyClaimLimit) {
revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
}
dailyClaimLimit -= by;
}
emit DailyClaimLimitAdjusted(msg.sender, oldLimit, dailyClaimLimit, increaseClaimLimit);
}
}
contract RaiseBoxFaucetEventPoC is Test {
RaiseBoxFaucet faucet;
RaiseBoxFaucetWithEvent faucetWithEvent;
address owner = address(0x123);
function setUp() public {
vm.prank(owner);
faucet = new RaiseBoxFaucet();
vm.prank(owner);
faucetWithEvent = new RaiseBoxFaucetWithEvent();
}
function test_NoEventEmitted() public {
vm.prank(owner);
vm.recordLogs();
faucet.adjustDailyClaimLimit(50, true);
Vm.Log[] memory logs = vm.getRecordedLogs();
console.log("Original contract: Number of events emitted: %s", logs.length);
uint256 newLimit = faucet.dailyClaimLimit();
assertEq(newLimit, 150, "Daily claim limit should increase to 150");
console.log("Original contract: dailyClaimLimit changed to %s", newLimit);
}
function test_EventEmittedInFixedContract() public {
vm.prank(owner);
vm.expectEmit(true, false, false, true);
emit RaiseBoxFaucetWithEvent.DailyClaimLimitAdjusted(owner, 100, 150, true);
faucetWithEvent.adjustDailyClaimLimit(50, true);
uint256 newLimit = faucetWithEvent.dailyClaimLimit();
assertEq(newLimit, 150, "Daily claim limit should increase to 150");
console.log("Fixed contract: Emitted event for dailyClaimLimit change to %s", newLimit);
}
function test_DecreaseLimitNoEvent() public {
vm.prank(owner);
vm.recordLogs();
faucet.adjustDailyClaimLimit(50, false);
Vm.Log[] memory logs = vm.getRecordedLogs();
console.log("Original contract: Number of events emitted: %s", logs.length);
uint256 newLimit = faucet.dailyClaimLimit();
assertEq(newLimit, 50, "Daily claim limit should decrease to 50");
console.log("Original contract: dailyClaimLimit changed to %s", newLimit);
}
function test_DecreaseLimitEventInFixedContract() public {
vm.prank(owner);
vm.expectEmit(true, false, false, true);
emit RaiseBoxFaucetWithEvent.DailyClaimLimitAdjusted(owner, 100, 50, false);
faucetWithEvent.adjustDailyClaimLimit(50, false);
uint256 newLimit = faucetWithEvent.dailyClaimLimit();
assertEq(newLimit, 50, "Daily claim limit should decrease to 50");
console.log("Fixed contract: Emitted event for dailyClaimLimit change to %s", newLimit);
}
function test_RevertOnInvalidDecrease() public {
vm.prank(owner);
vm.expectRevert(IRaiseBoxFaucet.RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy.selector);
faucet.adjustDailyClaimLimit(150, false);
vm.prank(owner);
vm.expectRevert(IRaiseBoxFaucet.RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy.selector);
faucetWithEvent.adjustDailyClaimLimit(150, false);
assertEq(faucet.dailyClaimLimit(), 100, "Daily claim limit should remain 100");
assertEq(faucetWithEvent.dailyClaimLimit(), 100, "Daily claim limit should remain 100");
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 9 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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