Missing Events for State Changes
Description
Functions that modify contract state should emit events to enable off-chain monitoring, indexing, and transparency for users and integrators who need to track changes.
The adjustDailyClaimLimit() function modifies critical state that affects all users but doesn't emit events, making it impossible for users, monitoring systems, or integration partners to track when and how daily limits are changed.
@> function adjustDailyClaimLimit(uint256 by, bool increaseClaimLimit) public onlyOwner {
if (increaseClaimLimit) {
dailyClaimLimit += by;
} else {
if (by > dailyClaimLimit) {
revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
}
dailyClaimLimit -= by;
}
@>
}
Risk
Likelihood: High
-
Off-chain monitoring systems cannot automatically track daily limit changes without events
-
Users have no notification mechanism when limits are modified affecting their access
-
Integration partners cannot programmatically react to limit changes in their systems
Impact: Low
-
Severely reduced transparency in protocol governance and administrative actions
-
Significant difficulty in debugging issues related to limit changes without event history
-
Poor user experience due to lack of notifications about limit modifications affecting service
-
Major compliance and auditing challenges due to lack of verifiable event trails
Proof of Concept
contract MonitoringLimitations {
RaiseBoxFaucet public faucet;
constructor(address _faucet) {
faucet = RaiseBoxFaucet(_faucet);
}
function demonstrateMonitoringGaps() external view returns (
string memory limitation1,
string memory limitation2,
string memory limitation3,
string memory limitation4
) {
limitation1 = "Cannot track when daily limits were changed";
limitation2 = "Cannot identify who changed the limits";
limitation3 = "Cannot see historical limit values";
limitation4 = "Cannot get notifications about limit changes";
return (limitation1, limitation2, limitation3, limitation4);
}
function simulateEventBasedMonitoring() external pure returns (
string memory currentApproach,
string memory limitation,
string memory requiredApproach,
string memory benefit
) {
currentApproach = "Poll contract state every block to detect changes";
limitation = "Expensive, slow, and provides no historical context";
requiredApproach = "Listen for DailyLimitAdjusted events with full details";
benefit = "Real-time notifications with full context and history";
return (currentApproach, limitation, requiredApproach, benefit);
}
function showIntegrationChallenges() external pure returns (
string[] memory challenges
) {
challenges = new string[](5);
challenges[0] = "Cannot build automated limit change notifications";
challenges[1] = "Cannot maintain historical limit change logs";
challenges[2] = "Cannot trigger automated responses to limit changes";
challenges[3] = "Cannot verify administrative actions for compliance";
challenges[4] = "Cannot provide users with limit change explanations";
return challenges;
}
function showDebuggingChallenges() external pure returns (
string memory scenario,
string memory problem,
string memory currentDifficulty,
string memory withEvents
) {
scenario = "User reports daily limit seems to have changed unexpectedly";
problem = "Support team needs to investigate when/why limit changed";
currentDifficulty = "Must check all historical blocks manually, no way to see who/when/why";
withEvents = "Query DailyLimitAdjusted events for instant answer with full context";
return (scenario, problem, currentDifficulty, withEvents);
}
}
Real-world monitoring scenario without events:
User complains they can't claim tokens due to daily limit
Support checks current limit: dailyClaimLimit = 50
User says it was 100 yesterday
Support has no way to verify when/why it changed
Must manually check every block's state changes (expensive)
Cannot identify who made the change or the reason
Cannot build automated alerts for future changes
Compliance audit requires manual reconstruction of all changes
Integration partners cannot react to limit changes automatically
Recommended Mitigation
The mitigation adds comprehensive event emission for state changes with detailed context information, enabling proper monitoring, debugging, and integration capabilities while maintaining transparency.
// Add comprehensive events for state changes
+ event DailyClaimLimitAdjusted(
+ uint256 indexed previousLimit,
+ uint256 indexed newLimit,
+ uint256 adjustment,
+ bool increased,
+ address indexed adjustedBy,
+ uint256 timestamp,
+ string reason
+ );
+
+ event DailyClaimLimitAdjustedDetailed(
+ uint256 previousLimit,
+ uint256 newLimit,
+ uint256 adjustment,
+ bool increased,
+ address adjustedBy,
+ uint256 timestamp,
+ string reason
+ );
// Update function to emit events with full context
function adjustDailyClaimLimit(uint256 by, bool increaseClaimLimit) external onlyOwner {
+ // Capture previous state for event
+ uint256 previousLimit = dailyClaimLimit;
+
if (increaseClaimLimit) {
dailyClaimLimit += by;
} else {
if (by > dailyClaimLimit) {
revert RaiseBoxFaucet_CurrentClaimLimitIsLessThanBy();
}
dailyClaimLimit -= by;
}
+ // Emit detailed events for monitoring and transparency
+ emit DailyClaimLimitAdjusted(
+ previousLimit,
+ dailyClaimLimit,
+ by,
+ increaseClaimLimit,
+ msg.sender,
+ block.timestamp,
+ increaseClaimLimit ? "Limit increased" : "Limit decreased"
+ );
+
+ // Emit non-indexed version for easier parsing of all data
+ emit DailyClaimLimitAdjustedDetailed(
+ previousLimit,
+ dailyClaimLimit,
+ by,
+ increaseClaimLimit,
+ msg.sender,
+ block.timestamp,
+ increaseClaimLimit ? "Limit increased" : "Limit decreased"
+ );
}
// Add events for other state changes that were missing
+ event FaucetTokensBurned(
+ address indexed burner,
+ uint256 amount,
+ uint256 timestamp
+ );
+
+ event FaucetTokensMinted(
+ address indexed minter,
+ address indexed recipient,
+ uint256 amount,
+ uint256 newContractBalance,
+ uint256 timestamp
+ );
// Update other functions to emit events
function burnFaucetTokens(uint256 amountToBurn) external onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
_transfer(address(this), msg.sender, amountToBurn);
_burn(msg.sender, amountToBurn);
+ emit FaucetTokensBurned(msg.sender, amountToBurn, block.timestamp);
}
function mintFaucetTokens(address to, uint256 amount) external onlyOwner {
if (to != address(this)) {
revert RaiseBoxFaucet_MiningToNonContractAddressFailed();
}
if (balanceOf(address(to)) > 1000 * 10 ** 18) {
revert RaiseBoxFaucet_FaucetNotOutOfTokens();
}
_mint(to, amount);
- emit MintedNewFaucetTokens(to, amount);
+ emit FaucetTokensMinted(msg.sender, to, amount, balanceOf(address(this)), block.timestamp);
+ emit MintedNewFaucetTokens(to, amount); // Keep existing event for compatibility
}
// Add getter functions for event-based monitoring
+ function getLatestLimitChange() external view returns (
+ uint256 currentLimit,
+ uint256 lastChangeBlock,
+ address lastChanger
+ ) {
+ // These would be maintained by tracking events off-chain
+ // or by adding state variables if on-chain tracking needed
+ return (dailyClaimLimit, 0, owner()); // Simplified implementation
+ }