Skip to Content

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

FeeTypeWhen it firesWhat happens
Deposit feeFlow feeOn every deposit / mintA slice of the deposit tokens is transferred to feeTreasury before shares are minted
Redeem feeFlow feeOn every requestRedeemA slice of requested shares is transferred to feeTreasury; the remainder is burned
Management feeDilution feeAutomatically on every deposit / mint / requestRedeemNew shares proportional to time elapsed are minted directly to managementFeeTreasury
Performance feeDilution feeOn demand, called by PERF_FEE_CLAIM_ROLENew 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).
  • feeTokens are transferred directly from the strategy to feeTreasury during the deposit transaction.
  • If depositFeeBps > 0 and feeTreasury is not set, the deposit reverts with StrategyDeposit_FeeTreasuryNotSet.
  • Only netAssets are converted to shares via the IAssetConverter and 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_ROLE

Redeem 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).
  • feeShares are transferred from the owner to feeTreasury (not burned).
  • Only netShares are burned, and only netShares × pricePerShare worth of asset() is queued for redemption payout.
  • If feeShares >= requestedShares (pathological bps + rounding), reverts AsyncRedemption_FeeSharesGreaterThanShares.
  • A fee of 0 bps or 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 days

How 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 / mint call runs _accrueManagementFee() before minting user shares.
  • Every requestRedeem call runs _accrueManagementFee() before burning shares.

When _accrueManagementFee() runs:

  1. Computes shares from totalSupply, managementFeeBps, and block.timestamp - lastFeeHarvestAt.
  2. Updates lastFeeHarvestAt to block.timestamp.
  3. If shares > 0, mints them directly to managementFeeTreasury. Emits ManagementFeeHarvested(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 == 0 at current PPS/HWM: no-op.
  • Otherwise: mints perfFeeShares to feeRecipient, raises HWM to current pricePerShare. Emits PerformanceFeeHarvested(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 > HWM and forfeitAccrued = false: accrued performance fee shares at the old rate are minted to feeRecipient.
  • 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 performanceFeeBps resets the HWM. If pricePerShare has risen significantly above the old HWM, set forfeitAccrued = false and provide a valid feeRecipient to avoid forfeiting the accrued fee.

Views

(uint16 feeBps, uint256 previewShares, uint256 hwm) = strategy.performanceFee();

Treasury configuration

There are two treasury addresses in the system:

AddressPurposeSet by
feeTreasuryReceives deposit fee tokens and redeem fee sharesMANAGER_ROLE via setFeeTreasury
managementFeeTreasuryReceives 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_ROLE

Both must be non-zero. If feeTreasury is not set and a deposit fee or redeem fee is non-zero, the relevant transaction will revert.


Fee interaction overview

Last updated on