Overview
Morpho Vault V2 supports two types of fees that are paid to designated recipients through share minting:
- Performance fee: Charged on vault profits (interest earned)
- Management fee: Charged continuously based on total assets
Fees are denominated in WAD (1e18), where 1e18 represents 100%. Both fees are paid by minting new shares to fee recipients rather than transferring assets.
Fee limits
The protocol enforces maximum fee levels:
MAX_PERFORMANCE_FEE = 0.5e18; // 50% maximum
MAX_MANAGEMENT_FEE = 0.05e18 / 365 days; // 5% per year maximum
Source: ConstantsLib.sol:10-11
The performance fee is taken on distributed interest (profits after applying the max rate).
function setPerformanceFee(uint256 newPerformanceFee) external;
Submit through timelock
The curator must submit the change through the timelock mechanism.
Wait for timelock
Wait for the timelock period to expire.
Execute change
Call setPerformanceFee() to apply the new fee. Interest is accrued first.
Example
// Set a 20% performance fee
vault.setPerformanceFee(0.2e18);
Requirements:
- Fee must not exceed
MAX_PERFORMANCE_FEE (50%)
- Fee recipient must be set if fee is non-zero
- Interest is accrued before applying the new fee
Source: VaultV2.sol:482-492
The performance fee is calculated on interest earned:
// Interest = newTotalAssets - _totalAssets (capped by maxRate)
uint256 interest = newTotalAssets.zeroFloorSub(_totalAssets);
// Performance fee assets
uint256 performanceFeeAssets = interest > 0 && performanceFee > 0
? interest.mulDivDown(performanceFee, WAD)
: 0;
// Convert to shares
uint256 performanceFeeShares = performanceFeeAssets.mulDivDown(
totalSupply + virtualShares,
newTotalAssetsWithoutFees + 1
);
Source: VaultV2.sol:673-688
Performance fee assets may round down to zero if interest * fee < WAD. This means very small interest amounts may not generate fee shares.
Management fee
The management fee is charged continuously on total assets, similar to a streaming fee.
Setting management fee
function setManagementFee(uint256 newManagementFee) external;
Example
// Set a 2% annual management fee
vault.setManagementFee(0.02e18 / 365 days);
Requirements:
- Fee must not exceed
MAX_MANAGEMENT_FEE (5% annually)
- Fee recipient must be set if fee is non-zero
- Interest is accrued before applying the new fee
Source: VaultV2.sol:494-504
Management fee calculation
The management fee accrues over time based on total assets:
// Time elapsed since last update
uint256 elapsed = block.timestamp - lastUpdate;
// Management fee assets
uint256 managementFeeAssets = elapsed > 0 && managementFee > 0
? (newTotalAssets * elapsed).mulDivDown(managementFee, WAD)
: 0;
// Convert to shares
uint256 managementFeeShares = managementFeeAssets.mulDivDown(
totalSupply + virtualShares,
newTotalAssetsWithoutFees + 1
);
Source: VaultV2.sol:681-690
The management fee is taken on newTotalAssets to make all approximations consistent. Interacting less with the vault results in higher accrued fees.
Fee recipients
Each fee has a designated recipient address that receives minted shares.
Setting fee recipients
function setPerformanceFeeRecipient(address newPerformanceFeeRecipient) external;
function setManagementFeeRecipient(address newManagementFeeRecipient) external;
Example
// Set recipients
vault.setPerformanceFeeRecipient(treasuryAddress);
vault.setManagementFeeRecipient(teamAddress);
Fee invariant: A fee cannot be non-zero if its recipient is the zero address. The contract enforces:require(
newPerformanceFeeRecipient != address(0) || performanceFee == 0,
ErrorsLib.FeeInvariantBroken()
);
Source: VaultV2.sol:506-524
Fee accrual
Fees are accrued and paid during the first interaction of each transaction:
function accrueInterest() public {
(uint256 newTotalAssets, uint256 performanceFeeShares, uint256 managementFeeShares) =
accrueInterestView();
_totalAssets = newTotalAssets.toUint128();
if (performanceFeeShares != 0) {
createShares(performanceFeeRecipient, performanceFeeShares);
}
if (managementFeeShares != 0) {
createShares(managementFeeRecipient, managementFeeShares);
}
lastUpdate = uint64(block.timestamp);
}
Source: VaultV2.sol:648-656
Accrual timing
Interest and fees are accrued only once per transaction, during the first vault interaction. Subsequent interactions in the same transaction use the already-updated values.
This prevents:
- Multiple fee charges in a single transaction
- Flashloan-based manipulation of fee calculations
Fee impact on share price
Performance fees are taken from distributed interest, so they don’t reduce the share price—they dilute profits:
// Without performance fee:
// 100 assets, 100 shares → 1.0 share price
// +10 interest → 110 assets, 100 shares → 1.1 share price (+10%)
// With 20% performance fee:
// 100 assets, 100 shares → 1.0 share price
// +10 interest, -2 fee → 110 assets, 101.8 shares → 1.08 share price (+8%)
Management fee
Management fees are taken from total assets and can reduce share price:
// Management fee accrues even if the vault has losses
// 100 assets, 100 shares → 1.0 share price
// Time passes, 0.5 assets management fee → 100 assets, 100.5 shares → 0.995 share price
The management fee is not bound to interest. It accrues even when the vault incurs losses, which can decrease the share price.
Source: VaultV2.sol:659-660
Fee rounding
Both fees are rounded down:
// Performance fee uses mulDivDown
performanceFeeAssets = interest.mulDivDown(performanceFee, WAD);
// Management fee uses mulDivDown
managementFeeAssets = (newTotalAssets * elapsed).mulDivDown(managementFee, WAD);
Fee recipients may receive slightly less than the theoretical fee amount due to rounding down. This protects users from overpayment.
Source: VaultV2.sol:661
Viewing accrued fees
To preview fees without state changes:
(
uint256 newTotalAssets,
uint256 performanceFeeShares,
uint256 managementFeeShares
) = vault.accrueInterestView();
This returns:
newTotalAssets: Total assets after accruing interest
performanceFeeShares: Shares to mint for performance fee
managementFeeShares: Shares to mint for management fee
Source: VaultV2.sol:664-693
Fee recipients must be able to receive shares:
function accrueInterestView() public view returns (uint256, uint256, uint256) {
// ...
uint256 performanceFeeAssets = interest > 0
&& performanceFee > 0
&& canReceiveShares(performanceFeeRecipient)
? interest.mulDivDown(performanceFee, WAD)
: 0;
// ...
}
If a fee recipient cannot receive shares (blocked by receiveSharesGate), the fee is not charged.
Source: VaultV2.sol:676-682
Timelocks and governance
All fee-related changes go through curator timelocks:
Submit proposal
Curator calls submit() with encoded function call.
Timelock period
Wait for the timelock duration specific to the function.
Execute
Call the actual function (e.g., setPerformanceFee()) after timelock expires.
This ensures users have notice before fee changes take effect.
Best practices
- Set reasonable fees that align with vault strategy and performance
- Always set fee recipients before setting non-zero fees
- Accrue interest at least every 10 years to prevent fee calculation overflows
- Monitor that fee recipients can receive shares (not blocked by gates)
- Consider the impact of management fees during loss periods
Source: VaultV2.sol:685