Accountant¶
Pluggable fee module for MultiStrategyVault. Time-prorated management fee on AUM + high-water-mark performance fee on per-share appreciation. Multi-vault safe — HWM state keyed by calling vault (msg.sender).
Source: khomdev-keep/src/Accountant.vy (Vyper 0.4.x)
Used by: MSV via set_accountant → report → _maybe_assess_fees
Implementation overview¶
- Mgmt fee —
total_assets * mgmt_fee_bps * dt / year / MAX_BPS - Perf fee — on strict PPS increase vs per-vault HWM:
(pps - hwm) * supply * perf_fee_bps / 1e18 / MAX_BPS - First call — per vault: fees = 0; establishes HWM +
last_assessbaseline only - Caps — mgmt ≤ 200 bps (2%/yr); perf ≤ 2000 bps (20% of gain)
- Trust —
assess()permissionless; state isolated bymsg.sendervault
flowchart LR
MSV[MultiStrategyVault] -->|assess| ACC[Accountant]
ACC -->|mgmt + perf assets| MSV
MSV -->|mint fee shares| FR[fee_recipient]
Constants¶
| Name | Value |
|---|---|
MAX_MGMT_FEE_BPS |
200 (2%/year) |
MAX_PERF_FEE_BPS |
2000 (20% of gain) |
PPS_SCALE |
1e18 |
Storage¶
| Name | Role |
|---|---|
mgmt_fee_bps / perf_fee_bps |
Global fee rates |
fee_recipient |
Receives fee shares (vault mints) |
hwm_pps_18[vault] |
Per-vault high-water PPS |
last_assess[vault] |
Per-vault last assessment timestamp |
Access control¶
snekmate access_control exports (DEFAULT_ADMIN_ROLE, grantRole, etc.). Admin sets rates and recipient.
Events¶
| Event | When |
|---|---|
FeeRatesUpdated |
Rates changed |
FeeRecipientUpdated |
Recipient rotated |
Assessed |
Vault called assess |
Admin¶
set_fee_rates¶
Accountant.set_fee_rates(mgmt_bps_, perf_bps_)
Update global management and performance fee rates.
| Param | Type | Description |
|---|---|---|
mgmt_bps_ |
uint256 |
Annual mgmt fee bps (≤ MAX_MGMT_FEE_BPS) |
perf_bps_ |
uint256 |
Performance fee bps on gain (≤ MAX_PERF_FEE_BPS) |
Returns / state: Sets mgmt_fee_bps, perf_fee_bps.
Access: DEFAULT_ADMIN_ROLE only.
Events: FeeRatesUpdated(mgmt_fee_bps, perf_fee_bps).
Source (khomdev-keep/src/Accountant.vy:110-119):
@external
def set_fee_rates(mgmt_bps_: uint256, perf_bps_: uint256):
access_control._check_role(access_control.DEFAULT_ADMIN_ROLE, msg.sender)
assert mgmt_bps_ <= MAX_MGMT_FEE_BPS, "accountant: mgmt cap"
assert perf_bps_ <= MAX_PERF_FEE_BPS, "accountant: perf cap"
self.mgmt_fee_bps = mgmt_bps_
self.perf_fee_bps = perf_bps_
log FeeRatesUpdated(mgmt_fee_bps=mgmt_bps_, perf_fee_bps=perf_bps_)
Example:
Cross-links: set_fee_recipient · assess
set_fee_recipient¶
Accountant.set_fee_recipient(recipient_)
Set address that receives fee shares (vault mints on assessment).
| Param | Type | Description |
|---|---|---|
recipient_ |
address |
Non-zero fee recipient |
Returns / state: Sets fee_recipient.
Access: DEFAULT_ADMIN_ROLE only.
Events: FeeRecipientUpdated(recipient).
Source (khomdev-keep/src/Accountant.vy:122-129):
@external
def set_fee_recipient(recipient_: address):
access_control._check_role(access_control.DEFAULT_ADMIN_ROLE, msg.sender)
assert recipient_ != empty(address), "accountant: zero recipient"
self.fee_recipient = recipient_
log FeeRecipientUpdated(recipient=recipient_)
Example:
Cross-links: set_fee_rates · assess · MultiStrategyVault report
Vault hook¶
assess¶
Accountant.assess(total_assets, total_supply) -> (uint256, uint256)
Compute fees since last call; update per-vault HWM and last_assess. Called by vault (typically MSV _maybe_assess_fees).
| Param | Type | Description |
|---|---|---|
total_assets |
uint256 |
Vault TVL at assessment |
total_supply |
uint256 |
Vault share supply |
Returns: (mgmt_fee_assets, perf_fee_assets) — underlying units. Vault mints equivalent shares to fee_recipient.
Returns / state: Updates last_assess[msg.sender], hwm_pps_18[msg.sender] when PPS rises. First call per vault: (0, 0) + HWM baseline only.
Access: Permissionless. State keyed by msg.sender (calling vault) — cannot corrupt other vaults.
Events: Assessed(vault, mgmt_fee_assets, perf_fee_assets, new_hwm_pps_18).
Source (khomdev-keep/src/Accountant.vy:136-189):
@external
def assess(total_assets, total_supply) -> (uint256, uint256):
if total_supply == 0 or total_assets == 0 or fee_recipient == empty(address):
if last_assess[msg.sender] == 0:
last_assess[msg.sender] = block.timestamp
return 0, 0
# mgmt: total_assets * mgmt_fee_bps * dt / year / MAX_BPS
pps = total_assets * PPS_SCALE // total_supply
# perf on pps > hwm; first call sets hwm, no fee
return mgmt_fee, perf_fee
Note: Perf fee uses marked PPS (strategy marks). MSV also rate-limits (1/hr) and caps (20% TVL) via inlined FeeCollector logic. Prefer report() after harvest() when CoilMaker is in the stack.
Example:
Cross-links: set_fee_rates · set_fee_recipient · MultiStrategyVault report · FeeCollector compute_fee