Raisebox Faucet

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

Change the address public faucetClaimer to a local variable

Root + Impact

Description

  • faucetClaimer is logically just a local variable of the current function;

    However, it is defined as a storage variable

contract RaiseBoxFaucet is ERC20, Ownable {
// state variables....
mapping(address => uint256) private lastClaimTime;
mapping(address => bool) private hasClaimedEth;
// @> faucetClaimer defined as a storage variable
address public faucetClaimer;
....
function claimFaucetTokens() public {
// @>faucetClaimer is logically just a local variable of the current function
faucetClaimer = msg.sender;

Risk

Likelihood:

  • Each call to claim triggers an SSTORE (write to storage), which is very expensive in the EVM;

    There's actually no need to store it on-chain

Impact:

  • A single SSTORE (storage slot modification) costs.

    Multiple claims incur additional gas costs .

    No external logic depends on this (e.g., events or subsequent reads)

  • Multiple claims incur additional gas costs .

    No external logic depends on this (e.g., events or subsequent reads)

Proof of Concept

/// @notice Test gas waste caused by faucetClaimer storage variable
/// @dev Prove that faucetClaimer wastes gas by being a storage variable instead of local variable
function testFaucetClaimerGasWaste() public {
console.log("=== Testing faucetClaimer gas waste ===");
// Test gas consumption for first claim
uint256 gasBeforeFirstClaim = gasleft();
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
uint256 gasAfterFirstClaim = gasleft();
uint256 gasUsedFirstClaim = gasBeforeFirstClaim - gasAfterFirstClaim;
console.log("Gas used for first claim:", gasUsedFirstClaim);
// Verify faucetClaimer was set
assertEq(raiseBoxFaucet.getClaimer(), user1, "faucetClaimer should be set to user1");
// Wait for cooldown period
vm.warp(block.timestamp + 3 days);
// Test gas consumption for second claim (same user)
uint256 gasBeforeSecondClaim = gasleft();
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
uint256 gasAfterSecondClaim = gasleft();
uint256 gasUsedSecondClaim = gasBeforeSecondClaim - gasAfterSecondClaim;
console.log("Gas used for second claim (same user):", gasUsedSecondClaim);
// Verify faucetClaimer was overwritten
assertEq(raiseBoxFaucet.getClaimer(), user1, "faucetClaimer should still be user1");
// Test gas consumption for different user claim
uint256 gasBeforeThirdClaim = gasleft();
vm.prank(user2);
raiseBoxFaucet.claimFaucetTokens();
uint256 gasAfterThirdClaim = gasleft();
uint256 gasUsedThirdClaim = gasBeforeThirdClaim - gasAfterThirdClaim;
console.log("Gas used for third claim (different user):", gasUsedThirdClaim);
// Verify faucetClaimer was overwritten again
assertEq(raiseBoxFaucet.getClaimer(), user2, "faucetClaimer should be set to user2");
console.log("=== Problem Analysis ===");
console.log("Problem: faucetClaimer is declared as storage variable but used as local variable");
console.log("Each claim operation writes to storage, consuming extra gas");
console.log("The variable is overwritten every time and doesn't need persistence");
// Calculate gas waste
// If faucetClaimer was a local variable, we wouldn't need storage writes
// Each storage write costs approximately 20,000 gas for first write, 5,000 for subsequent writes
uint256 estimatedStorageWriteGas = 5000; // Approximate cost for storage write
console.log("Estimated gas waste per claim (storage write):", estimatedStorageWriteGas);
// Test with multiple users to show cumulative gas waste
address[] memory users = new address[](5);
for (uint256 i = 0; i < 5; i++) {
users[i] = makeAddr(string(abi.encodePacked("testUser", i)));
}
uint256 totalGasWaste = 0;
for (uint256 i = 0; i < users.length; i++) {
uint256 gasBefore = gasleft();
vm.prank(users[i]);
raiseBoxFaucet.claimFaucetTokens();
uint256 gasAfter = gasleft();
uint256 gasUsed = gasBefore - gasAfter;
totalGasWaste += gasUsed;
console.log("Gas used for user", i + 1, "claim:", gasUsed);
}
console.log("Total gas consumed for 5 claims:", totalGasWaste);
console.log("Estimated gas waste due to storage writes:", estimatedStorageWriteGas * 5);
console.log("Impact: Unnecessary gas consumption for every claim operation");
console.log("Solution: Change faucetClaimer to local variable in claimFaucetTokens function");
}

Recommended Mitigation

function claimFaucetTokens() public {
address faucetClaimer = msg.sender; // Change to a local variable
...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 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.