Skip to content

FeeCollector

Stateless fee-assessment library for MultiStrategyVault. Pulls quotes from Accountant, applies TVL cap + rate-limit checks. Not deployed — logic inlined in MSV (from . import FeeCollector breaks titanoboa 0.2.8 deploy).

Source: khomdev-keep/src/FeeCollector.vy (Vyper 0.4.x)

Consumer: MSV _maybe_assess_fees (via inlined equivalent) on report

Implementation overview

  • is_rate_limited — at most one fee mint per MIN_FEE_INTERVAL (1 hour)
  • compute_fee — calls Accountant.assess, hard-caps total at MAX_FEE_PER_ASSESS_BPS (20% TVL), returns recipient from fee_recipient
  • Defence-in-depth — bounds rogue/misconfigured accountant: ~20%/hour worst case vs ~20%/block
flowchart LR
    MSV[MSV report] --> FC[FeeCollector logic]
    FC --> ACC[Accountant.assess]
    FC -->|mgmt + perf capped| MINT[mint fee shares]

Constants

Name Value Role
MAX_FEE_PER_ASSESS_BPS 2000 (20%) Per-call TVL cap
MIN_FEE_INTERVAL 3600 s Min time between assessments

Internal functions

is_rate_limited

FeeCollector.is_rate_limited(last_fee_assess) -> bool

Whether a fee assessment would be blocked by the hourly rate limit.

Param Type Description
last_fee_assess uint256 Vault's last_fee_assess timestamp

Returns: boolTrue if block.timestamp < last_fee_assess + MIN_FEE_INTERVAL. First assessment (last_fee_assess == 0) always allowed.

Access: @internal @view — inlined in MSV _maybe_assess_fees.

Source (khomdev-keep/src/FeeCollector.vy:34-40):

@internal
@view
def is_rate_limited(last_fee_assess: uint256) -> bool:
    if last_fee_assess == 0:
        return False
    return block.timestamp < last_fee_assess + MIN_FEE_INTERVAL

Example:

# MSV checks before calling Accountant
if not FeeCollector.is_rate_limited(self.last_fee_assess):
    ...

Cross-links: compute_fee · Accountant assess

compute_fee

FeeCollector.compute_fee(accountant, total_assets, total_supply) -> (uint256, uint256, address)

Pull fee quote from Accountant, apply 20% TVL cap, return mint recipient.

Param Type Description
accountant address Accountant contract; 0x0 → no-op
total_assets uint256 Vault TVL at assessment
total_supply uint256 Vault share supply

Returns: (mgmt_fee, perf_fee, recipient) — underlying asset units. All zero + empty recipient when no fee due. Over-cap fees prorated between mgmt/perf.

Access: @internal — inlined in MSV fee path.

Source (khomdev-keep/src/FeeCollector.vy:44-83):

@internal
def compute_fee(accountant, total_assets, total_supply) -> (uint256, uint256, address):
    if accountant == empty(address) or total_assets == 0 or total_supply == 0:
        return (0, 0, empty(address))
    mgmt_fee, perf_fee = extcall IAccountant(accountant).assess(total_assets, total_supply)
    cap = total_assets * MAX_FEE_PER_ASSESS_BPS // MAX_BPS
    # prorate if over cap; fetch fee_recipient
    return (mgmt_fee, perf_fee, recipient)

Example:

mgmt, perf, to = FeeCollector.compute_fee(accountant, tvl, supply)

Cross-links: is_rate_limited · Accountant assess · MultiStrategyVault report