Skip to main content

OllaGovernance

What is OllaGovernance?

OllaGovernance is the protocol's timelock and the on-chain admin of every other contract. It inherits OpenZeppelin's TimelockControllerUpgradeable and is itself UUPS-upgradeable.

Two distinct entities use the same name in conversation, but they are not the same:

  • The OllaGovernance contract: a single deployed proxy address. This is the owner() of OllaCore and OllaVault, the DEFAULT_ADMIN_ROLE holder on every satellite, and the upgrade authority for the proxies it controls.
  • The governance admin: a multisig wallet held by the team / DAO that holds the timelock's PROPOSER_ROLE, EXECUTOR_ROLE, and CANCELLER_ROLE. It is the wallet that operates the timelock. Tracked on-chain as governanceAdmin.

Transferring governance changes the admin wallet, not the contract address. Satellite admin pointers do not need to move during a transfer.

Key responsibilities:

  • Timelocking parameter changes and upgrades.
  • Wrapping setters on OllaCore, OllaVault, and SafetyModule as onlySelf passthroughs (so they can only be invoked via the timelock execute flow).
  • Holding the treasury address and routing it to fee distribution.
  • Two-step governance handover (proposeGovernance then acceptGovernance).
  • Emergency pause()/unpause() of OllaCore and OllaVault that bypass the timelock.

How a setter actually fires

Every parameter change happens in three steps:

  1. The governance admin wallet calls schedule(...) on the timelock with calldata that targets OllaGovernance itself, e.g. setProtocolFeeBP(newFeeBP).
  2. The timelock waits minDelay.
  3. The governance admin wallet calls execute(...) on the timelock. The timelock makes the internal call to OllaGovernance.setProtocolFeeBP(newFeeBP) with msg.sender == address(this). The onlySelf modifier passes only because of this self-call.
  4. OllaGovernance.setProtocolFeeBP then forwards to OllaCore.setProtocolFeeBP, which checks onlyOwner (owner() == OllaGovernance) and applies the change.

The end result: every parameter change waits through the timelock, no matter which contract it ultimately mutates.

Where setters live

Some settings live on OllaGovernance as wrappers; others live directly on the target contract and are invoked by scheduling a direct timelock action targeting that contract.

ParameterWrapped by OllaGovernance (timelocked)Lives on
setProtocolFeeBPyesOllaCore
setTreasuryFeeSplitBPyesOllaCore
setRebalanceGasThresholdyesOllaCore (also propagates to StakingManager)
setRebalanceCooldownyesOllaCore
setSafetyModuleyesOllaCore (replace the module without UUPS)
setDepositCapyesSafetyModule
setWithdrawalMinimumyesSafetyModule
setMinRateDropBpsyesSafetyModule
setMaxQueueRatioBpsyesSafetyModule
setMaxAccountingDelayyesSafetyModule
setTreasuryyesOllaGovernance
recoverStAztecyesOllaVault
reconcileBufferedAssetsyesOllaVault
setRateHighWaterMarkno, schedule a direct callSafetyModule
removeDrainedRewardRollupno, schedule a direct callStakingManager
setProviderRewardsRecipientno, staking-provider role, not governanceStakingProviderRegistry
setVaultone-time setVault (onlyOwner)OllaCore
setCoreone-time setCore (DEFAULT_ADMIN_ROLE)OllaGovernance

Rebalance derives the buffer target from OllaVault.pendingWithdrawalAssets() directly, unstaking enough to cover the queue and staking everything else.

Methods

Emergency

Callable directly by the governance admin, not timelocked, for rapid incident response. Restricted by if (msg.sender != governanceAdmin) revert.

function emergencyPauseAll() external
function emergencyUnpauseAll() external

emergencyPauseAll() calls pause() on both OllaCore and OllaVault. The SafetyModule is intentionally not paused. It is only reachable through Core and Vault, both of which are paused, so its independent pause is unnecessary.

Timelocked passthroughs (onlySelf)

Each of these reverts unless invoked via the timelock execute flow.

// OllaCore parameters
function setProtocolFeeBP(uint256 newFeeBP) external
function setTreasuryFeeSplitBP(uint256 newSplitBP) external
function setRebalanceGasThreshold(uint256 newThreshold) external
function setRebalanceCooldown(uint256 cooldown_) external
function setSafetyModule(address newSafetyModule) external

// OllaVault operations
function recoverStAztec(address recipient, uint256 amount) external
function reconcileBufferedAssets() external

// SafetyModule parameters
function setDepositCap(uint256 cap) external
function setWithdrawalMinimum(uint256 minShares) external
function setMinRateDropBps(uint256 bps) external
function setMaxQueueRatioBps(uint256 bps) external
function setMaxAccountingDelay(uint256 delay) external

// Treasury / governance handover
function setTreasury(address newTreasury) external
function proposeGovernance(address newGovernance) external
function cancelGovernanceProposal() external

Upgrades (onlySelf)

Both functions accept optional initialization calldata. Pass "" for a no-op upgrade or ABI-encoded initializer calldata for atomic post-upgrade init.

function upgradeCore(address newImplementation, bytes calldata data) external
function upgradeSatellite(address proxy, address newImplementation, bytes calldata data) external

OllaGovernance itself upgrades via the standard UUPS path (upgradeToAndCall), gated by _authorizeUpgrade which is onlySelf. Even self-upgrades go through the timelock.

Setup

function setCore(address core_) external // DEFAULT_ADMIN_ROLE, one-time

A pointer to OllaCore is required for the passthrough setters to know where to forward. setCore can only succeed once.

Governance handover

Two-step transfer to prevent typos sending governance into a black hole:

function proposeGovernance(address newGovernance) external // onlySelf (timelocked)
function acceptGovernance() external // direct call by the new admin
function cancelGovernanceProposal() external // onlySelf (timelocked)

When the new admin calls acceptGovernance():

  • PROPOSER_ROLE, EXECUTOR_ROLE, CANCELLER_ROLE, and DEFAULT_ADMIN_ROLE on the timelock are granted to the new admin and revoked from the old admin.
  • The on-chain governanceAdmin pointer is updated.
  • Satellite contracts are not touched. They were granted DEFAULT_ADMIN_ROLE to the OllaGovernance contract address (not to the admin wallet), so a wallet handover does not change who can upgrade or admin them.

View methods

function core() external view returns (address) // managed OllaCore
function treasury() external view returns (address) // current treasury
function pendingGovernance() external view returns (address)
function governanceAdmin() external view returns (address) // current admin wallet

Events

event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury)
event GovernanceTransferProposed(address indexed proposer, address indexed newGovernance)
event GovernanceTransferAccepted(address indexed oldGovernance, address indexed newGovernance)
event GovernanceTransferCancelled(address indexed pendingGovernance)
event EmergencyPauseAll()
event EmergencyUnpauseAll()
event CoreSet(address indexed core)