# Fee management \[How fees are configured, collected, and distributed in Railnet]

The FeeManager is an optional contract attached to a Vehicle, a MultiVehicle or a Conduit at initialization. The address cannot be changed afterwards, and neither can the `maxFees` ceiling set at its own deployment, so choose both with room to grow. One FeeManager can serve several contracts.

Which layer you attach it to decides whose revenue it is. On a Vehicle or MultiVehicle it pays whoever operates the position. On a Conduit it is the distributor's margin, applied on top of the wrapped Vehicle, which needs no FeeManager of its own. [Configure fees](/conduits/configure-fees) covers choosing rates as a distributor.

## The four fees

```solidity [Solidity]
struct Fees {
    uint32 performanceFeeBps;  // on gains above the high water mark
    uint32 managementFeeBps;   // annualized, prorated by time elapsed
    uint32 depositFeeBps;      // deducted from shares received
    uint32 redeemFeeBps;       // deducted from assets received
}

struct FeeRecipient {
    address target;
    uint32 shareBps;
}
```

| Fee | Calculated in | Applied as |
|-----|---------------|------------|
| Performance | `onOperations`, during `create` | Shares minted to the FeeManager |
| Management | `onOperations`, during `create` | Shares minted to the FeeManager |
| Deposit | `applyFees`, during `unlock` | Fewer shares paid to the user |
| Redeem | `applyFees`, during `unlock` | Fewer assets paid to the user |

`setFees` reverts `FeeTooHigh` above `maxFees()`. Performance and management ceilings can reach 10,000 bps; deposit and redeem ceilings cap at 9,999, because a 100% transactional fee is not reversible by `reverseFees`.

## Formulas

```text
// Management: annualized, prorated since the last onUpdate
managementFeeAssets = currentTotalAssets * managementFeeBps * timeDelta / (10000 * 31536000)

// Performance: on gains above the high water mark (lastTotalAssets)
earnings             = max(0, currentTotalAssets - lastTotalAssets)
performanceFeeAssets = earnings * performanceFeeBps / 10000

// Transactional: deducted from the gross amount, rounded down
feeAmount = amount * feeBps / 10000
netAmount = amount - feeAmount

// Reverse: the gross needed for a target net, rounded up
grossAmount = netAmount * 10000 / (10000 - feeBps)
feeAmount   = grossAmount - netAmount
```

`timeDelta` is seconds since the last `onUpdate`; `31536000` is a 365-day year. The high water mark refreshes on `onUpdate` at the end of an operation, so a product that loses value collects no performance fee until the share price passes its previous peak.

:::warning
Management fees accrue against `block.timestamp`, so the first operation after a long idle period mints a large batch of shares at once.
:::

## Config IDs pin in-flight queries

Every accepted `setFees` derives `configId = keccak256(abi.encode(fees))`, stores the struct under it, and makes it current. `fees()` returns `(configId, Fees)`; `fees(configId)` returns any past one. `bytes32(0)` resolves to the current config, and an unknown id reverts `NonExistingConfigId`.

* **Transactional fees.** The Vehicle pins the active `configId` at `create` and reads it back at `unlock`, so a rate change mid-flight never hits an in-flight query. Read it with `vehicle.feesConfigId(query)`, or `conduit.feesConfigId(query)` for a Conduit, which pins its own FeeManager's id.
* **Ongoing fees.** `onUpdate` snapshots the current `configId` into the Vehicle's `Cache` as `applicableConfigId`, alongside the high water mark and timestamp. A new rate only starts accruing after the next update.

## Recipients and dispatch

Recipients split everything the FeeManager holds. Targets must be sorted strictly ascending and `shareBps` must sum to exactly 10,000.

```solidity [Solidity]
FeeManager.FeeRecipient[] memory recipients = new FeeManager.FeeRecipient[](2);
recipients[0] = FeeManager.FeeRecipient({ target: operator, shareBps: 7000 });
recipients[1] = FeeManager.FeeRecipient({ target: daoTreasury, shareBps: 3000 });
feeManager.setFeeRecipients(recipients);   // FEE_MANAGER_SET_FEE_RECIPIENTS
```

Performance and management fees sit at the FeeManager as Vehicle or Conduit shares; transactional fees arrive denominated in the payout asset. The FeeManager never redeems anything itself. You dispatch each ERC20 balance and each recipient redeems on its own schedule:

```solidity [Solidity]
feeManager.dispatchERC20(IERC20(address(conduit)));  // accrued shares
feeManager.dispatchERC20(IERC20(baseAsset));         // transactional fees
// FEE_MANAGER_DISPATCH_ERC20; the last recipient absorbs the rounding remainder
```

`dispatchERC20` reverts `ZeroValue` when the FeeManager holds none of it.

:::warning
Changing recipients does not settle what is already accrued: the old recipients lose their claim to it. Dispatch everything before calling `setFeeRecipients`.
:::

## A distributor's margin

One Aave V3 USDC Vehicle with no FeeManager, one Conduit with a 50 bps management fee. That 50 bps is your whole margin, the only fee between the Aave rate and your users.

```solidity [Solidity]
// FeeManager on the Conduit only
Fees({ performanceFeeBps: 0, managementFeeBps: 50, depositFeeBps: 0, redeemFeeBps: 0 })

// Conduit holding 5,000,000 USDC of Aave V3 Vehicle shares, 30 days elapsed
// fee = 5_000_000 * 50 * 2_592_000 / (10000 * 31536000)
//     = ~2,055 USDC worth of cShares minted to the FeeManager
//     = ~25,000 USDC a year on 5M

// Users see 4.00% Aave supply rate - 0.50% margin = 3.50% net APR
```

The fee mints cShares, so your margin dilutes holders instead of touching the Aave position. Add a second recipient to split it with a partner, or move the rate to `performanceFeeBps` to charge only on yield produced.

## Views

```solidity [Solidity]
(bytes32 configId, FeeManager.Fees memory current) = feeManager.fees();
FeeManager.Fees memory ceiling = feeManager.maxFees();
FeeManager.FeeRecipient[] memory recipients = feeManager.feeRecipients();
FeeManager.Cache memory cache = feeManager.cache(address(vehicle));  // HWM, last update

uint256 feeShares = feeManager.previewOnOperations(totalSupply, totalAssets, sharesDecimals, assetDecimals);
(Asset memory net, Asset memory fee) = feeManager.previewApplyTransactionalFees(configId, inputAsset, Mode.DEPOSIT);
(Asset memory gross, Asset memory reverseFee) = feeManager.reverseFees(configId, targetNetAsset, Mode.DEPOSIT);
```
