Raisebox Faucet

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

Missing Emergency Controls

Author Revealed upon completion

Missing Emergency Controls

Description

The contract should implement emergency pause mechanisms to halt operations during security incidents or when vulnerabilities are discovered.

The contract lacks any emergency stop functionality that could prevent continued exploitation during security incidents, leaving users and funds vulnerable during the critical time needed to respond to attacks or deploy fixes.

@> function claimFaucetTokens() public {
// No pause mechanism to halt during emergencies
// Function continues to operate even during security incidents
// No way to stop ongoing attacks while developing fixes
}
// No emergency pause functions exist anywhere in the contract

Risk

Likelihood: Medium

  • Security vulnerabilities are discovered in production smart contracts regularly in the DeFi space

  • DeFi protocols frequently need emergency pause mechanisms during incidents and exploits

  • Hackers may exploit vulnerabilities during the extended time needed to develop and deploy fixes

Impact: Medium

  • Continued exploitation of vulnerabilities during incident response periods

  • Greater financial losses due to inability to halt malicious activities immediately

  • Damage to protocol reputation due to inability to respond quickly to security threats

  • User funds remain at risk during extended vulnerability disclosure and fix development periods

Proof of Concept

// Demonstrate lack of emergency controls during attack
contract EmergencyControlsTest {
RaiseBoxFaucet public faucet;
bool public attackInProgress;
constructor(address _faucet) {
faucet = RaiseBoxFaucet(_faucet);
}
// Simulate ongoing attack that cannot be stopped
function simulateUnstoppableAttack() external {
attackInProgress = true;
// Scenario: Reentrancy attack is discovered and being exploited
while (address(faucet).balance > 0 && attackInProgress) {
try faucet.claimFaucetTokens() {
// Attack continues successfully
// Protocol team has no way to halt this
} catch {
// Even if individual calls fail, attacker can retry
// No emergency brake to stop the exploitation
}
}
// Protocol team's limited options during attack:
// 1. Deploy new contract (abandons current users and funds)
// 2. Wait for attacker to finish (complete fund loss)
// 3. Try to compete with attacker (unlikely to succeed)
// 4. No immediate protective action possible
}
// Show time-critical nature of security incidents
function demonstrateIncidentTimeline() external pure returns (
string memory phase1,
string memory phase2,
string memory phase3,
string memory phase4
) {
phase1 = "T+0min: Attack detected, funds draining";
phase2 = "T+15min: Team assesses vulnerability, no way to stop drain";
phase3 = "T+60min: Fix developed, but attacker still draining";
phase4 = "T+120min: New contract deployed, original funds lost";
return (phase1, phase2, phase3, phase4);
}
// Compare with protocols that have emergency controls
function showBestPracticeResponse() external pure returns (
string memory step1,
string memory step2,
string memory step3
) {
step1 = "T+0min: Attack detected, emergency pause activated immediately";
step2 = "T+15min: Vulnerability assessed safely, no further fund loss";
step3 = "T+60min: Fix deployed and tested, operations resumed safely";
return (step1, step2, step3);
}
}

Scenario without emergency controls:

  1. Security researcher discovers reentrancy vulnerability

  2. Before disclosure, malicious actor also finds vulnerability

  3. Attacker begins draining faucet systematically

  4. Protocol team gets alert about unusual activity

  5. Team confirms attack in progress but has no way to stop it

  6. While team develops fix, attacker continues draining

  7. By time fix is ready, significant damage already done

  8. Users lose trust due to slow/ineffective response

Recommended Mitigation

The mitigation implements comprehensive emergency controls using OpenZeppelin's Pausable contract, providing immediate response capabilities during security incidents while maintaining proper access controls and transparency.

+ import "@openzeppelin/contracts/security/Pausable.sol";
- contract RaiseBoxFaucet is ERC20, Ownable, ReentrancyGuard {
+ contract RaiseBoxFaucet is ERC20, Ownable, ReentrancyGuard, Pausable {
// Add pause protection to critical functions
- function claimFaucetTokens() public nonReentrant {
+ function claimFaucetTokens() public nonReentrant whenNotPaused {
// All existing logic remains the same
// Function automatically blocked when paused
}
// Implement emergency pause with proper access controls
+ function emergencyPause() external onlyOwner {
+ require(!paused(), "Already paused");
+ _pause();
+ emit EmergencyPaused(msg.sender, block.timestamp, "Emergency pause activated");
+ }
+
+ // Implement gradual unpause with safety checks
+ function emergencyUnpause() external onlyOwner {
+ require(paused(), "Not paused");
+ _unpause();
+ emit EmergencyUnpaused(msg.sender, block.timestamp, "Emergency pause deactivated");
+ }
// Add pause protection to other critical functions
- function mintFaucetTokens(address to, uint256 amount) public onlyOwner {
+ function mintFaucetTokens(address to, uint256 amount) public onlyOwner whenNotPaused {
// Existing logic
}
- function refillSepEth(uint256 amountToRefill) external payable onlyOwner {
+ function refillSepEth(uint256 amountToRefill) external payable onlyOwner whenNotPaused {
// Existing logic
}
// Implement timelocked unpause for additional security
+ uint256 public constant UNPAUSE_DELAY = 6 hours; // Minimum pause duration
+ uint256 public pauseStartTime;
+
+ function _pause() internal override {
+ pauseStartTime = block.timestamp;
+ super._pause();
+ }
+
+ function emergencyUnpauseWithDelay() external onlyOwner {
+ require(paused(), "Not paused");
+ require(block.timestamp >= pauseStartTime + UNPAUSE_DELAY, "Minimum pause duration not met");
+ _unpause();
+ emit EmergencyUnpausedWithDelay(msg.sender, block.timestamp);
+ }
// Add view functions for emergency status monitoring
+ function getEmergencyStatus() external view returns (
+ bool isPaused,
+ uint256 pausedAt,
+ uint256 earliestUnpause
+ ) {
+ isPaused = paused();
+ pausedAt = pauseStartTime;
+ earliestUnpause = isPaused ? pauseStartTime + UNPAUSE_DELAY : 0;
+ return (isPaused, pausedAt, earliestUnpause);
+ }
// Allow view functions to work during pause for monitoring
+ function getFaucetTotalSupply() public view returns (uint256) {
+ return balanceOf(address(this)); // Works during pause for monitoring
+ }
+
+ function getContractSepEthBalance() public view returns (uint256) {
+ return address(this).balance; // Works during pause for monitoring
+ }
// Implement emergency withdrawal for owner (last resort)
+ function emergencyWithdrawEth() external onlyOwner {
+ require(paused(), "Can only withdraw ETH when paused");
+ require(address(this).balance > 0, "No ETH to withdraw");
+
+ uint256 amount = address(this).balance;
+ (bool success, ) = msg.sender.call{value: amount}("");
+ require(success, "ETH withdrawal failed");
+
+ emit EmergencyEthWithdrawn(msg.sender, amount);
+ }
// Add comprehensive emergency events
+ event EmergencyPaused(address indexed pauser, uint256 timestamp, string reason);
+ event EmergencyUnpaused(address indexed unpauser, uint256 timestamp, string reason);
+ event EmergencyUnpausedWithDelay(address indexed unpauser, uint256 timestamp);
+ event EmergencyEthWithdrawn(address indexed withdrawer, uint256 amount);
// Override receive and fallback to work during pause (for donations)
receive() external payable {
// Allow ETH donations even when paused
emit SepEthDonated(msg.sender, msg.value);
}
fallback() external payable {
// Allow ETH donations even when paused
emit SepEthDonated(msg.sender, msg.value);
}
Updates

Lead Judging Commences

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

Support

FAQs

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