MultiStrategyVault¶
ERC-4626 USDC yield vault routing capital across up to 10 child strategies. Yearn V3–style with smart APR routing and optional Accountant fees.
Source: khomdev-keep/src/MultiStrategyVault.vy (Vyper 0.4.x)
Concept guides: Keep overview · MultiStrategyVault
Receives: CoilFeeRouter keeper slice · SurplusSplitter emission
Implementation overview¶
- ERC-4626 — custom
_total_assets()=total_idle + total_debt(notbalanceOf— anti-donation). - Strategy router — up to 10 strategies;
allocateCapital/rebalancedeploy idle USDC. - FLYWHEEL 2.0 —
coil_maker_strategygets priority allocation (CoilMakerStrategy). - Roles —
STRATEGY_MANAGER_ROLE,ALLOCATOR_ROLE(gauge only),EMERGENCY_ROLE. - Pausable deposits —
pauseblocks new deposits; withdrawals always allowed.
flowchart LR
CFR[CoilFeeRouter] -->|USDC| MSV[MultiStrategyVault]
SS[SurplusSplitter] -->|emission| MSV
GWR[GaugeWeightRouter] -->|set_allocations| MSV
MSV --> CMS[CoilMakerStrategy]
MSV --> FB[Fallback strategies]
Immutables¶
| Name | Role |
|---|---|
asset / _ASSET |
Underlying USDC (ERC-20) |
Constants¶
| Name | Value | Role |
|---|---|---|
MAX_STRATEGIES |
10 | Strategy cap |
DECIMALS_OFFSET |
6 | Inflation-attack virtual shares |
MIN_DEPOSIT |
1_000 | 0.001 USDC floor |
MAX_BUFFER_BPS |
5_000 | Max idle buffer (50% TVL) |
Events¶
| Event | When |
|---|---|
Deposit / Withdraw |
ERC-4626 flows |
StrategyAdded / StrategyRemoved / DebtUpdated |
Strategy registry |
StrategyReported / FeesAssessed |
P&L + accountant |
AllocationsUpdated / CapitalAllocated |
Routing |
BufferUpdated / SmartRoutingToggled / AccountantUpdated |
Config |
ERC-4626 views¶
totalAssets¶
MultiStrategyVault.totalAssets() -> uint256
Total USDC under management — idle in vault plus deployed strategy debt.
Returns: uint256 — total_idle + total_debt. Not balanceOf(vault) — direct token transfers do not move share price.
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:352-360, _total_assets:791-792):
@external
@view
def totalAssets() -> uint256:
return self._total_assets()
@internal
@view
def _total_assets() -> uint256:
return self.total_idle + self.total_debt
Example:
Cross-links: convertToShares · deposit · report
convertToShares¶
MultiStrategyVault.convertToShares(assets) -> uint256
ERC-4626 quote: underlying USDC → vault shares (floor rounding).
| Param | Type | Description |
|---|---|---|
assets |
uint256 |
USDC amount (6 decimals) |
Returns: uint256 — share amount. Uses virtual shares (DECIMALS_OFFSET = 6) for inflation resistance.
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:362-368, _convert_to_shares:796-802):
@external
@view
def convertToShares(assets: uint256) -> uint256:
return self._convert_to_shares(assets, False)
# shares = assets * (totalSupply + 10**6) / (totalAssets + 1)
Example:
Cross-links: convertToAssets · totalAssets · previewDeposit
convertToAssets¶
MultiStrategyVault.convertToAssets(shares) -> uint256
ERC-4626 quote: vault shares → underlying USDC (floor rounding).
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Vault share amount |
Returns: uint256 — USDC value of shares at current totalAssets / totalSupply.
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:370-376, _convert_to_assets:806-812):
@external
@view
def convertToAssets(shares: uint256) -> uint256:
return self._convert_to_assets(shares, False)
# assets = shares * (totalAssets + 1) / (totalSupply + 10**6)
Example:
Cross-links: convertToShares · totalAssets · previewRedeem
maxDeposit¶
MultiStrategyVault.maxDeposit(receiver) -> uint256
Maximum USDC depositable right now without reverting.
| Param | Type | Description |
|---|---|---|
receiver |
address |
Share recipient (unused in limit math; ERC-4626 signature) |
Returns: uint256 — 0 if paused or at deposit_limit; else limit - totalAssets (or max_uint if no limit).
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:378-384, _max_deposit:816-825):
@external
@view
def maxDeposit(receiver: address) -> uint256:
return self._max_deposit(receiver)
# paused → 0; deposit_limit==0 → unlimited; else limit - TVL
Example:
Cross-links: deposit · maxMint · set_deposit_limit · pause
maxMint¶
MultiStrategyVault.maxMint(receiver) -> uint256
Maximum shares mintable right now without reverting. Derived from maxDeposit.
| Param | Type | Description |
|---|---|---|
receiver |
address |
Share recipient (ERC-4626 signature) |
Returns: uint256 — convertToShares(maxDeposit); max_uint if deposit unlimited.
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:386-396):
@external
@view
def maxMint(receiver: address) -> uint256:
max_assets: uint256 = self._max_deposit(receiver)
if max_assets == max_value(uint256):
return max_value(uint256)
return self._convert_to_shares(max_assets, False)
Example:
Cross-links: maxDeposit · mint · convertToShares
maxWithdraw¶
MultiStrategyVault.maxWithdraw(owner) -> uint256
Largest USDC withdraw that would succeed for owner given current vault liquidity.
| Param | Type | Description |
|---|---|---|
owner |
address |
Share holder |
Returns: uint256 — min(convertToAssets(balance), _max_liquid()). Liquidity-aware (stricter than pure ERC-4626 spec).
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:398-409, _max_liquid:829-842):
@external
@view
def maxWithdraw(owner: address) -> uint256:
owner_assets: uint256 = self._convert_to_assets(erc20.balanceOf[owner], False)
return min(owner_assets, self._max_liquid())
# _max_liquid = total_idle + sum(min(strategy.maxWithdraw, current_debt))
Example:
Cross-links: withdraw · maxRedeem · rebalance
maxRedeem¶
MultiStrategyVault.maxRedeem(owner) -> uint256
Largest share count owner can redeem given current vault liquidity.
| Param | Type | Description |
|---|---|---|
owner |
address |
Share holder |
Returns: uint256 — min(owner_shares, convertToShares(_max_liquid())). 0 if no liquid assets.
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:411-422):
@external
@view
def maxRedeem(owner: address) -> uint256:
owner_shares: uint256 = erc20.balanceOf[owner]
liquid_assets: uint256 = self._max_liquid()
if liquid_assets == 0:
return 0
liquid_shares: uint256 = self._convert_to_shares(liquid_assets, False)
return min(owner_shares, liquid_shares)
Example:
Cross-links: maxWithdraw · redeem
previewDeposit¶
MultiStrategyVault.previewDeposit(assets) -> uint256
Simulate deposit — shares received for given USDC in (floor rounding).
| Param | Type | Description |
|---|---|---|
assets |
uint256 |
USDC to deposit |
Returns: uint256 — expected shares (= convertToShares at current exchange rate).
Access: view, any caller. Does not check pause/deposit limit.
Source (khomdev-keep/src/MultiStrategyVault.vy:424-430):
@external
@view
def previewDeposit(assets: uint256) -> uint256:
return self._convert_to_shares(assets, False)
Example:
Cross-links: deposit · convertToShares · previewMint
previewMint¶
MultiStrategyVault.previewMint(shares) -> uint256
Simulate mint — USDC required to receive exact share amount (ceil rounding).
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Target share amount |
Returns: uint256 — USDC cost (= _convert_to_assets(shares, roundup=True)).
Access: view, any caller.
Source (khomdev-keep/src/MultiStrategyVault.vy:432-438):
@external
@view
def previewMint(shares: uint256) -> uint256:
return self._convert_to_assets(shares, True)
Example:
Cross-links: mint · previewDeposit · convertToAssets
previewWithdraw¶
MultiStrategyVault.previewWithdraw(assets) -> uint256
Simulate withdraw — shares burned to receive exact USDC out (ceil rounding).
| Param | Type | Description |
|---|---|---|
assets |
uint256 |
USDC to withdraw |
Returns: uint256 — shares required (= _convert_to_shares(assets, roundup=True)).
Access: view, any caller. Does not check liquidity.
Source (khomdev-keep/src/MultiStrategyVault.vy:440-446):
@external
@view
def previewWithdraw(assets: uint256) -> uint256:
return self._convert_to_shares(assets, True)
Example:
Cross-links: withdraw · previewRedeem · maxWithdraw
previewRedeem¶
MultiStrategyVault.previewRedeem(shares) -> uint256
Simulate redeem — USDC received for burning shares (floor rounding).
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Shares to redeem |
Returns: uint256 — USDC out (= convertToAssets at current rate).
Access: view, any caller. Does not check liquidity.
Source (khomdev-keep/src/MultiStrategyVault.vy:448-454):
@external
@view
def previewRedeem(shares: uint256) -> uint256:
return self._convert_to_assets(shares, False)
Example:
Cross-links: redeem · previewWithdraw · maxRedeem
User flows¶
deposit¶
MultiStrategyVault.deposit(assets, receiver) -> uint256
Deposit USDC, mint vault shares to receiver.
| Param | Type | Description |
|---|---|---|
assets |
uint256 |
USDC amount (≥ MIN_DEPOSIT = 0.001 USDC) |
receiver |
address |
Share recipient |
Returns: uint256 — shares minted.
Returns / state: Increments total_idle; mints shares. Caller must approve USDC.
Access: Any caller when not paused. Reverts if over deposit_limit or below MIN_DEPOSIT.
Events: Deposit(sender, owner, assets, shares).
Source (khomdev-keep/src/MultiStrategyVault.vy:460-474):
@external
@nonreentrant
def deposit(assets: uint256, receiver: address) -> uint256:
pausable._require_not_paused()
assert assets <= self._max_deposit(receiver), "vault: deposit exceeds limit"
assert assets >= MIN_DEPOSIT, "vault: below min deposit"
shares: uint256 = self._convert_to_shares(assets, False)
self._do_deposit(msg.sender, receiver, assets, shares)
return shares
Example:
Cross-links: previewDeposit · maxDeposit · allocateCapital · mint
mint¶
MultiStrategyVault.mint(shares, receiver) -> uint256
Mint exact share amount to receiver, pulling required USDC from caller.
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Target share amount |
receiver |
address |
Share recipient |
Returns: uint256 — USDC pulled from msg.sender.
Returns / state: Same as deposit — increments total_idle, mints shares. Assets computed with ceil rounding.
Access: Any caller when not paused. Reverts if over deposit_limit or below MIN_DEPOSIT.
Events: Deposit(sender, owner, assets, shares).
Source (khomdev-keep/src/MultiStrategyVault.vy:476-488):
@external
@nonreentrant
def mint(shares: uint256, receiver: address) -> uint256:
pausable._require_not_paused()
assets: uint256 = self._convert_to_assets(shares, True)
assert assets <= self._max_deposit(receiver), "vault: mint exceeds limit"
self._do_deposit(msg.sender, receiver, assets, shares)
return assets
Example:
Cross-links: previewMint · maxMint · deposit
withdraw¶
MultiStrategyVault.withdraw(assets, receiver, owner) -> uint256
Burn shares from owner, send exact USDC to receiver. Sources idle first, then strategies in withdrawal queue order.
| Param | Type | Description |
|---|---|---|
assets |
uint256 |
USDC to withdraw |
receiver |
address |
Payout recipient |
owner |
address |
Share holder (caller or approved) |
Returns: uint256 — shares burned.
Access: Any caller with allowance on owner's shares. Not blocked by pause — exits always allowed. Reverts if strategies cannot return full assets (max_loss_bps = 0).
Events: Withdraw(sender, receiver, owner, assets, shares).
Source (khomdev-keep/src/MultiStrategyVault.vy:490-504):
@external
@nonreentrant
def withdraw(assets: uint256, receiver: address, owner: address) -> uint256:
shares: uint256 = self._convert_to_shares(assets, True)
self._do_withdraw(msg.sender, receiver, owner, assets, shares, 0)
return shares
Example:
Cross-links: previewWithdraw · maxWithdraw · withdraw_with_max_loss
withdraw_with_max_loss¶
MultiStrategyVault.withdraw_with_max_loss(assets, receiver, owner, max_loss_bps) -> uint256
Like withdraw, but tolerates strategy shortfall up to max_loss_bps of requested assets.
| Param | Type | Description |
|---|---|---|
assets |
uint256 |
USDC requested |
receiver |
address |
Payout recipient |
owner |
address |
Share holder |
max_loss_bps |
uint256 |
Max acceptable shortfall (bps of assets, ≤ 10_000) |
Returns: uint256 — shares burned. May pay less than assets if strategies illiquid (within tolerance).
Access: Same as withdraw. Loss socialized via total_debt accounting — all shareholders bear proportional loss.
Events: Withdraw(sender, receiver, owner, assets, shares).
Source (khomdev-keep/src/MultiStrategyVault.vy:506-520):
@external
@nonreentrant
def withdraw_with_max_loss(assets, receiver, owner, max_loss_bps) -> uint256:
assert max_loss_bps <= MAX_BPS, "vault: max_loss > 100%"
shares: uint256 = self._convert_to_shares(assets, True)
self._do_withdraw(msg.sender, receiver, owner, assets, shares, max_loss_bps)
return shares
Example:
Cross-links: withdraw · redeem_with_max_loss
redeem¶
MultiStrategyVault.redeem(shares, receiver, owner) -> uint256
Burn exact shares from owner, pay USDC to receiver.
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Shares to burn |
receiver |
address |
USDC recipient |
owner |
address |
Share holder |
Returns: uint256 — USDC paid out (may be less than previewRedeem only via redeem_with_max_loss; strict path reverts on shortfall).
Access: Any caller with share allowance. Not blocked by pause. max_loss_bps = 0 — full payout required.
Events: Withdraw(sender, receiver, owner, assets, shares).
Source (khomdev-keep/src/MultiStrategyVault.vy:522-532):
@external
@nonreentrant
def redeem(shares: uint256, receiver: address, owner: address) -> uint256:
assets: uint256 = self._convert_to_assets(shares, False)
return self._do_withdraw(msg.sender, receiver, owner, assets, shares, 0)
Example:
Cross-links: previewRedeem · maxRedeem · redeem_with_max_loss
redeem_with_max_loss¶
MultiStrategyVault.redeem_with_max_loss(shares, receiver, owner, max_loss_bps) -> uint256
Like redeem, but tolerates strategy shortfall up to max_loss_bps of marked share value.
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Shares to burn |
receiver |
address |
USDC recipient |
owner |
address |
Share holder |
max_loss_bps |
uint256 |
Max acceptable shortfall (bps, ≤ 10_000) |
Returns: uint256 — USDC actually paid out (may be below previewRedeem within tolerance).
Access: Same as redeem. Loss socialized across all shareholders via debt accounting.
Events: Withdraw(sender, receiver, owner, assets, shares).
Source (khomdev-keep/src/MultiStrategyVault.vy:534-546):
@external
@nonreentrant
def redeem_with_max_loss(shares, receiver, owner, max_loss_bps) -> uint256:
assert max_loss_bps <= MAX_BPS, "vault: max_loss > 100%"
assets: uint256 = self._convert_to_assets(shares, False)
return self._do_withdraw(msg.sender, receiver, owner, assets, shares, max_loss_bps)
Example:
Cross-links: redeem · withdraw_with_max_loss
Strategy admin¶
add_strategy¶
MultiStrategyVault.add_strategy(strategy, max_debt, target_weight_bps)
Register new child strategy (ERC-4626-compatible, same USDC asset).
| Param | Type | Description |
|---|---|---|
strategy |
address |
Strategy contract |
max_debt |
uint256 |
Absolute cap on assets strategy may hold |
target_weight_bps |
uint256 |
Target TVL share in fixed-allocation mode (≤ 10_000) |
Returns / state: Appends to strategy_list (also withdrawal queue). Initializes current_debt = 0.
Access: STRATEGY_MANAGER_ROLE only. Max MAX_STRATEGIES (10). Strategy asset() must match vault.
Events: StrategyAdded(strategy, max_debt, target_weight_bps).
Source (khomdev-keep/src/MultiStrategyVault.vy:552-574):
@external
def add_strategy(strategy: address, max_debt: uint256, target_weight_bps: uint256):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
assert staticcall IStrategy(strategy).asset() == asset, "vault: asset mismatch"
self.strategies[strategy] = StrategyParams(activated=True, max_debt=max_debt, ...)
self.strategy_list.append(strategy)
Example:
Cross-links: remove_strategy · set_max_debt · set_allocations · CoilMakerStrategy
remove_strategy¶
MultiStrategyVault.remove_strategy(strategy, force)
Deregister strategy from vault and withdrawal queue.
| Param | Type | Description |
|---|---|---|
strategy |
address |
Strategy to remove |
force |
bool |
False = require zero debt; True = write off outstanding debt as loss |
Returns / state: Clears strategies[strategy]; removes from strategy_list. Force path decrements total_debt and socializes loss.
Access: STRATEGY_MANAGER_ROLE only (@nonreentrant).
Events: StrategyRemoved(strategy, forced); StrategyReported on forced debt write-off.
Source (khomdev-keep/src/MultiStrategyVault.vy:576-598):
@external
@nonreentrant
def remove_strategy(strategy: address, force: bool):
debt: uint256 = self.strategies[strategy].current_debt
if not force:
assert debt == 0, "vault: debt outstanding"
else:
if debt != 0:
self.total_debt -= debt
self._remove_from_list(strategy)
Example:
Cross-links: add_strategy · report
set_max_debt¶
MultiStrategyVault.set_max_debt(strategy, max_debt)
Update absolute debt ceiling for registered strategy.
| Param | Type | Description |
|---|---|---|
strategy |
address |
Active strategy |
max_debt |
uint256 |
New max assets strategy may hold |
Returns / state: Updates strategies[strategy].max_debt. Does not move capital — rebalance / allocateCapital enforce.
Access: STRATEGY_MANAGER_ROLE only.
Source (khomdev-keep/src/MultiStrategyVault.vy:600-607):
@external
def set_max_debt(strategy: address, max_debt: uint256):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
assert self.strategies[strategy].activated, "vault: not a strategy"
self.strategies[strategy].max_debt = max_debt
Example:
Cross-links: add_strategy · set_allocations · allocateCapital
set_allocations¶
MultiStrategyVault.set_allocations(weights)
Set per-strategy target weights (bps) in strategy_list order. Gauge voting seam.
| Param | Type | Description |
|---|---|---|
weights |
uint256[] |
Target bps per strategy (length = len(strategy_list), sum ≤ 10_000) |
Returns / state: Updates target_weight_bps for each strategy. Used by fixed-allocation rebalance.
Access: ALLOCATOR_ROLE only (GaugeWeightRouter in FLYWHEEL 2.0 — not STRATEGY_MANAGER_ROLE).
Events: AllocationsUpdated(weights).
Source (khomdev-keep/src/MultiStrategyVault.vy:609-627):
@external
def set_allocations(weights: DynArray[uint256, MAX_STRATEGIES]):
access_control._check_role(ALLOCATOR_ROLE, msg.sender)
assert len(weights) == len(self.strategy_list), "vault: weight count mismatch"
for i in range(len(weights)):
self.strategies[self.strategy_list[i]].target_weight_bps = weights[i]
log AllocationsUpdated(weights=weights)
Example:
Cross-links: GaugeWeightRouter · rebalance · set_buffer_bps
set_buffer_bps¶
MultiStrategyVault.set_buffer_bps(new_bps)
Set target idle USDC buffer as fraction of TVL. Rebalancer keeps total_idle near this ratio.
| Param | Type | Description |
|---|---|---|
new_bps |
uint256 |
Buffer share of TVL (0–MAX_BUFFER_BPS, max 5000 = 50%) |
Returns / state: Updates buffer_bps.
Access: STRATEGY_MANAGER_ROLE only.
Events: BufferUpdated(new_bps).
Source (khomdev-keep/src/MultiStrategyVault.vy:629-637):
@external
def set_buffer_bps(new_bps: uint256):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
assert new_bps <= MAX_BUFFER_BPS, "vault: buffer too large"
self.buffer_bps = new_bps
log BufferUpdated(new_bps=new_bps)
Example:
Cross-links: rebalance · allocateCapital · set_allocations
set_smart_routing¶
MultiStrategyVault.set_smart_routing(enabled)
Toggle APR-based smart routing vs fixed target_weight_bps allocation.
| Param | Type | Description |
|---|---|---|
enabled |
bool |
True = rebalance tilts to highest-APR strategies (capped by max_debt); False = honor manager weights |
Returns / state: Updates smart_routing_enabled.
Access: STRATEGY_MANAGER_ROLE only.
Events: SmartRoutingToggled(enabled).
Source (khomdev-keep/src/MultiStrategyVault.vy:639-646):
@external
def set_smart_routing(enabled: bool):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
self.smart_routing_enabled = enabled
log SmartRoutingToggled(enabled=enabled)
Example:
Cross-links: rebalance · set_allocations · report
set_accountant¶
MultiStrategyVault.set_accountant(new_accountant)
Set pluggable fee-assessment module. empty disables performance/management fee minting.
| Param | Type | Description |
|---|---|---|
new_accountant |
address |
Accountant contract (IAccountant); 0x0 = off |
Returns / state: Updates accountant storage. Fees assessed via report → _maybe_assess_fees (rate-limited 1/hr, max 20% TVL per assessment).
Access: STRATEGY_MANAGER_ROLE only.
Events: AccountantUpdated(new_accountant).
Source (khomdev-keep/src/MultiStrategyVault.vy:648-655):
@external
def set_accountant(new_accountant: address):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
self.accountant = new_accountant
log AccountantUpdated(new_accountant=new_accountant)
Example:
Cross-links: report · FeesAssessed event · set_deposit_limit
set_deposit_limit¶
MultiStrategyVault.set_deposit_limit(new_limit)
Set hard cap on vault TVL (totalAssets). 0 = unlimited.
| Param | Type | Description |
|---|---|---|
new_limit |
uint256 |
Max USDC TVL vault accepts; 0 disables cap |
Returns / state: Updates deposit_limit. Enforced by maxDeposit / deposit.
Access: STRATEGY_MANAGER_ROLE only.
Events: DepositLimitUpdated(new_limit).
Source (khomdev-keep/src/MultiStrategyVault.vy:657-664):
@external
def set_deposit_limit(new_limit: uint256):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
self.deposit_limit = new_limit
log DepositLimitUpdated(new_limit=new_limit)
Example:
Cross-links: maxDeposit · deposit
set_coil_maker_strategy¶
MultiStrategyVault.set_coil_maker_strategy(strategy)
FLYWHEEL 2.0: set primary CoilMakerStrategy for priority capital allocation in allocateCapital.
| Param | Type | Description |
|---|---|---|
strategy |
address |
Registered CoilMakerStrategy; empty = disable priority path |
Returns / state: Updates coil_maker_strategy. Strategy must already be in strategy_list with matching asset.
Access: STRATEGY_MANAGER_ROLE only.
Events: CoilMakerStrategySet(strategy).
Source (khomdev-keep/src/MultiStrategyVault.vy:667-682):
@external
def set_coil_maker_strategy(strategy: address):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
if strategy != empty(address):
assert self.strategies[strategy].activated, "vault: not a strategy"
self.coil_maker_strategy = strategy
log CoilMakerStrategySet(strategy=strategy)
Example:
Cross-links: CoilMakerStrategy · allocateCapital · CoilFeeRouter set_keeper
set_withdrawal_queue¶
MultiStrategyVault.set_withdrawal_queue(new_order)
Reorder strategy_list — defines withdrawal drain priority (front drained first in withdraw).
| Param | Type | Description |
|---|---|---|
new_order |
address[] |
Permutation of current strategies (same length, no adds/removes) |
Returns / state: Replaces strategy_list order. Each address must be activated exactly once.
Access: STRATEGY_MANAGER_ROLE only.
Source (khomdev-keep/src/MultiStrategyVault.vy:685-705):
@external
def set_withdrawal_queue(new_order: DynArray[address, MAX_STRATEGIES]):
access_control._check_role(STRATEGY_MANAGER_ROLE, msg.sender)
assert len(new_order) == len(self.strategy_list), "vault: queue size mismatch"
# validates permutation — each strategy exactly once
self.strategy_list = new_order
Example:
Cross-links: withdraw · add_strategy
Keeper¶
rebalance¶
MultiStrategyVault.rebalance()
Permissionless. Pull/push capital between strategies to converge on target allocations.
Returns / state: Withdraws from over-allocated strategies to idle, deposits idle to under-allocated (respecting max_debt). Skips moves within REBALANCE_THRESHOLD_BPS (5% deviation).
Modes:
- Fixed — targets from target_weight_bps minus buffer_bps idle reserve
- Smart — APR-tilted targets when smart_routing_enabled
Access: Any caller when not paused (@nonreentrant).
Source (khomdev-keep/src/MultiStrategyVault.vy:711-719, _rebalance:1012-1075):
Example:
Cross-links: set_allocations · set_smart_routing · set_buffer_bps · allocateCapital
allocateCapital¶
MultiStrategyVault.allocateCapital()
FLYWHEEL 2.0 priority deploy. Idle USDC → CoilMakerStrategy first, remainder → rebalance.
Returns / state: Deposits to coil_maker_strategy up to deployAvailable() and max_debt; calls _rebalance() for leftover idle. No-op if no idle. Falls through to full rebalance if coil maker unset.
Access: Any caller when not paused (@nonreentrant).
Events: CapitalAllocated(to_coil, to_fallbacks).
Source (khomdev-keep/src/MultiStrategyVault.vy:722-733, _allocate_capital:971-1005):
@external
@nonreentrant
def allocateCapital():
pausable._require_not_paused()
self._allocate_capital()
# coil first via deployAvailable(), then _rebalance() on remainder
Example:
Cross-links: set_coil_maker_strategy · CoilMakerStrategy · rebalance · CoilFeeRouter
report¶
MultiStrategyVault.report(strategy)
Permissionless. Recognize strategy P&L since last report; optionally mint accountant fee shares.
| Param | Type | Description |
|---|---|---|
strategy |
address |
Registered strategy to value |
Returns / state: Calls strategy.sync(), recomputes current_debt from on-chain assets vs prior debt, updates total_debt. Triggers _maybe_assess_fees if accountant set (max 1/hr, 20% TVL cap).
Access: Any caller (@nonreentrant). Strategy must be activated.
Events: StrategyReported(strategy, gain, loss, new_debt); FeesAssessed if accountant mints.
Source (khomdev-keep/src/MultiStrategyVault.vy:736-746, _report_internal:1187-1212):
@external
@nonreentrant
def report(strategy: address):
assert self.strategies[strategy].activated, "vault: not a strategy"
self._report_internal(strategy)
# gain/loss = strategy assets - current_debt; then _maybe_assess_fees()
Example:
Cross-links: set_accountant · harvest · rebalance
harvest¶
MultiStrategyVault.harvest(strategy) -> uint256
Convenience: call strategy harvest() hook, then report P&L.
| Param | Type | Description |
|---|---|---|
strategy |
address |
Strategy to harvest + report |
Returns: uint256 — value returned by strategy.harvest() (strategy-specific).
Access: Any caller when not paused (@nonreentrant). Strategy must be activated.
Events: Inherits from report — StrategyReported, optional FeesAssessed.
Source (khomdev-keep/src/MultiStrategyVault.vy:748-763):
@external
@nonreentrant
def harvest(strategy: address) -> uint256:
pausable._require_not_paused()
harvested: uint256 = extcall IStrategy(strategy).harvest()
self._report_internal(strategy)
return harvested
Example:
Cross-links: report · CoilMakerStrategy harvest
Emergency¶
pause¶
MultiStrategyVault.pause()
Pause new deposits and capital movement (rebalance, allocateCapital, harvest). Withdrawals stay open.
Returns / state: Sets paused = True (snekmate pausable).
Access: EMERGENCY_ROLE only.
Source (khomdev-keep/src/MultiStrategyVault.vy:769-775):
Example:
Cross-links: unpause · deposit · withdraw
unpause¶
MultiStrategyVault.unpause()
Resume deposits and capital movement after emergency pause.
Returns / state: Sets paused = False.
Access: EMERGENCY_ROLE only.
Source (khomdev-keep/src/MultiStrategyVault.vy:777-783):
Example:
Cross-links: pause · deposit · rebalance