Architecture Overview
King's Vault V2 is an ERC-4626-based DeFi asset management system. Investors deposit assets into a Vault to receive vault shares; the operations team uses the Controller to manage vaults, strategies, NAV settlement, and performance fees; Keepers deploy idle assets according to strategies into Aave, ERC-4626 vaults, Morpho, or HyperEVM / HyperCore.
The system is divided into six main layers:
| Layer | Contract | Purpose |
|---|---|---|
| Vault | KingsVaultV2 |
Synchronous ERC-4626 vault supporting deposits, withdrawals, dual-pricing conversion, liquidity waterfall replenishment, and emergency redemption. |
| Async Vault | KingsVaultV2Async |
Asynchronous redemption version. Escrows shares during withdrawal, which are batch executed by a Keeper before the Investor can claim assets. |
| Controller | Controller |
UUPS upgradeable central coordinator managing vault registration, strategy registry, fund routing, NAV propose/confirm, and fee shares minting. |
| Standard Strategies | AaveV3Strategy, ERC4626Strategy, MorphoStrategy |
Deploys vault assets to external yield sources. |
| Cross-chain Strategy | HyperStrategy |
Source chain HyperEVM CCTP V2 strategy responsible for cross-chain burn/mint, remote state synchronization, and divest requests. |
| HyperEVM Execution | HyperCoreAllocator, HyperCoreRouter |
HyperEVM side receives assets, mints internal shares, allocates to HyperCore/HLP, and handles cross-chain redemptions. |
Roles
All custom roles are defined in the Roles library in src/libraries/Constants.sol. The system utilizes OpenZeppelin's AccessControl or AccessControlUpgradeable.
| Role | Constant | Scope | Responsibility |
|---|---|---|---|
| Admin | DEFAULT_ADMIN_ROLE |
Controller, Vault, Strategy, Allocator, Router | Authorizes other roles, upgrades UUPS contracts, sets manager, confirms NAV, manages role grants for vault/strategy. |
| Developer | Roles.DEVELOPER |
Controller, Allocator | Registers vaults, adds/removes strategies, adds HyperCore routers. |
| Keeper | Roles.KEEPER |
Controller, Async Vault, Strategy, Allocator, Router | Executes operations: invest, divest, NAV propose, fee rate adjustments, CCTP relay, async executeRedeem, and HyperCore allocations. |
| Guardian | Roles.GUARDIAN |
Vault, Controller | Emergency operations: pause, unpause, permanent shutdown, and forced exit of all strategies. |
| Investor | Roles.INVESTOR |
Vault, Async Vault | User operations: deposit, mint, withdraw, redeem, claim, emergencyRedeem. |
Role Assignment Notes
| Contract | Initial Roles |
|---|---|
Controller.initialize(owner, manager) |
owner receives DEFAULT_ADMIN_ROLE; manager is only the fee receiver, not automatically a role holder. |
KingsVaultV2 constructor |
owner_ receives DEFAULT_ADMIN_ROLE and Roles.GUARDIAN; vault starts paused. |
KingsVaultV2Async constructor |
Same as KingsVaultV2. Keeper role must be granted on the async vault for executeRedeem(). |
AaveV3Strategy, ERC4626Strategy, MorphoStrategy, HyperStrategy, HyperCoreRouter constructors |
owner_ receives DEFAULT_ADMIN_ROLE. |
HyperCoreAllocator.initialize(...) |
owner_ receives DEFAULT_ADMIN_ROLE. |
Important implementation detail: KingsVaultV2 does not use a Roles.CONTROLLER constant. Controller-only functions check msg.sender == CONTROLLER through the immutable CONTROLLER address.
For strategy execution through Controller.fundInvest(), Controller.fundDivest(), Controller.fundReplenishOnlyForVault(), and Controller.strategyRevoke(), the strategy sees msg.sender as the Controller contract. Therefore, the Controller proxy must be granted Roles.KEEPER on each concrete strategy that enforces onlyRole(Roles.KEEPER).
Architecture
flowchart TD
Investor["Investor"] --> Vault["KingsVaultV2 / KingsVaultV2Async"]
Admin["Admin"] --> Controller["Controller"]
Developer["Developer"] --> Controller
Keeper["Keeper"] --> Controller
Guardian["Guardian"] --> Vault
Guardian --> Controller
Controller --> Vault
Controller --> Strategies["Strategies"]
Strategies --> Aave["Aave V3"]
Strategies --> ERC4626["External ERC4626 Vault"]
Strategies --> HyperStrategy["HyperStrategy"]
HyperStrategy --> CCTP["CCTP V2"]
CCTP --> Allocator["HyperCoreAllocator"]
Allocator --> Router["HyperCoreRouter"]
Router --> HyperCore["HyperCore / HLP"]
Asset Accounting
KingsVaultV2.totalAssets() returns:
idleAssets() + Controller.totalStrategyAssets(address(this))
idleAssets() means tokens physically held by the vault contract. Strategy assets are counted through each registered strategy's totalAssets().
Dual Pricing
Vault share conversion compares the live ERC-4626 spot value with the confirmed Controller mark-to-market values.
| Direction | Function Path | Code Behavior |
|---|---|---|
| Assets to shares | _convertToShares(assets, rounding) |
Computes both spot shares and marked shares. If rounding is Ceil, returns the larger value. Otherwise returns the smaller value. |
| Shares to assets | _convertToAssets(shares, rounding) |
Computes both spot assets and marked assets. If rounding is Ceil, returns the larger value. Otherwise returns the smaller value. |
Because OpenZeppelin ERC-4626 uses different rounding modes for deposit, mint, withdraw, and redeem, the effective result depends on the user action:
| User Action | ERC-4626 Preview | Effect |
|---|---|---|
deposit(assets) |
assets to shares, floor | mints the lower of spot/marked shares. |
mint(shares) |
shares to assets, ceil | requires the higher of spot/marked assets. |
withdraw(assets) |
assets to shares, ceil | burns the higher of spot/marked shares. |
redeem(shares) |
shares to assets, floor | pays the lower of spot/marked assets. |