Roles & Actions
Every privileged role, its actions, timelock status, and recommended multisig threshold
Overview — Role hierarchy
① DEFAULT_ADMIN_ROLE
DEFAULT_ADMIN_ROLE
System-wide configuration, emergency controls, fee settings, vault registration
3/5 MULTISIG
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
setAdminFacet() |
VaultManager | NONE | Sets the admin facet for delegatecall | |
setAccessManager() |
VaultManagerAdmin | TIMELOCKED | Changes the access control contract | |
setShareToken() |
VaultManagerAdmin | TIMELOCKED | Changes the share token address | |
setFundVault() |
VaultManagerAdmin | TIMELOCKED | Changes FundVault address | |
setRequestManager() |
VaultManagerAdmin | TIMELOCKED | Changes AsyncRequestManager address | |
setPriceFeed() |
VaultManagerAdmin | TIMELOCKED | Changes the price oracle | |
setFundNav() |
VaultManagerAdmin | TIMELOCKED | Changes the FundNavFeed contract | |
setNavAggregateModel() |
VaultManagerAdmin | TIMELOCKED | Changes the NAV computation model | |
registerVault() |
VaultManagerAdmin | TIMELOCKED | Registers a new AssetVault | |
removeVault() |
VaultManagerAdmin | TIMELOCKED | Removes an AssetVault | |
setDeviationPps() |
VaultManagerAdmin | TIMELOCKED | PPS deviation safety limit | |
setMaxNavStaleness() |
VaultManagerAdmin | TIMELOCKED | NAV freshness window | |
setPerformanceFeeRate() |
VaultManagerAdmin | TIMELOCKED | Max 50% (0.5e18) | |
setManagementFeeRate() |
VaultManagerAdmin | TIMELOCKED | Max 10% (0.1e18) | |
setFeeReceiver() |
VaultManagerAdmin | TIMELOCKED | Where fees are sent | |
pauseContract() |
VaultManagerAdmin | IMMEDIATE | Emergency pause — no timelock by design | |
unpauseContract() |
VaultManagerAdmin | TIMELOCKED | Asymmetric: pause instant, unpause delayed | |
disableFunction() |
VaultManagerAdmin | IMMEDIATE | Emergency disable of a single function | |
enableFunction() |
VaultManagerAdmin | TIMELOCKED | Re-enable after investigation | |
setTimelockDuration() |
HaBaseUpgradeable | NONE | Controls timelock delay per function selector | |
addNavCategory() |
FundNavFeed | NONE | Add new NAV category (starts inactive) | |
removeNavCategory() |
FundNavFeed | NONE | Remove a NAV category | |
setCategoryStatus() |
FundNavFeed | NONE | Activate / deactivate a category |
Why 3/5 multisig: This role controls core contract addresses, fee parameters,
vault registration, and emergency pause. A compromised admin key could redirect fund flows, freeze the system, or
change fee receivers. The 3/5 threshold ensures no single party can make critical changes, while still allowing
fast emergency response (pause/disable are intentionally non-timelocked).
② UPGRADE_EXECUTOR_ROLE
UPGRADE_EXECUTOR_ROLE
UUPS proxy upgrade authorization — can replace contract implementations
3/5 MULTISIG
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
_authorizeUpgrade() |
VaultManager | NONE | Upgrade VaultManager implementation | |
_authorizeUpgrade() |
All HaBaseUpgradeable | NONE | Upgrade FundVault, AssetVault, etc. | |
_authorizeUpgrade() |
AccessManager | NONE | Upgrade the access control itself |
Why 3/5 multisig: A malicious upgrade can replace any contract with
an arbitrary implementation — effectively a skeleton key to the entire protocol. This should use the
same or higher threshold as DEFAULT_ADMIN_ROLE. Consider a separate multisig with different signers for defense-in-depth.
③ CURATOR_ROLE
CURATOR_ROLE
Capital allocation to strategies, fee collection, strategy whitelisting
2/3 MULTISIG
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
allocate(strategy, amount) |
FundVault | NONE | Move idle USDC to a whitelisted strategy | |
deallocate(strategy, amount) |
FundVault | NONE | Pull USDC back from strategy to FundVault | |
addStrategy(strategy) |
FundVault | TIMELOCKED | Whitelist a new strategy | |
removeStrategy(strategy) |
FundVault | TIMELOCKED | Remove a strategy from whitelist | |
setStrategyCap(strategy, cap) |
FundVault | TIMELOCKED | Set max allocation per strategy | |
harvestManagementFee() |
VaultManager | NONE | Mint fee shares (time-based accrual) | |
harvestPerformanceFee() |
VaultManager | NONE | Mint fee shares (high-watermark gated) |
Why 2/3 multisig: The curator controls where capital is deployed.
allocate() can only send funds to whitelisted strategies (which require timelock to add),
so the day-to-day risk is bounded. Fee harvesting is formulaic and cannot be gamed beyond the configured rates.
A 2/3 threshold balances operational agility with safety.
④ OPERATOR_ROLE
OPERATOR_ROLE
NAV updates, redeem fulfillment, off-chain NAV sync — day-to-day operations
2/3 MULTISIG
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
syncNavValue(asset, desc, nav) |
FundNavFeed | NONE | Update off-chain strategy NAV value | |
updateNav() |
VaultManager | NONE | Recompute PPS (bounded by deviationPps) | |
fulfillRedeem(amount, controllers) |
AssetVault | NONE | Pull USDC from FundVault, mark requests claimable | |
cancelRedeem(controllers) |
AssetVault | NONE | Cancel pending requests, return shares to users | |
sweep(amount) |
AssetVault | NONE | Return surplus assets from AssetVault to FundVault |
Why 2/3 multisig: Operator actions are frequent (daily NAV updates,
redeem fulfillments) and bounded by protocol guards:
deviationPps caps PPS movement,
fulfillRedeem only releases what was requested. A 2/3 multisig prevents a single
compromised signer from manipulating NAV or draining user redemptions.
⑤ PRICE_UPDATER_ROLE
PRICE_UPDATER_ROLE
Asset price feeds for denomination conversion
AUTOMATED KEEPER
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
setPrice(asset, price) |
PriceFeed | NONE | Set denomination price (must be non-zero) | |
enableAsset(asset, spotIndex, dec) |
HyperCorePriceFeed | TIMELOCKED | Whitelist asset for HyperCore oracle | |
disableAsset(asset) |
HyperCorePriceFeed | NONE | Remove asset from oracle (emergency) |
Why automated keeper: Price updates must be frequent and automated.
The risk is mitigated by
deviationPps on updateNav() — even if a bad price is
pushed, the PPS guard will reject the resulting NAV if it deviates too much. For manual setPrice(),
consider rate-limiting or a secondary validation layer.
⑥ STRATEGY_OPERATOR_ROLE
STRATEGY_OPERATOR_ROLE
Per-strategy operations — unique role per strategy address, cannot cross-operate
2/3 MULTISIG
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
withdrawToForDefi(wallet, amt) |
HaForDefiStrategy | NONE | Transfer assets to ForDefi MPC wallet | |
setForDefiValue(value) |
HaForDefiStrategy | NONE | Report off-chain position value | |
bridgeUsdcToCore(amount) |
HyperLiquidSpotStrategy | NONE | Bridge USDC to HyperCore spot balance | |
spotBuy(sz, limitPx) |
HyperLiquidSpotStrategy | NONE | IOC spot buy order on HyperCore | |
spotSell(sz, limitPx) |
HyperLiquidSpotStrategy | NONE | IOC spot sell order on HyperCore | |
bridgeUsdcToEvm(amount) |
HyperLiquidSpotStrategy | NONE | Bridge USDC back from HyperCore (async) | |
setDescription(desc) |
HaBaseStrategy | NONE | Update strategy metadata |
Why 2/3 multisig: Strategy operators can move real assets
(
withdrawToForDefi, bridging, trading). Each strategy has a unique role
(keccak256(abi.encode(address(this), "STRATEGY_OPERATOR"))) so compromise of one strategy's
operator cannot affect another. The 2/3 threshold prevents a single compromised signer from draining strategy funds.
⑦ SENTINEL_ROLE & TIMELOCK_PROPOSER_ROLE
SENTINEL_ROLE
Emergency cancellation of pending timelocked operations
1/1 EOA
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
revoke(data) |
HaBaseUpgradeable | NONE | Cancel any pending timelocked operation |
TIMELOCK_PROPOSER_ROLE
Queues timelocked operations, including upgrade implementation proposals, for future execution
2/3 MULTISIG
| Action | Contract | Timelock | Risk | Notes |
|---|---|---|---|---|
submit(data) |
HaBaseUpgradeable | NONE | Queue operations (including upgrade implementation proposals) — cannot execute until delay passes |
Design rationale:
Sentinel is a fast-response role (1/1 EOA) that can only cancel — it cannot
execute or queue anything. Its purpose is to block malicious proposals during the timelock window.
Timelock Proposer can queue operations but must wait for the delay to pass before anyone can
execute them, giving the sentinel time to intervene.
⑧ Timelock Mechanism
⑨ Summary — Recommended multisig thresholds
| Role | Multisig | Actions | Timelocked | Rationale |
|---|---|---|---|---|
| DEFAULT_ADMIN | 3/5 | 22 | 14 yes, 8 no | Controls all config + emergency; highest threshold |
| UPGRADE_EXECUTOR | 3/5 | 3 | 0 | Can replace any implementation; same as admin |
| CURATOR | 2/3 | 7 | 3 yes, 4 no | Capital allocation bounded by strategy whitelist + caps |
| OPERATOR | 2/3 | 5 | 0 | Frequent ops; bounded by deviationPps guard |
| PRICE_UPDATER | Keeper / EOA | 3 | 1 yes, 2 no | Automated; bad prices blocked by PPS deviation guard |
| STRATEGY_OPERATOR | 2/3 | 7 | 0 | Per-strategy isolation; moves real assets |
| SENTINEL | 1/1 EOA | 1 | 0 | Cancel-only; speed matters more than consensus |
| TIMELOCK_PROPOSER | 2/3 | 1 | 0 | Can only queue, not execute; delay is the guard |
Key design principles:
• Emergency actions bypass timelock —
• Recovery requires timelock —
• Separation of concerns — the UPGRADER_EXECUTOR_ROLE should ideally be a different multisig than DEFAULT_ADMIN_ROLE for defense-in-depth
• Per-strategy isolation — each strategy has a unique STRATEGY_OPERATOR_ROLE, so compromising one strategy's operator cannot affect others
• Emergency actions bypass timelock —
pauseContract() and disableFunction()
are intentionally immediate so the admin can respond to exploits instantly• Recovery requires timelock —
unpauseContract() and enableFunction()
are timelocked to prevent an attacker from immediately undoing a protective pause• Separation of concerns — the UPGRADER_EXECUTOR_ROLE should ideally be a different multisig than DEFAULT_ADMIN_ROLE for defense-in-depth
• Per-strategy isolation — each strategy has a unique STRATEGY_OPERATOR_ROLE, so compromising one strategy's operator cannot affect others