Skip to content

MorphoStrategy

ERC-4626-shaped MSV strategy. Single-market supply-only Morpho Blue; market identity fixed at deploy.

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

Consumer: MultiStrategyVault fallback / smart-routed strategy

Implementation overview

  • NAV — Morpho supply position via morpho.position(market_id, self) + SharesMathLib virtual offsets
  • Market — immutable MarketParams (loan/collateral/oracle/IRM/LLTV); multi-market routing belongs in a separate adapter
  • Shares — internal accounting; only vault deposits/withdraws
  • APR — cached apr_bps for smart routing (admin-set)
  • Harvest — no-op (no Morpho protocol rewards)
  • syncmorpho.accrueInterest(market) before MSV report() (audit A7)
flowchart LR
    MSV[MultiStrategyVault] --> M[MorphoStrategy]
    M --> MB[Morpho Blue market]
    M -->|sync| MB

Immutables

Name Role
asset Base loan token (must match market loanToken)
vault Parent MSV
morpho Morpho Blue singleton
loan_token / collateral_token / oracle / irm / lltv Fixed market identity
market_id keccak256(abi.encode(MarketParams))

Roles

Role Powers
DEFAULT_ADMIN_ROLE APR, pause deposits, sweep stray tokens

Events

Event When
Deposit / Withdraw Vault capital flows
AprUpdated / DepositsPaused Admin config

ERC-4626 views

totalAssets

MorphoStrategy.totalAssets() -> uint256

Strategy NAV — pro-rata Morpho supply shares valued with SharesMathLib virtual offsets.

Returns: uint256supplyShares * (totalSupplyAssets + VIRTUAL_ASSETS) / (totalSupplyShares + VIRTUAL_SHARES).

Access: view, any caller.

Note: Reads last-accrued market state; MSV report() calls sync first (audit A7).

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:132-135, _total_assets 380-394):

@external
@view
def totalAssets() -> uint256:
    return self._total_assets()

# pos = morpho.position(market_id, self)
# return pos.supplyShares * total_assets // total_shares  # with virtual offsets

Example:

nav = morpho_strategy.totalAssets()

Cross-links: sync · convertToShares · MultiStrategyVault report

balanceOf

MorphoStrategy.balanceOf(account) -> uint256

Internal share balance for account (typically only MSV holds shares).

Param Type Description
account address Share holder

Returns: uint256shares_held[account].

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:137-140):

@external
@view
def balanceOf(account: address) -> uint256:
    return self.shares_held[account]

Cross-links: totalSupply · convertToAssets

totalSupply

MorphoStrategy.totalSupply() -> uint256

Total strategy shares outstanding.

Returns: uint256total_shares.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:142-145):

@external
@view
def totalSupply() -> uint256:
    return self.total_shares

Cross-links: totalAssets · balanceOf

convertToAssets

MorphoStrategy.convertToAssets(shares) -> uint256

Shares → base asset quote (floor).

Param Type Description
shares uint256 Strategy shares

Returns: uint256 — asset value at current Morpho NAV.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:147-150, _convert_to_assets 408-416):

@external
@view
def convertToAssets(shares: uint256) -> uint256:
    return self._convert_to_assets(shares, False)

Cross-links: convertToShares · previewRedeem

convertToShares

MorphoStrategy.convertToShares(assets) -> uint256

Base asset → shares quote (floor).

Param Type Description
assets uint256 Base asset amount

Returns: uint256 — shares at current NAV. No virtual-share offset (vault-only depositor).

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:152-155, _convert_to_shares 396-406):

@external
@view
def convertToShares(assets: uint256) -> uint256:
    return self._convert_to_shares(assets, False)

Cross-links: convertToAssets · previewDeposit

maxDeposit

MorphoStrategy.maxDeposit(receiver) -> uint256

Max base asset depositable (ERC-4626 view).

Param Type Description
receiver address Must be vault

Returns: uint2560 if paused or receiver != vault; else max_uint256.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:157-162):

@external
@view
def maxDeposit(receiver: address) -> uint256:
    if self.deposits_paused or receiver != vault:
        return 0
    return max_value(uint256)

Cross-links: deposit · maxMint · set_deposits_paused

maxMint

MorphoStrategy.maxMint(receiver) -> uint256

Max shares mintable (vault-only).

Param Type Description
receiver address Must be vault

Returns: uint256 — same gates as maxDeposit.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:164-169):

@external
@view
def maxMint(receiver: address) -> uint256:
    if self.deposits_paused or receiver != vault:
        return 0
    return max_value(uint256)

Cross-links: mint · maxDeposit

maxWithdraw

MorphoStrategy.maxWithdraw(owner) -> uint256

Max base asset withdrawable for owner.

Param Type Description
owner address Must be vault

Returns: uint256convertToAssets(shares_held[vault]); 0 if not vault.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:171-176):

@external
@view
def maxWithdraw(owner: address) -> uint256:
    if owner != vault:
        return 0
    return self._convert_to_assets(self.shares_held[owner], False)

Cross-links: withdraw · maxRedeem

maxRedeem

MorphoStrategy.maxRedeem(owner) -> uint256

Max shares redeemable by owner.

Param Type Description
owner address Must be vault

Returns: uint256 — full shares_held[owner]; 0 if not vault.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:178-183):

@external
@view
def maxRedeem(owner: address) -> uint256:
    if owner != vault:
        return 0
    return self.shares_held[owner]

Cross-links: redeem · maxWithdraw

previewDeposit

MorphoStrategy.previewDeposit(assets) -> uint256

Simulate deposit — shares minted (floor).

Param Type Description
assets uint256 Base asset in

Returns: uint256 — expected shares (= convertToShares).

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:185-188):

@external
@view
def previewDeposit(assets: uint256) -> uint256:
    return self._convert_to_shares(assets, False)

Cross-links: deposit · convertToShares

previewWithdraw

MorphoStrategy.previewWithdraw(assets) -> uint256

Simulate withdraw — shares burned (ceil).

Param Type Description
assets uint256 Base asset out

Returns: uint256 — shares required.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:190-193):

@external
@view
def previewWithdraw(assets: uint256) -> uint256:
    return self._convert_to_shares(assets, True)

Cross-links: withdraw · previewRedeem

previewRedeem

MorphoStrategy.previewRedeem(shares) -> uint256

Simulate redeem — base asset out (floor).

Param Type Description
shares uint256 Shares to burn

Returns: uint256 — expected asset (= convertToAssets).

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:195-198):

@external
@view
def previewRedeem(shares: uint256) -> uint256:
    return self._convert_to_assets(shares, False)

Cross-links: redeem · convertToAssets

IStrategy hooks

getApr

MorphoStrategy.getApr() -> uint256

Cached APR for MSV smart routing (from off-chain Morpho supply APY).

Returns: uint256apr_bps.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:204-207):

@external
@view
def getApr() -> uint256:
    return self.apr_bps

Cross-links: set_apr_bps · Rebalancer compute_smart_targets

harvest

MorphoStrategy.harvest() -> uint256

No-op. Morpho Blue has no protocol rewards; interest accrues via sync.

Returns: uint256 — always 0.

Access: Any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:209-215):

@external
def harvest() -> uint256:
    return 0

Cross-links: sync · totalAssets

sync

MorphoStrategy.sync()

Pokes Morpho Blue lazy interest accrual before MSV report() (audit A7).

Access: Any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:217-224):

@external
def sync():
    extcall IMorpho(morpho).accrueInterest(self._market_params())

Cross-links: totalAssets · harvest · MultiStrategyVault report

deployAvailable

MorphoStrategy.deployAvailable(amount) -> uint256

Fallback deploy capacity — always accepts full amount (vault max_debt is real cap).

Param Type Description
amount uint256 Requested base asset deployment

Returns: uint256max_uint256.

Access: view, any caller.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:231-238):

@external
@view
def deployAvailable(amount: uint256) -> uint256:
    return max_value(uint256)

Cross-links: MultiStrategyVault allocateCapital

ERC-4626 writes

deposit

MorphoStrategy.deposit(assets_in, receiver) -> uint256

Vault supplies base asset to Morpho Blue; mints internal shares to receiver (must be vault).

Param Type Description
assets_in uint256 Base asset amount
receiver address Share recipient (vault only)

Returns: uint256 — shares minted.

Returns / state: transferFrom vault → morpho.supply(market, assets, 0, self).

Access: vault only. nonreentrant. Reverts if deposits_paused.

Events: Deposit.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:245-267):

@external
@nonreentrant
def deposit(assets_in: uint256, receiver: address) -> uint256:
    self._only_vault()
    shares = self._convert_to_shares(assets_in, False)
    IERC20(asset).transferFrom(vault, self, assets_in)
    IMorpho(morpho).supply(self._market_params(), assets_in, 0, self, b"")
    self.total_shares += shares
    self.shares_held[receiver] += shares
    return shares

Cross-links: previewDeposit · withdraw

mint

MorphoStrategy.mint(shares, receiver) -> uint256

Mint exact shares; vault pays ceil base asset → Morpho supply.

Param Type Description
shares uint256 Target shares
receiver address Must be vault

Returns: uint256 — base asset pulled from vault.

Access: vault only. Same gates as deposit.

Events: Deposit.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:269-290):

@external
@nonreentrant
def mint(shares: uint256, receiver: address) -> uint256:
    assets_in = self._convert_to_assets(shares, True)
    IMorpho(morpho).supply(self._market_params(), assets_in, 0, self, b"")
    return assets_in

Cross-links: deposit · maxMint

withdraw

MorphoStrategy.withdraw(assets_out, receiver, owner) -> uint256

Burn shares; pull base asset from Morpho to receiver.

Param Type Description
assets_out uint256 Target base asset
receiver address Payout address
owner address Must be vault

Returns: uint256 — shares burned. Full exit caps pull at share value (audit A3).

Access: vault only. nonreentrant.

Events: Withdraw.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:292-310):

@external
@nonreentrant
def withdraw(assets_out, receiver, owner) -> uint256:
    shares = self._convert_to_shares(assets_out, True)
    if shares > held:
        shares = held
        assets_to_pull = self._convert_to_assets(shares, False)
    IMorpho(morpho).withdraw(self._market_params(), assets_to_pull, 0, self, receiver)
    return shares

Cross-links: redeem · previewWithdraw

redeem

MorphoStrategy.redeem(shares, receiver, owner) -> uint256

Burn exact shares; pull base asset from Morpho to receiver.

Param Type Description
shares uint256 Shares to burn
receiver address Asset recipient
owner address Must be vault

Returns: uint256 — base asset withdrawn (convertToAssets floor).

Access: vault only. nonreentrant.

Events: Withdraw.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:312-325):

@external
@nonreentrant
def redeem(shares, receiver, owner) -> uint256:
    assets_out = self._convert_to_assets(shares, False)
    IMorpho(morpho).withdraw(self._market_params(), assets_out, 0, self, receiver)
    return assets_out

Cross-links: withdraw · previewRedeem

Admin

set_apr_bps

MorphoStrategy.set_apr_bps(new_bps)

Update cached APR for smart routing (from off-chain Morpho supply APY).

Param Type Description
new_bps uint256 APR bps (≤ 5000)

Access: DEFAULT_ADMIN_ROLE only.

Events: AprUpdated(new_bps).

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:331-336):

@external
def set_apr_bps(new_bps: uint256):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    assert new_bps <= 5_000
    self.apr_bps = new_bps

Cross-links: getApr

set_deposits_paused

MorphoStrategy.set_deposits_paused(state)

Pause/resume vault deposits. Withdrawals always allowed.

Param Type Description
state bool True = pause deposits

Access: DEFAULT_ADMIN_ROLE only.

Events: DepositsPaused(state).

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:338-342):

@external
def set_deposits_paused(state: bool):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    self.deposits_paused = state

Cross-links: deposit · maxDeposit

sweep

MorphoStrategy.sweep(token, to)

Recover stray ERC-20. Cannot sweep underlying (custody lives in Morpho Blue).

Param Type Description
token address Token to sweep
to address Recipient

Access: DEFAULT_ADMIN_ROLE only. Cannot sweep asset.

Source (khomdev-keep/src/strategies/MorphoStrategy.vy:344-354):

@external
def sweep(token: address, to: address):
    assert token != asset
    bal = IERC20(token).balanceOf(self)
    IERC20(token).transfer(to, bal)