Strategy: Fee System
The strategy has four distinct fee surfaces: two flow fees that divert tokens or shares at transaction time, and two dilution fees that mint new shares to a treasury over time.
Summary
| Fee | Type | When it fires | What happens |
|---|---|---|---|
| Deposit fee | Flow fee | On every deposit / mint | A slice of the deposit tokens is transferred to feeTreasury before shares are minted |
| Redeem fee | Flow fee | On every requestRedeem | A slice of requested shares is transferred to feeTreasury; the remainder is burned |
| Management fee | Dilution fee | Automatically on every deposit / mint / requestRedeem | New shares proportional to time elapsed are minted directly to managementFeeTreasury |
| Performance fee | Dilution fee | On demand, called by PERF_FEE_CLAIM_ROLE | New shares are minted to a specified recipient when pricePerShare exceeds the high-water mark |
Deposit fee
The deposit fee is charged on the gross deposit amount and paid in the deposit token (not in shares).
feeTokens = grossAssets × depositFeeBps / 10_000 (ceiling)
netAssets = grossAssets - feeTokens- Configured per deposit asset via
setDepositFeeBps(token, bps)(MANAGER_ROLE;bps < 10_000). feeTokensare transferred directly from the strategy tofeeTreasuryduring the deposit transaction.- If
depositFeeBps > 0andfeeTreasuryis not set, the deposit reverts withStrategyDeposit_FeeTreasuryNotSet. - Only
netAssetsare converted to shares via theIAssetConverterand used to determine the share mint. - Emits
DepositFeeTransferred(token, treasury, feeTokens).
Configuration
// Set or update fee for a listed deposit asset (MANAGER_ROLE)
strategy.setDepositFeeBps(tokenAddress, 50); // 50 bps = 0.5%
// Set the treasury to receive deposit fee tokens
strategy.setFeeTreasury(treasuryAddress); // MANAGER_ROLERedeem fee
The redeem fee is charged on the requested shares and paid in shares to feeTreasury.
feeShares = requestedShares × redeemFeeBps / 10_000 (ceiling)
netShares = requestedShares - feeShares- Configured via
setRedeemFeeBps(bps)(DEFAULT_ADMIN_ROLE;bps < 10_000). feeSharesare transferred from the owner tofeeTreasury(not burned).- Only
netSharesare burned, and onlynetShares × pricePerShareworth ofasset()is queued for redemption payout. - If
feeShares >= requestedShares(pathological bps + rounding), revertsAsyncRedemption_FeeSharesGreaterThanShares. - A fee of
0 bpsor an unset treasury means no fee is taken.netShares == requestedShares. - Emits
RedeemFeeCharged(treasury, feeShares).
Configuration
// Set redeem fee globally (DEFAULT_ADMIN_ROLE)
strategy.setRedeemFeeBps(30); // 30 bps = 0.3%Management fee
The management fee is an annualised percentage of total share supply, accrued continuously based on elapsed time.
mgmtFeeShares = totalSupply × managementFeeBps × elapsedSeconds
─────────────────────────────────────────────────
10_000 × 365 daysHow it accrues
Unlike performance fees, management fees do not require an explicit harvest transaction. They accrue automatically as a side-effect of user interactions:
- Every
deposit/mintcall runs_accrueManagementFee()before minting user shares. - Every
requestRedeemcall runs_accrueManagementFee()before burning shares.
When _accrueManagementFee() runs:
- Computes
sharesfromtotalSupply,managementFeeBps, andblock.timestamp - lastFeeHarvestAt. - Updates
lastFeeHarvestAttoblock.timestamp. - If
shares > 0, mints them directly tomanagementFeeTreasury. EmitsManagementFeeHarvested(treasury, shares, elapsed).
If the strategy is idle (no user interactions), management fee accrues silently in lastFeeHarvestAt state and is settled on the next user transaction.
Configuration
// Set annual management fee rate (DEFAULT_ADMIN_ROLE; bps < 10_000)
strategy.setManagementFeeBps(200); // 200 bps = 2% per year
// Set the treasury that receives management fee shares (MANAGER_ROLE)
strategy.setManagementFeeTreasury(treasuryAddress);Calling setManagementFeeBps resets the accrual clock: any accrued-but-un-settled fee at the old rate is lost (it was never minted because no interaction triggered the accrual). Coordinate off-chain before changing rates to ensure no surprise gaps.
Views
(uint16 feeBps, uint48 lastHarvestAt, address treasury) = strategy.managementFee();Performance fee
The performance fee is triggered when pricePerShare rises above the stored high-water mark (HWM). It is computed as a percentage of the incremental supply that would correspond to the NAV gain above the HWM.
gainShares = totalSupply × (pricePerShare - HWM) / HWM (floor)
perfFeeShares = gainShares × performanceFeeBps / 10_000 (floor)Unlike the management fee, the performance fee requires an explicit harvest call by an account with PERF_FEE_CLAIM_ROLE.
Harvesting
// Mint performance fee shares to a specified recipient (PERF_FEE_CLAIM_ROLE)
strategy.harvestPerformanceFee(feeRecipient);- If
pricePerShare <= HWM: no-op (returns silently, not a revert). - If
perfFeeShares == 0at current PPS/HWM: no-op. - Otherwise: mints
perfFeeSharestofeeRecipient, raises HWM to currentpricePerShare. EmitsPerformanceFeeHarvested(recipient, shares, oldHWM, newHWM).
The feeRecipient parameter is supplied by the caller and does not have to equal feeTreasury.
Updating the performance fee rate
// DEFAULT_ADMIN_ROLE; bps < 10_000
strategy.setPerformanceFeeBps(bps, feeRecipient, forfeitAccrued);Before the rate is updated:
- If
pricePerShare > HWMandforfeitAccrued = false: accrued performance fee shares at the old rate are minted tofeeRecipient. - If
forfeitAccrued = true: the HWM is reset without minting any accrued shares.
After the update, the HWM is reset to the current pricePerShare. The first harvest at the new rate starts from this baseline.
Coordination note: Updating
performanceFeeBpsresets the HWM. IfpricePerSharehas risen significantly above the old HWM, setforfeitAccrued = falseand provide a validfeeRecipientto avoid forfeiting the accrued fee.
Views
(uint16 feeBps, uint256 previewShares, uint256 hwm) = strategy.performanceFee();Treasury configuration
There are two treasury addresses in the system:
| Address | Purpose | Set by |
|---|---|---|
feeTreasury | Receives deposit fee tokens and redeem fee shares | MANAGER_ROLE via setFeeTreasury |
managementFeeTreasury | Receives management fee shares (minted automatically) | MANAGER_ROLE via setManagementFeeTreasury |
Performance fee harvests accept an explicit feeRecipient argument at call time rather than reading from a stored address.
strategy.setFeeTreasury(address); // MANAGER_ROLE
strategy.setManagementFeeTreasury(address); // MANAGER_ROLEBoth must be non-zero. If feeTreasury is not set and a deposit fee or redeem fee is non-zero, the relevant transaction will revert.