Roles
Roles and permissions
Five privileged roles across three contracts, plus a handful of functions that only the protocol's own contracts can call. This page is the user-facing summary — the complete enumeration with line references lives in docs/ROLES.md in the repository.
Summary
Governance (DEFAULT_ADMIN_ROLE)
Timelock (48h)
Yes — via upgrade
Yes — via upgrade
Admin (ADMIN_ROLE)
Timelock (48h)
Yes — emergencyRecover, whitelist
Indirectly, via parameters
Guardian (GUARDIAN_ROLE)
Safe (instant)
No
No
Operator (OPERATOR_ROLE)
Executor
Yes — execute
No
Rebalancer (REBALANCER_ROLE)
Offchain engine
No
Yes — entirely
The clean way to hold it in your head:
Operator controls where the money physically is. Rebalancer controls what the books say it is worth. Admin decides the rules. Governance decides the code. Guardian can only stop things.
Where each role exists:
DEFAULT_ADMIN_ROLE
✓
✓
✓
ADMIN_ROLE
✓
✓
✓
GUARDIAN_ROLE
✓
✓
—
OPERATOR_ROLE
—
✓
—
REBALANCER_ROLE
—
—
✓
Governance
DEFAULT_ADMIN_ROLE. Upgrade authority over all three contracts, and the admin of every other role. No contract calls _setRoleAdmin, so this one role can grant and revoke all of them.
upgradeToAndCall(newImplementation, data)
all three
Replaces the contract's code behind its proxy, optionally running a call on the new code in the same transaction.
grantRole(role, account)
all three
Gives account any role on that contract — including DEFAULT_ADMIN_ROLE itself.
revokeRole(role, account)
all three
Removes any role from any account.
An upgrade can replace any contract with arbitrary code, which subsumes every other limit in this documentation — including the hard-coded 1% exit fee cap.
Admin
ADMIN_ROLE. Governance parameters, plus powers over custody and the pause switch.
Vault
setFee(newFee)
Sets the exit fee charged on redemption. Reverts above 1% (1e18).
setCooldownPeriod(newPeriod)
Sets how long a redemption request must wait before it can be collected. Reverts above 7 days.
setVestingPeriod(newPeriod)
Sets how long reported profit takes to reach the share price. Reverts above 30 days. Profit still in flight is re-spread across the new period from now.
setManager(newManager)
Repoints the vault at a different Manager contract — which controls minting, burning and asset bookkeeping.
pause()
Blocks deposits and withdrawals, including operator-settled redemptions. Shared with the Guardian.
unpause()
Lifts the pause. Admin only — the Guardian cannot.
emergencyRecover(token, amount, recipient)
Transfers any token or ETH held by the vault to any address — including assets earmarked for pending redemptions.
Manager
setWhitelistedAddresses(wallets[], statuses[])
Adds or removes destinations the Operator may call through execute. Refuses the zero address and the protocol's own contracts, and refuses to run before the vault address is set.
removeWhitelistedAddresses(wallets[])
Removal-only version of the above. Shared with the Guardian.
setTreasury(treasury)
Changes where performance and exit fees are minted — and whose shares are burned on a loss.
setManagerAndYield(yield, vault)
Repoints the Manager at a different Yield contract and vault. (Despite the name, it sets Yield and the vault.) Refuses addresses that are currently whitelisted.
setMinSharesInVaultToken(vault, minShares)
Sets the minimum shares a deposit must mint. Must be non-zero.
setMaxRedeemCap(vault, cap)
Sets the largest amount of assets a single redemption request may be for. 0 removes the cap.
Yield
setMaxYieldPercentage(bps)
Caps each report's amount as a share of the vault's recognised assets, in basis points. Must be between 1 and 10,000. Default 50 (0.5%).
setMaxFeePercentage(bps)
Caps each report's feeAmount as a share of amount, in basis points. Up to 10,000 (100%). Default 5,000 (50%).
setMinDistributionInterval(seconds)
Sets the minimum gap between reports. Must be non-zero; no other floor. Default 79,200s (22 hours).
setManager(manager)
Repoints Yield at a different Manager.
Admin both sets the whitelist and can extract assets. The whitelist therefore constrains the operator, not the admin.
Guardian
GUARDIAN_ROLE, on the vault and the Manager, held by the Safe. It exists because the timelock applies one delay to everything, and two emergency actions cannot wait 48 hours.
pause()
Vault
Halts deposits and withdrawals immediately.
removeWhitelistedAddresses(wallets[])
Manager
Cuts off capital destinations immediately. Can only ever write false.
It is deliberately asymmetric. It can pause but not unpause, and remove whitelist entries but not add them. It cannot change any parameter, upgrade anything, or grant itself anything. The worst a compromised Guardian achieves is a denial of service that the timelock can undo — it can never create a path for funds to leave.
Operator
OPERATOR_ROLE, on the Manager only. This role holds the keys to the address custodying every deposited asset.
execute(targets[], data[])
Makes arbitrary calls to whitelisted addresses, batched, from the Manager. Within the whitelist there is no amount cap, no per-destination cap, and no rate limit. This is how capital reaches the offchain strategy.
transferToVault(token, amount)
Pushes assets from the Manager back into the vault so redemptions can be paid. This is the function your withdrawal depends on. It emits no event.
redeemFor(vault, user)
Settles a user's redemption request. Bounded: it can only pay that user, at the amount already locked in. It does not check the cooldown, so it can settle a request early. Blocked while paused.
Cannot: change the whitelist, grant roles, upgrade, pause, set parameters, or report yield.
Rebalancer
REBALANCER_ROLE, on Yield only.
distributeYield(amount, feeAmount, nonce, isProfit)
Reports one epoch's profit or loss. amount goes to shareholders and feeAmount to the treasury, as two separate slices. nonce must be exactly epoch + 1. See How profit and loss reach your shares.
It sets the share price. There is no oracle — the vault books what it is told, bounded by the per-epoch cap, the minimum interval, strict epoch sequencing, and duplicate rejection. The figures themselves are unverifiable onchain.
Cannot: move any asset, change the whitelist, grant roles, upgrade, pause, or set parameters. Its entire surface is the accounting.
Contract-only functions
These are not roles. Each function checks msg.sender against another protocol contract's address, so no person or key can call them directly — only the contract named. They are listed because the addresses they trust are set by Admin (setManager, setManagerAndYield).
transferInRewards(amount, profit)
Vault
Yield
Puts profit on the vesting schedule, or applies a loss.
updateTotalAssets(amount, add)
Vault
Manager
Moves virtualAssets up or down.
mintVaultToken(to, shares)
Vault
Manager
Mints shares — and only to the treasury.
burnVaultToken(from, shares)
Vault
Manager
Burns shares — and only from the treasury.
redeemFor(user)
Vault
Manager
The vault side of the Operator's redeemFor.
manageAssetAndShares(to, order)
Manager
Yield
Books the treasury's fee: adjusts assets, then mints or burns treasury shares.
If a role were compromised
Rebalancer
Mispriced redemptions; value leaks gradually
0.5%/epoch, ~22h interval
Operator
Assets sent to whitelisted destinations
The whitelist
Guardian
Vault paused, whitelist emptied — a denial of service
Admin can unpause and re-whitelist, after 48h
Admin
Whitelist any address then extract; 100% fee
48h public delay — needs the Safe to propose
Governance
Total — can grant itself every role, or upgrade
48h public delay — needs the Safe to propose
Verifying who holds what
Role holders are onchain state. Do not take them from documentation — read them.
The contracts use plain AccessControl, not the Enumerable variant, so there is no getRoleMember or getRoleMemberCount. To list every holder of a role, replay its grant history:
Then check each account with hasRole — a later RoleRevoked may have removed it.
If a holder is a Safe, getOwners() and getThreshold() give the real signing requirement. The Governance and Admin holder should be the timelock, not the Safe — Governance has the full check. Watch RoleGranted and RoleRevoked for rotations; for anything behind the timelock, a CallScheduled event on the timelock warns 48 hours ahead.
Known weaknesses in the current separation
The timelock delays, it does not prevent. Whitelisting, upgrades, and role rotations now take 48 hours and are public, but one Safe is still proposer, executor, canceller and guardian. It can also pause instantly and unpausing takes 48 hours, so a pause can block depositors from exiting during a pending change. See Governance and the timelock.
Admin mixes risk parameters with custody powers.
setFeeandemergencyRecoversit behind the same role, so parameter management cannot be delegated without also delegating fund extraction. The timelock delays both but does not separate them.The whitelist setter and the fund mover may be the same key. They must be distinct for the whitelist to function as a control.
Two parameters are unbounded in the direction that matters — the performance fee can reach 100%, and the reporting interval can be dropped to one second — while having no maximum, so reporting can also stop entirely with no staleness check.
Last updated