MultistrategyVaultFactory
Inherits: IMultistrategyVaultFactory
Title: MultistrategyVault Factory
Author: yearn.finance; adapted by Golem Foundation
Factory for deploying MultistrategyVault instances with protocol fee management
Deploys minimal proxy clones of VAULT_ORIGINAL using CREATE2 for deterministic addresses
DEPLOYMENT MECHANISM:
- Uses OpenZeppelin Clones library for minimal proxy (EIP-1167)
- CREATE2 deployment for deterministic addresses
- Salt = keccak256(deployer, asset, name, symbol)
- Same deployer cannot deploy identical vault twice
- Each vault is a lightweight proxy delegating to VAULT_ORIGINAL
PROTOCOL FEE SYSTEM:
Revenue Share Model:
- Vaults charge total fees during processReport()
- Protocol takes percentage of total fees
- Formula: protocolFees = totalFees * protocolFeeBps / MAX_BPS
- Remainder goes to vault-specific accountant
Example:
- Vault charges 100 assets in fees
- Protocol fee = 10% (1000 bps)
- Protocol receives: 10 assets
- Accountant receives: 90 assets
FEE PACKING OPTIMIZATION:
Protocol fee data packed into single uint256 storage slot:
Bit Layout (256 bits total):
| Bits | Content | Size | Max Value |
|---|---|---|---|
| 0-7 | Custom flag | 8 bits | 0 or 1 |
| 8-23 | Fee in basis points | 16 bits | 65535 |
| 24-183 | Fee recipient addr | 160 bits | address |
| 184-255 | Unused | 72 bits | - |
Benefits:
- Single SLOAD for fee queries (saves ~2100 gas)
- Efficient storage (fits in one slot)
TWO-TIER FEE SYSTEM:
- Default fee: Applied to all vaults unless custom set
- Custom fee: Per-vault override (recipient always uses default)
Notes:
-
security-contact: [email protected]
-
origin: https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultFactory.vy
-
security: Governance controls all fee parameters
-
security: Maximum fee capped at 50% (MAX_FEE_BPS = 5000)
State Variables
API_VERSION
API version of vaults deployed by this factory
Must match VAULT_ORIGINAL's API version
string public constant override API_VERSION = "3.0.4"
MAX_FEE_BPS
Maximum protocol fee in basis points (50%)
Hard cap to prevent excessive fees. 5000 bps = 50%
uint16 public constant override MAX_FEE_BPS = 5_000
FEE_BPS_MASK
Bitmask for extracting fee basis points from packed data
16-bit mask: 0xFFFF (bits 8-23 in packed data)
uint256 public constant override FEE_BPS_MASK = 2 ** 16 - 1
VAULT_ORIGINAL
Address of the canonical vault implementation
All deployed vaults are minimal proxies pointing to this implementation Set once during construction, never changes
address public immutable override VAULT_ORIGINAL
shutdown
Whether factory has been permanently shutdown
When true, no new vaults can be deployed. Cannot be reversed
bool public override shutdown
governance
Address with governance authority over factory
Can set fees, shutdown factory, transfer governance
address public override governance
pendingGovernance
Address pending to become new governance
Two-step transfer process for safety
address public override pendingGovernance
name
Human-readable name for this factory instance
Useful for identifying different factory versions
string public override name
defaultProtocolFeeData
Default protocol fee configuration packed into single slot
Bit layout: [72 empty][160 recipient][16 fee bps][8 custom flag] Applied to all vaults unless custom fee is set Packing saves gas by requiring only one SLOAD
uint256 private defaultProtocolFeeData
customProtocolFeeData
Per-vault custom protocol fee overrides
Maps vault address → custom fee data (same bit layout as default) Custom flag (bit 0) set to 1 indicates custom fee active Recipient always uses default (custom only overrides fee bps)
mapping(address => uint256) private customProtocolFeeData
Functions
constructor
Initializes the factory with implementation and governance
Sets immutable values that cannot be changed after deployment
constructor(string memory _name, address _vaultOriginal, address _governance) ;
Parameters
| Name | Type | Description |
|---|---|---|
_name | string | Human-readable name for this factory (e.g., "Octant V3 Factory") |
_vaultOriginal | address | Address of MultistrategyVault implementation to clone |
_governance | address | Address with authority to manage factory settings |
deployNewVault
Deploys a new MultistrategyVault as a minimal proxy
Uses CREATE2 for deterministic addresses based on unique parameters DEPLOYMENT PROCESS:
- Validates factory not shutdown
- Generates unique salt from: deployer + asset + name + symbol
- Deploys minimal proxy clone via CREATE2
- Initializes the new vault with provided parameters
- Emits NewVault event DETERMINISTIC ADDRESSES:
- Same parameters always produce same address
- Different name/symbol required for same deployer+asset
- Prevents duplicate deployments
- Address predictable before deployment GAS COSTS:
- Minimal proxy: ~50k gas (vs ~3M for full deployment)
- All logic delegated to VAULT_ORIGINAL
- Storage lives in proxy contract
Note: security: Reverts if factory is shutdown
function deployNewVault(
address asset,
string memory _name,
string memory symbol,
address roleManager,
uint256 profitMaxUnlockTime
) external returns (address);
Parameters
| Name | Type | Description |
|---|---|---|
asset | address | Address of underlying ERC20 asset token |
_name | string | Vault token name (must be unique for this deployer+asset combo) |
symbol | string | Vault token symbol (must be unique for this deployer+asset combo) |
roleManager | address | Address to manage vault roles |
profitMaxUnlockTime | uint256 | Profit unlock duration in seconds (0-31556952) |
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | vaultAddress Address of the newly deployed vault |
vaultOriginal
Returns the vault implementation address used for cloning
function vaultOriginal() external view returns (address);
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | implementation Vault implementation contract address |
apiVersion
Returns the API version string
function apiVersion() external pure override returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | version API version identifier |
protocolFeeConfig
Returns protocol fee configuration for a vault
Checks for vault-specific custom fee, falls back to default Called by vaults during processReport() to calculate protocol fees LOGIC:
- If vault has custom fee (custom flag set): Return (custom fee bps, default recipient)
- Otherwise: Return (default fee bps, default recipient) NOTE: Recipient ALWAYS comes from default config (custom only overrides bps)
function protocolFeeConfig(address vault) external view override returns (uint16, address);
Parameters
| Name | Type | Description |
|---|---|---|
vault | address | Address of the vault to query (or address(0) to use msg.sender) |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint16 | feeBps Protocol fee in basis points (0-5000, where 5000 = 50%) |
<none> | address | recipient Address to receive protocol fees |
useCustomProtocolFee
Returns whether vault uses custom protocol fee
function useCustomProtocolFee(address vault) external view override returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
vault | address | Vault address to query |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | usesCustomFee True if vault has custom fee configured |
setProtocolFeeBps
Sets the default protocol fee basis points
Updates default fee applied to all vaults without custom fees Requires fee recipient to be set first
Notes:
-
security: Only callable by governance
-
security: Capped at MAX_FEE_BPS (50%)
function setProtocolFeeBps(uint16 newProtocolFeeBps) external override;
Parameters
| Name | Type | Description |
|---|---|---|
newProtocolFeeBps | uint16 | New fee in basis points (0-5000, where 5000 = 50%) |
setProtocolFeeRecipient
Sets the default protocol fee recipient address
Updates recipient for all fees (default and custom) Custom fees only override bps, recipient always comes from default
Note: security: Only callable by governance
function setProtocolFeeRecipient(address newProtocolFeeRecipient) external override;
Parameters
| Name | Type | Description |
|---|---|---|
newProtocolFeeRecipient | address | Address to receive protocol fees (cannot be zero) |
setCustomProtocolFeeBps
Sets a custom protocol fee for a specific vault
Allows per-vault fee overrides while using default recipient Requires default recipient to be set first
Notes:
-
security: Only callable by governance
-
security: Capped at MAX_FEE_BPS (50%)
function setCustomProtocolFeeBps(address vault, uint16 newCustomProtocolFee) external override;
Parameters
| Name | Type | Description |
|---|---|---|
vault | address | Address of the vault to set custom fee for |
newCustomProtocolFee | uint16 | Custom fee in basis points (0-5000) |
removeCustomProtocolFee
Removes custom protocol fee for a vault (reverts to default)
Clears custom fee data, vault will use default fee configuration
Note: security: Only callable by governance
function removeCustomProtocolFee(address vault) external override;
Parameters
| Name | Type | Description |
|---|---|---|
vault | address | Address of the vault to clear custom fee for |
shutdownFactory
Permanently shuts down the factory
IRREVERSIBLE: Prevents new vault deployments forever Existing vaults continue operating normally
Notes:
-
security: Only callable by governance
-
security: Cannot be reversed
function shutdownFactory() external override;
transferGovernance
Initiates governance transfer (step 1 of 2)
Two-step process prevents accidental transfer to wrong address
Note: security: Only callable by current governance
function transferGovernance(address newGovernance) external override;
Parameters
| Name | Type | Description |
|---|---|---|
newGovernance | address | Address to become new governance |
acceptGovernance
Completes governance transfer (step 2 of 2)
Caller must be the pendingGovernance address
Note: security: Only callable by pendingGovernance
function acceptGovernance() external override;
_createClone
Creates a minimal proxy clone using CREATE2
function _createClone(address target, bytes32 salt) internal returns (address);
Parameters
| Name | Type | Description |
|---|---|---|
target | address | Implementation address to clone |
salt | bytes32 | Unique salt for deterministic address |
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | clone Address of the deployed clone |
_unpackProtocolFee
Extracts fee basis points from packed data
function _unpackProtocolFee(uint256 configData) internal pure returns (uint16);
Parameters
| Name | Type | Description |
|---|---|---|
configData | uint256 | Packed fee configuration |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint16 | fee Fee in basis points (bits 8-23) |
_unpackFeeRecipient
Extracts fee recipient address from packed data
function _unpackFeeRecipient(uint256 configData) internal pure returns (address);
Parameters
| Name | Type | Description |
|---|---|---|
configData | uint256 | Packed fee configuration |
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | recipient Fee recipient address (bits 24-183) |
_unpackCustomFlag
Extracts custom flag from packed data
function _unpackCustomFlag(uint256 configData) internal pure returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
configData | uint256 | Packed fee configuration |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | isCustom True if custom fee is set (bit 0) |
_packProtocolFeeData
Packs fee data into single uint256
Bit packing layout: [72 empty bits][160 recipient][16 fee bps][8 custom flag]
function _packProtocolFeeData(address recipient, uint16 fee, bool custom) internal pure returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Fee recipient address (160 bits) |
fee | uint16 | Fee in basis points (16 bits, 0-65535) |
custom | bool | Custom flag (8 bits, 0 or 1) |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | packed Single uint256 with all data packed |