Raisebox Faucet

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

Unhandled Edge Case in ETH Drip Logic

Description

The claimFaucetTokens function drips Sepolia ETH to first-time claimers but continues with token claims even if the ETH drip fails (due to insufficient contract balance or daily cap). This results in inconsistent behavior, where users receive tokens but not the expected ETH, causing confusion.

// Root cause in the codebase with @> marks to highlight the relevant section
function claimFaucetTokens() public {
// ... checks ...
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
// ... daily drip checks ...
@>if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
// ... drip ETH ...
@>} else {
@> emit SepEthDripSkipped(...); // Continues with token claim
@>}
}
// ... token transfer ...
}

Risk

Likelihood:

  • Occurs when the contract’s ETH balance is below sepEthAmountToDrip or dailyDrips exceeds dailySepEthCap.

  • Occurs for first-time claimers expecting both tokens and ETH.

Impact:

  • Users receive tokens without ETH, leading to confusion and poor user experience.

  • Could reduce trust in the faucet’s reliability for testnet interactions.

Proof of Concept

Explanation: The PoC shows that when the contract has insufficient ETH, the claimFaucetTokens function emits SepEthDripSkipped but still transfers tokens. This creates an inconsistent experience for first-time claimers expecting both ETH and tokens.

function testEthDripFailure(RaiseBoxFaucet faucet) public {
// Contract has insufficient ETH
vm.deal(address(faucet), 0);
vm.prank(user);
faucet.claimFaucetTokens(); // Emits SepEthDripSkipped, still transfers tokens
}

Recommended Mitigation

Explanation: We modify claimFaucetTokens to revert the entire transaction for first-time claimers if the ETH drip fails, ensuring that users either receive both tokens and ETH or neither. This provides a consistent user experience and aligns with the faucet’s purpose.

function claimFaucetTokens() public {
// ... checks ...
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
// ... daily drip checks ...
if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
// ... drip ETH ...
} else {
- emit SepEthDripSkipped(...);
+ revert("ETH drip failed: insufficient balance or daily cap reached");
}
}
// ... rest of function ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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