Discussion: Defense-in-Depth — Emergency Controls & Transfer-Layer Safety

Pre-SLURP discussion document — intended to open a thread on talk.stake.link. If the community converges on scope, this can be formalized as a SLURP in a follow-up.

Summary

This is a discussion document — not a SLURP — intended to open a forum thread on talk.stake.link and gauge community appetite before anyone invests effort drafting a formal proposal. It sketches a protocol-level emergency-response module that would close the gap between exploit detection and on-chain response: a new EmergencyController contract that consolidates (a) a transfer-layer hook on stLINK and SDL and (b) scoped pause logic for critical user paths, activated by a whitelisted automated monitoring address (Hypernative) rather than a manual multisig. A secondary topic — rate-limiting on large withdrawal flows — is mentioned briefly in an appendix but is not the focus of this discussion.

If the thread converges on scope, activator model, and governance sunset parameters, the natural next step is a formal SLURP that the DAO governance multisig can execute through the GovernanceTimelock once ratified.

Motivation

The recent Kelp DAO incident — a cross-chain / off-chain verifier compromise where funds were drained faster than governance could respond — demonstrates that smart-contract correctness is only half of protocol safety. The other half is what the protocol can do in the minutes immediately following breach detection. Kelp’s emergency multisig froze contracts roughly 46 minutes after detection and meaningfully reduced losses; the lesson we draw is that even multisig-mediated response is often too slow, and that automated detection wired to on-chain response is strictly better wherever trust assumptions permit.

The current stakedotlink/contracts codebase has strong deposit/withdrawal logic, but the incident-response surface is fragmented:

· PriorityPool inherits PausableUpgradeable, but that mechanism is used operationally for merkle distribution windows — it is not an emergency pause. Re-using it for exploit response would overload its semantics and interfere with distributions.

· The existing emergency-pause path is RebaseController.pausePool() via emergencyPauser, which flips poolStatus to CLOSED. Hypernative already holds a 1/1 authorized address on that path and can react within seconds of detection. This is the right model — but it is scoped to slashing-adjacent state and does not cover the stLINK / SDL transfer layer.

· StakingPool, WithdrawalPool, SecurityPool (outside slashing), SDLPool, and the stLINK / SDL ERC-677 tokens have no transfer hook and no way to halt laundering of stolen liquid tokens during an active incident.

· Access control is OwnableUpgradeable singletons with owner = GovernanceTimelock (0xb72d8F5213b3E52FAf13Aa074b03C4788e78349F). Proposer and executor is the DAO governance multisig (0xB351EC0FEaF4B99FdFD36b484d9EC90D0422493D), with the Governance Council ratifying proposals and reSDL holders voting via Snapshot. Every privileged deactivation must sit out the full timelock delay before execution — which is correct for unpausing, but too slow for activation.

Design Choice: Automated Activation over Manual Multisig

An earlier draft of this document proposed a 2-of-N Guardian multisig as the emergency pauser. On reflection, a manual multisig is the wrong primitive for the activation side of incident response:

· Exploits drain funds in minutes. Human signer coordination takes longer than that in the worst case.

· Hypernative already monitors the protocol and already holds an authorized pauser address for the existing poolStatus emergency path. Extending that same model to the new module re-uses trust assumptions the protocol has already accepted.

· Pausing is cheap and reversible; unpausing is the privileged operation. The correct asymmetry is: a fast, automated activator can pause, but only the DAO via timelock can unpause or unfreeze.

Accordingly, the proposal below is structured around a single new contract that consolidates the transfer hook and pause logic, with activation whitelisted to Hypernative’s monitoring address (and, optionally, a small backup set) rather than gated on a manual multisig.

Proposed Module (for Discussion)

The spec below is a starting point — the exact gating surfaces, activator set, and sunset parameters are open for debate in the forum thread.

EmergencyController — Transfer-Layer Hook + Scoped Pauser

Goal: a single new contract, owned by the GovernanceTimelock, that (a) provides a transfer-layer hook for stLINK and SDL, (b) exposes scoped pause flags for critical user paths, and (c) is activated by a whitelist of authorized addresses — primarily Hypernative’s automated monitor — while deactivation remains with governance.

Activation surface:

· transfersPaused (bool) — halts stLINK / SDL transfers globally during an active incident. Whitelisted activator flips true; only GovernanceTimelock flips false.

· frozen(address) (mapping) — freezes individual known-exploiter addresses. Whitelisted activator can freeze; only GovernanceTimelock can unfreeze. Hardcoded simultaneous-freeze cap (e.g. 16) to prevent role abuse.

· criticalPathsPaused (bitmap or per-contract flag) — scoped pause for WithdrawalPool.queueWithdrawal, WithdrawalPool.withdraw(ids, batchIds), WithdrawalPool.unqueueTokens, and SDLPool entry/exit paths. Intentionally does NOT pause PriorityPool deposit/withdraw — that contract’s Pausable is reserved for merkle distribution windows and should not be overloaded. Instead, a parallel guard is added at the PriorityPool call-site that consults EmergencyController.

Integration points:

· Override _transfer in StakingRewardsPoolBase.sol (currently has no hook) to call an internal _beforeTokenTransfer(from, to, amount) which consults EmergencyController.transfersPaused and EmergencyController.frozen(from) / EmergencyController.frozen(to).

· Add equivalent consults in SDL’s transfer path, and in the designated critical-path functions listed above. The check is a single external SLOAD in the default case — budget target ≤ 2,500 extra gas per transfer in the no-op path.

Activator whitelist:

· Primary: Hypernative’s existing authorized monitoring address (the same 1/1 address already authorized for RebaseController.pausePool()).

· Optional backup: a small additional set (e.g. 1–2 Council-held addresses) strictly as a failover in case Hypernative is unreachable. Backup addresses have identical powers — activate only, never deactivate.

· Whitelist is mutable by GovernanceTimelock so the DAO can add, rotate, or remove activator addresses through the standard SLURP path.

Scope boundaries:

· No generic blocklist / sanctions-list integration. The freeze list is scoped to “halt a known exploiter address within the incident window” — not compliance gating.

· Governance sunset: any transfer pause or individual freeze auto-expires after 14 days unless extended through the full SLURP path (reSDL Snapshot vote + Council ratification, executed by the DAO multisig via the timelock). Prevents indefinite activator-only state.

· The existing RebaseController.pausePool() / emergencyPauser path is unchanged. This module is additive.

Role & Trust Assumptions

· Activator (Hypernative monitoring address, plus optional backup): can flip transfersPaused=true, freeze individual addresses (capped), and set criticalPathsPaused. Cannot unpause, unfreeze, upgrade, or modify the whitelist.

· GovernanceTimelock (0xb72d…349F), executing transactions proposed by the DAO governance multisig (0xB351…493D) after the standard authorization flow (forum → reSDL Snapshot vote → Council ratifies → multisig executes): unpause, unfreeze, upgrade implementations, rotate activator whitelist, tighten or loosen any parameter.

· emergencyPauser (existing): unchanged. Retained for the existing RebaseController slashing flow; not expanded by this proposal.

Rationale & Design Choices

· Automated activation, governance-gated deactivation. Asymmetric trust — a false positive from the activator is a temporary pause (recoverable in hours via timelock); a true positive saves funds in the minutes that matter most.

· Single consolidated contract. Keeping the token hook and pause logic in one place — EmergencyController — gives Hypernative (and any future activator) one integration target, simplifies audit scope, and means the transfer-hook consult is a single external SLOAD.

· PriorityPool’s existing PausableUpgradeable is left alone. It is used for merkle distribution windows; overloading it for emergency response would produce operational ambiguity. Emergency-path gating for PriorityPool is achieved by adding an explicit EmergencyController consult, not by re-using Pausable.

· Freeze list is capped and time-boxed. Prevents the activator role from becoming a de-facto compliance blocklist. Any lasting freeze must be ratified through the standard SLURP path.

· No OFAC / sanctions integration. Keeps the protocol neutral. The freeze mechanism is scoped to active-incident response.

Backwards Compatibility

All additions are non-breaking at the ERC-20 / ERC-677 interface level — transfers, deposits, and withdrawals retain their signatures and revert semantics. Users and integrators see no API change in the default (unpaused, non-frozen) state. PriorityPool’s existing Pausable semantics — used for merkle distributions — are preserved unchanged. Upgrade is performed via the existing UUPS pattern under GovernanceTimelock authorization.

Security Considerations

· Hypernative monitor key compromise: worst case is a denial-of-service pause or a capped set of false-positive freezes. DAO unpauses / unfreezes via timelock. Signer rotation and Hypernative SLA should be documented on the forum.

· Hypernative monitor unreachable during incident: mitigated by the optional Council-held backup activator set.

· Timelock compromise: out of scope for this proposal — pre-existing trust assumption.

· Freeze-list abuse: mitigated by the hardcoded capacity limit, 14-day sunset, and timelock-only unfreeze.

· Transfer hook gas cost: must remain cheap in the no-op path — benchmark target ≤ 2,500 extra gas per transfer in the default case. Single external SLOAD + branch.

· Upgrade hook interaction: EmergencyController is UUPS-upgradeable under timelock, consistent with the rest of the codebase.

Implementation Plan

· Week 1: contract scaffolding — EmergencyController with activator whitelist, transfersPaused, frozen mapping, criticalPathsPaused; _beforeTokenTransfer hook wired into StakingRewardsPoolBase and SDL.

· Week 2: tests — unit, fuzz, and fork tests covering activate/deactivate flows, freeze/unfreeze, sunset expiry, cap enforcement, gas regression, and integration against the existing Hypernative monitor.

· Week 3: audit engagement — one pass from a recognized firm (Sigma Prime / Trail of Bits / ChainSecurity tier).

· Week 4: Council review of activator whitelist (Hypernative address + optional backup set), deployment proposal, on-chain vote, timelock queue, execution.

Indicative Cost (If Formalized)

· Engineering: 2–3 weeks of core-team time.

· Audit: one pass from a recognized firm (Sigma Prime / Trail of Bits / ChainSecurity tier).

· Bug bounty uplift: recommend raising Immunefi ceiling in line with added attack surface — would go in a separate SLURP.

Open Questions for the Forum

· Is there consensus that automated activation (Hypernative) is preferable to a manual Guardian multisig for the activation side of incident response?

· Should the activator whitelist be Hypernative-only, or include a small Council-held backup set as failover? If backup: how many addresses and who holds them?

· Is the 14-day auto-expiry on pauses and freezes the right window? Too short, too long?

· Should the freeze cap (suggested: 16 simultaneous addresses) be higher, lower, or parameterized by governance?

· Agreement that PriorityPool’s existing Pausable should be left alone (reserved for merkle distributions) and emergency gating added via an explicit EmergencyController consult?

· Does the community want the rate-limiting appendix developed further, or is it off the table entirely?

· Are there other emergency controls — not covered here — that should be part of the same module?

Appendix A — Optional Rate-Limiting (Not in Scope)

Included for transparency; not part of this proposal’s authorization request. A future proposal could add a rolling 24h withdrawal cap on PriorityPool and WithdrawalPool, expressed as min(absoluteCap, percentOfTVL), plus per-address cooldowns on large withdrawals. The UX cost on large depositors is non-trivial and warrants a dedicated discussion thread. The EmergencyController module addresses the exploit-response window more directly and should ship first.

Appendix B — Referenced Contracts

· Governance multisig (Gnosis Safe): 0xB351EC0FEaF4B99FdFD36b484d9EC90D0422493D

· GovernanceTimelock: 0xb72d8F5213b3E52FAf13Aa074b03C4788e78349F

· contracts/core/priorityPool/PriorityPool.sol (Pausable reserved for merkle distributions)

· contracts/core/priorityPool/WithdrawalPool.sol

· contracts/core/StakingPool.sol

· contracts/core/SecurityPool.sol

· contracts/core/RebaseController.sol (existing emergencyPauser / Hypernative path — unchanged)

· contracts/core/sdlPool/SDLPool.sol

· contracts/core/base/StakingRewardsPoolBase.sol (target for _beforeTokenTransfer hook)

· contracts/governance/GovernanceTimelock.sol

2 Likes