Safety Mechanisms
Olla uses multiple layers of protection to safeguard user assets: automatic circuit breakers, a guardian role for manual intervention, and a governance timelock for parameter changes.
Circuit breakers
The SafetyModule monitors protocol health and automatically pauses operations when predefined thresholds are breached. It runs three independent checks:
Rate drop breaker
Triggers when the exchange rate drops by more than a configured threshold between accounting updates. A sudden rate drop typically indicates a slashing event on the Aztec rollup.
| Parameter | Range | Setter |
|---|---|---|
minRateDropBps | 1 – 5,000 bps (0.01% – 50%) | setMinRateDropBps() |
When it fires: During updateAccounting(), the protocol compares the new exchange rate against the previous one. If the drop exceeds minRateDropBps, the circuit breaker triggers and pauses the protocol.
Queue ratio breaker
Triggers when pending withdrawal requests grow too large relative to total protocol assets. High queue pressure may indicate a bank-run scenario or an impending liquidity crisis.
| Parameter | Range | Setter |
|---|---|---|
maxQueueRatioBps | 100 – 9,000 bps (1% – 90%) | setMaxQueueRatioBps() |
When it fires: During rebalance (step 3: finalize withdrawals) and during updateAccounting(), the protocol checks queued assets / total assets. If it exceeds maxQueueRatioBps, the breaker triggers.
Accounting staleness breaker
Triggers when too much time passes without an accounting update. Stale accounting means the exchange rate doesn't reflect current on-chain state, which could mask slashing or reward changes.
| Parameter | Range | Setter |
|---|---|---|
maxAccountingDelay | 1 hour – 7 days | setMaxAccountingDelay() |
When it fires: During deposit and withdrawal operations, the protocol checks how long it's been since the last accounting update. If the elapsed time exceeds maxAccountingDelay, the breaker triggers.
What happens when a breaker triggers
When any circuit breaker fires, the SafetyModule:
- Sets its internal
pausedflag totrue. - Emits a
CircuitBreakerTriggered(reason)event identifying which breaker fired. - Emits a
Paused()event.
While paused, the protocol blocks:
- New deposits
- Instant redemptions
- New withdrawal requests
Existing queued withdrawals that are already finalized can still be claimed. Rebalance cycles in progress can still be continued. The guardian must manually unpause after investigating the cause.
Guardian role
The guardian is a privileged role (expected to be a multisig) that can respond to emergencies faster than governance. The guardian holds GUARDIAN_ROLE on OllaCore, OllaVault, and SafetyModule.
Guardian capabilities
| Action | Contract | Purpose |
|---|---|---|
pause() | OllaCore | Halt rebalancing and staking operations |
unpause() | OllaCore | Resume operations after investigation |
pause() | OllaVault | Halt deposits, redemptions, and withdrawals |
unpause() | OllaVault | Resume vault operations |
pause() | SafetyModule | Manually trigger protocol-wide pause |
unpause() | SafetyModule | Resume after circuit breaker or manual pause |
forceRebalanceReset() | OllaCore | Reset a stuck rebalance state machine |
What the guardian cannot do
The guardian is deliberately separated from governance to limit blast radius:
- Cannot upgrade contracts
- Cannot change fee parameters
- Cannot grant or revoke roles
- Cannot move user funds
- Cannot change the treasury or provider addresses
These actions require governance (the timelock), giving the community time to react.
Force rebalance reset
If a rebalance cycle gets stuck mid-execution (e.g., due to an external contract failure), the guardian can call forceRebalanceReset() to reset the state machine back to Done. This discards any in-progress work for that cycle:
- Unharvested rewards are not lost — they wait for the next cycle.
- Partial unstakes are tracked on-chain by the rollup and will be picked up in the next
refreshAttesterState()call.
The reset also updates lastRebalanceTimestamp, so the cooldown must elapse before a new cycle can start.
Deposit cap
Governance can set a maximum total deposit to limit protocol exposure during early operation or risk events.
| Parameter | Setter |
|---|---|
depositCap | setDepositCap(uint256) |
When set, checkDepositAllowed() rejects deposits that would push total assets above the cap. Setting the cap to type(uint256).max effectively disables it.
Withdrawal minimum
To prevent dust withdrawals that waste gas, governance can set a minimum share amount for withdrawal requests.
| Parameter | Range | Setter |
|---|---|---|
withdrawalMinimum | 0 – 1,000e18 shares | setWithdrawalMinimum(uint256) |
Both queued withdrawals (requestRedeem) and instant redemptions (instantRedeem) enforce this minimum.
Governance timelock
All governance actions — fee changes, parameter updates, contract upgrades, role grants — flow through OllaGovernance, which embeds a TimelockController. Actions must be:
- Scheduled by the governance admin (proposer role).
- Delayed for the configured timelock duration.
- Executed by the governance admin (executor role) after the delay.
This gives the community a window to review proposed changes and react (e.g., by exiting the protocol) before they take effect. The only exceptions are emergencyPauseAll() and emergencyUnpauseAll(), which bypass the timelock for rapid incident response.
Non-upgradeable SafetyModule
The SafetyModule is intentionally not upgradeable via UUPS proxy. This is a deliberate trust decision: as the protocol's circuit breaker, users need confidence that its behavior cannot be silently changed through a proxy upgrade. If the SafetyModule needs to be replaced, governance uses OllaCore.setSafetyModule() to point to a new deployment — a visible, auditable action that requires the protocol to be unpaused with no active rebalance.
See Trust Assumptions for the full security model.