Emergency withdrawal
A deliberate, formally declared escape hatch. It is not an early-exit feature, and it is not available under normal conditions.
Declaring an emergency
function setEmergencyMode(bool enabled) external onlyRole(EMERGENCY_ROLE) { emergencyMode = enabled; if (enabled && !paused()) _pause(); emit EmergencyModeSet(enabled);}Declaring one also pauses new staking, so nobody joins a system that is being evacuated.
What the exit does
function emergencyWithdraw(uint256 positionId) external nonReentrant returns (uint256 principal) { if (!emergencyMode) revert NotEmergency(); // ... ownership and open checks ... uint256 forfeited = pendingRewards(positionId); principal = p.amount; uint256 weight = p.weight; p.amount = 0; p.weight = 0; p.rewardDebt = 0; totalStaked -= principal; totalWeight -= weight; // Forfeited ETH is recycled to the remaining stakers, or held if none remain. if (forfeited != 0) { if (totalWeight != 0) { accRewardPerWeight += (forfeited * ACC_PRECISION) / totalWeight; } else { pendingUndistributed += forfeited; } } emit EmergencyWithdrawn(msg.sender, positionId, principal, forfeited); IERC20(registry.ploutoToken()).safeTransfer(msg.sender, principal);}- You receive
Your full principal, regardless of maturity.
- You forfeit
All unclaimed ETH rewards for that position.
- The forfeited rewards
Go to the stakers who remain. If none remain, they are held and flushed to the next staker. No admin captures them.
Claim first
Rewards are only forfeited if they are still unclaimed. If the ETH path is functioning, claim before exiting:
claim(positionId) → emergencyWithdraw(positionId)The interface shows the emergency exit only while emergencyMode is true, and the button carries a title explaining that rewards are forfeited.
Why not a permanent early exit
A penalty-based early exit would need a penalty rate, which is a governance parameter, which is a lever someone could tune against stakers. Fixed-term means fixed-term, and the escape hatch is scoped to genuine emergencies rather than being a routine feature with a fee.
Clearing an emergency
setEmergencyMode(false) closes the exit. Staking remains paused until unpause() is called separately, so resuming is two deliberate actions rather than one.
Testing
The suite drives the full path: declare, verify staking is paused, exit with pending rewards, assert principal returned, assert zero ETH paid to the exiting staker, and assert the forfeited amount reappears in the remaining staker's pending balance.