Skip to content

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 (not balanceOf — anti-donation).
  • Strategy router — up to 10 strategies; allocateCapital / rebalance deploy idle USDC.
  • FLYWHEEL 2.0coil_maker_strategy gets priority allocation (CoilMakerStrategy).
  • RolesSTRATEGY_MANAGER_ROLE, ALLOCATOR_ROLE (gauge only), EMERGENCY_ROLE.
  • Pausable depositspause blocks 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: uint256total_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:

tvl = vault.totalAssets()

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:

shares = vault.convertToShares(10_000 * 10**6)

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:

usdc_out = vault.convertToAssets(my_shares)

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: uint2560 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:

cap = vault.maxDeposit(user)

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: uint256convertToShares(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:

max_shares = vault.maxMint(user)

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: uint256min(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:

max_out = vault.maxWithdraw(user)

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: uint256min(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:

max_shares = vault.maxRedeem(user)

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:

shares_out = vault.previewDeposit(5_000 * 10**6)

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:

usdc_in = vault.previewMint(1_000 * 10**18)

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:

shares_burn = vault.previewWithdraw(1_000 * 10**6)

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:

usdc_out = vault.previewRedeem(my_shares)

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:

usdc.approve(vault, 10_000 * 10**6)
shares = vault.deposit(10_000 * 10**6, user)

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:

usdc_spent = vault.mint(target_shares, user)

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:

shares_burned = vault.withdraw(5_000 * 10**6, user, user)

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:

vault.withdraw_with_max_loss(5_000 * 10**6, user, user, 100)  # tolerate 1% shortfall

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:

usdc_out = vault.redeem(my_shares, user, user)

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:

paid = vault.redeem_with_max_loss(my_shares, user, user, 50)  # tolerate 0.5% shortfall

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:

vault.add_strategy(coil_maker, 1_000_000 * 10**6, 5000)  # 50% target weight

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:

vault.remove_strategy(old_strategy, False)  # must be fully withdrawn first

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:

vault.set_max_debt(coil_maker, 2_000_000 * 10**6)

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:

vault.set_allocations([5000, 3000, 1500])  # called by GaugeWeightRouter

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 (0MAX_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:

vault.set_buffer_bps(1000)  # 10% TVL kept liquid

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:

vault.set_smart_routing(True)

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:

vault.set_accountant(accountant_addr)

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:

vault.set_deposit_limit(10_000_000 * 10**6)  # 10M USDC cap

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:

vault.set_coil_maker_strategy(coil_maker)  # after add_strategy

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:

vault.set_withdrawal_queue([coil_maker, fallback_a, fallback_b])

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):

@external
@nonreentrant
def rebalance():
    pausable._require_not_paused()
    self._rebalance()

Example:

vault.rebalance()  # keeper or anyone

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:

vault.allocateCapital()  # after CoilFeeRouter USDC lands

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:

vault.report(coil_maker)  # recognize CoilMakerStrategy gains

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 reportStrategyReported, 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:

vault.harvest(coil_maker)

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):

@external
def pause():
    access_control._check_role(EMERGENCY_ROLE, msg.sender)
    pausable._pause()

Example:

vault.pause()  # EMERGENCY_ROLE holder

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):

@external
def unpause():
    access_control._check_role(EMERGENCY_ROLE, msg.sender)
    pausable._unpause()

Example:

vault.unpause()

Cross-links: pause · deposit · rebalance