Skip to Content

Strategy: Roles and Security


Access control

Strategy contracts use OpenZeppelin AccessControlDefaultAdminRulesUpgradeable, which adds a mandatory timelock on DEFAULT_ADMIN_ROLE transfers. All other roles are granted and revoked by the default admin.

Roles are designed to protect the depositor, and balance security needs of the Strategy operator. Hence, whilst DEFAULT_ADMIN_ROLE is behind a timelock, other operational roles are not.

Strategy roles

RoleCapabilities
DEFAULT_ADMIN_ROLEManage role membership; registerShard; add / remove deposit assets; setAssetConverter; set redeemFeeBps, managementFeeBps, performanceFeeBps; replace blocklist oracle
MANAGER_ROLEsetFeeTreasury; setManagementFeeTreasury; setDepositFeeBps; setPricePerShareRateLimit; deregister shards; setDefaultShard; unpauseDeposit; unpauseRedeem; unpause (ERC-20 transfers)
PRICE_SETTER_ROLEpostPricePerShare
STRATEGY_PAUSER_ROLEpauseDeposit; pauseRedeem
TRANSFERS_PAUSER_ROLEpause (ERC-20 transfer pause)
PAY_REDEMPTIONS_ROLEfulfillRedeemRequests
PERF_FEE_CLAIM_ROLEharvestPerformanceFee

Shard roles

RoleContractCapabilities
DEFAULT_ADMIN_ROLEShardBaseUpgradeablesetValidator; grant / revoke all shard roles
PRIVILEGED_EXECUTOR_ROLEShardBaseUpgradeableexec(to, value, data) (validator bypass for emergencies)
SHARD_TRANSFER_ROLEShardpullFromStrategy; pushToStrategy

Blocklist Oracle roles

RoleCapabilities
DEFAULT_ADMIN_ROLEunblockAccount; add / remove third-party oracle lists (e.g. Chainalysis)
BLOCKLIST_SETTER_ROLEblockAccount (manual blocklist add)

Design: PRICE_SETTER_ROLE vs MANAGER_ROLE separation

The rate-limit configuration (MANAGER_ROLE via setPricePerShareRateLimit) and price posting itself (PRICE_SETTER_ROLE via postPricePerShare) are deliberately held by different parties. Compromising one key alone cannot both widen the allowed price band and post an out-of-range price; both keys would need to be compromised simultaneously.


Pause surfaces

The strategy has three independent pause switches, each with a distinct scope:

SwitchWho pausesWho unpausesEffect
Deposit pauseSTRATEGY_PAUSER_ROLEMANAGER_ROLEBlocks deposit and mint
Redeem pauseSTRATEGY_PAUSER_ROLEMANAGER_ROLEBlocks requestRedeem
ERC-20 transfer pauseTRANSFERS_PAUSER_ROLEMANAGER_ROLEBlocks all share transfers (ERC20Pausable)

These three switches are fully independent:

  • Pausing deposits does not affect redemption requests or transfers.
  • Pausing transfers does not affect deposit or redemption request entrypoints (but does block share transfers that are part of fee routing if combined with other conditions).
  • fulfillRedeemRequests is not blocked by any of the three pauses; once shares are burned and a request is queued, the operator can always pay it out.

Automatic pause on price rate-limit breach

postPricePerShare accepts a revertOnRateLimit flag:

  • revertOnRateLimit = true: reverts with PriceRateLimiter_RateLimitExceeded if the move exceeds the bucket capacity.
  • revertOnRateLimit = false: sets the price, drains the bucket, and automatically pauses both deposit and redeem. This is an auto-circuit-breaker for price feeds that push an unexpectedly large move.

Both pauses must be explicitly cleared by MANAGER_ROLE before normal operations resume.


Price rate limiter

pricePerShare moves are capped by a token-bucket rate limiter (PriceRateLimiter) that measures the relative price change in micro-bps (1 bps = 10,000 micro-bps):

relativeChangeBps = |newPPS - oldPPS| × 10_000 / oldPPS

The bucket has:

  • maxBps: maximum burst capacity (ceiling on how large a single move can be before the bucket is empty).
  • refillRate: capacity refilled per second, allowing larger cumulative moves over time.

MANAGER_ROLE configures both parameters via setPricePerShareRateLimit. The bucket starts at zero capacity on deployment; setPricePerShareRateLimit must be called before the first postPricePerShare will succeed. Current bucket state (with refill virtually applied) is readable via pricePerShareRateLimiterState().


Compliance: Blocklist Oracle

Every deposit / mint and requestRedeem call runs a blocklist check via IBlocklistOracle.check(account). If the account is blocked, the oracle reverts and the operation fails.

The BlocklistOracle contract combines:

  • A manual blocklist (blockAccount / unblockAccount), managed by BLOCKLIST_SETTER_ROLE and DEFAULT_ADMIN_ROLE respectively.
  • An iterable registry of ISanctionList contracts (e.g. Chainalysis), each of which is consulted on every check call.

If either the manual blocklist or any registered sanction list flags an account, check reverts.

The oracle address is set at initialisation and can be replaced by DEFAULT_ADMIN_ROLE via setBlocklistOracle. It can never be set to address(0); the compliance requirement cannot be disabled on-chain.

Scope: blocklist checks fire on deposit sender, deposit receiver, and requestRedeem owner only. Generic ERC-20 share transfers are not individually checked by the strategy.

Last updated on