Skip to content

CompoundV3Strategy

ERC-4626-shaped MSV strategy. Supplies USDC to Compound V3 (Comet); permissionless harvest() claims COMP rewards and swaps back to base asset via Uniswap V3.

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

Consumer: MultiStrategyVault fallback / smart-routed strategy

Implementation overview

  • NAVtotalAssets = comet.balanceOf(self) (interest accrues in Comet balance)
  • Harvest — claim COMP from CometRewards → Uni V3 swap → resupply to Comet; guarded by admin min_out_rate + max_slippage_bps
  • Shares — internal accounting; only vault deposits/withdraws
  • APR — cached apr_bps for smart routing (admin-set)
  • sync — no-op (Comet balance live on read)
flowchart LR
    MSV[MultiStrategyVault] --> C3[CompoundV3Strategy]
    C3 --> Comet[Compound V3 Comet]
    C3 -->|harvest| Uni[Uniswap V3]
    Uni -->|USDC| Comet

Immutables

Name Role
asset Base asset (USDC)
vault Parent MSV
comet Compound V3 market (e.g. cUSDCv3)
comet_rewards CometRewards distributor
comp_token COMP reward token

Admin config

Name Role
swap_router Uniswap V3 router for COMP→asset
swap_pool_fee V3 fee tier (e.g. 3000 = 0.3%)
min_out_rate Fair COMP price floor (asset per 1e18 COMP)
max_slippage_bps Slippage cap below fair price (≤ 1000)

Roles

Role Powers
DEFAULT_ADMIN_ROLE APR, swap config, pause deposits, sweep stray tokens

Events

Event When
Deposit / Withdraw Vault capital flows
Harvested COMP claimed + swapped + resupplied
AprUpdated / SwapConfigUpdated / DepositsPaused Admin config

ERC-4626 views

totalAssets

CompoundV3Strategy.totalAssets() -> uint256

Strategy NAV — Comet supplier balance in base asset (includes accrued interest).

Returns: uint256comet.balanceOf(self).

Access: view, any caller.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:147-150, _total_assets 436-443):

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

# return staticcall ICompoundV3(comet).balanceOf(self)

Example:

nav = compound_strategy.totalAssets()

Cross-links: convertToShares · MultiStrategyVault report · harvest

balanceOf

CompoundV3Strategy.balanceOf(account) -> uint256

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

Inputs:

Name Type Description
account address Share holder

Returns: uint256shares_held[account].

Access: view, any caller.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:152-155):

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

Example:

vault_shares = compound_strategy.balanceOf(msv.address)

Cross-links: totalSupply · convertToAssets

totalSupply

CompoundV3Strategy.totalSupply() -> uint256

Total strategy shares outstanding.

Returns: uint256total_shares.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:157-160):

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

Cross-links: totalAssets · balanceOf

convertToAssets

CompoundV3Strategy.convertToAssets(shares) -> uint256

Shares → base asset quote (floor).

Param Type Description
shares uint256 Strategy shares

Returns: uint256 — asset value at current Comet NAV.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:162-165, _convert_to_assets 457-465):

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

Cross-links: convertToShares · previewRedeem

convertToShares

CompoundV3Strategy.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/CompoundV3Strategy.vy:167-170, _convert_to_shares 445-455):

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

Cross-links: convertToAssets · previewDeposit

maxDeposit

CompoundV3Strategy.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/CompoundV3Strategy.vy:172-177):

@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

CompoundV3Strategy.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/CompoundV3Strategy.vy:179-184):

@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

CompoundV3Strategy.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/CompoundV3Strategy.vy:186-191):

@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

CompoundV3Strategy.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/CompoundV3Strategy.vy:193-198):

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

Cross-links: redeem · maxWithdraw

previewDeposit

CompoundV3Strategy.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/CompoundV3Strategy.vy:200-203):

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

Cross-links: deposit · convertToShares

previewWithdraw

CompoundV3Strategy.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/CompoundV3Strategy.vy:205-208):

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

Cross-links: withdraw · previewRedeem

previewRedeem

CompoundV3Strategy.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/CompoundV3Strategy.vy:210-213):

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

Cross-links: redeem · convertToAssets

IStrategy hooks

getApr

CompoundV3Strategy.getApr() -> uint256

Cached APR for MSV smart routing (off-chain Compound supply rate → admin update).

Returns: uint256apr_bps.

Access: view, any caller.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:219-222):

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

Cross-links: set_apr_bps · Rebalancer compute_smart_targets

sync

CompoundV3Strategy.sync()

No-op. Comet balanceOf returns interest-included balance on every read.

Access: Any caller.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:224-230):

@external
def sync():
    pass

Cross-links: totalAssets · harvest

deployAvailable

CompoundV3Strategy.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/CompoundV3Strategy.vy:237-244):

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

Cross-links: MultiStrategyVault allocateCapital

harvest

CompoundV3Strategy.harvest() -> uint256

Claim COMP from CometRewards, swap to base asset via Uniswap V3, resupply to Comet.

Returns: uint256 — asset received from swap; 0 if no COMP or min_out_rate == 0.

Access: Permissionless. nonreentrant.

Events: Harvested.

Slippage: min_out = comp * min_out_rate / 1e18 * (10000 - max_slippage_bps) / 10000. If min_out_rate == 0, swap skipped (COMP stays until admin sets rate).

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:247-295):

@external
@nonreentrant
def harvest() -> uint256:
    extcall ICometRewards(comet_rewards).claim(comet, self, True)
    comp_balance = IERC20(comp_token).balanceOf(self)
    if comp_balance == 0:
        return 0
    if self.min_out_rate == 0:
        return 0
    expected = comp_balance * self.min_out_rate // 10**18
    min_out = expected * (10_000 - self.max_slippage_bps) // 10_000
    # Uni V3 exactInputSingle COMP → asset, then comet.supply(asset, received)
    return received

Example:

received = compound_strategy.harvest()

Cross-links: set_swap_config · totalAssets · MultiStrategyVault report

ERC-4626 writes

deposit

CompoundV3Strategy.deposit(assets, receiver) -> uint256

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

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

Returns: uint256 — shares minted.

Returns / state: transferFrom vault → comet.supply → supplier balance accrues interest.

Access: vault only. nonreentrant. Reverts if deposits_paused.

Events: Deposit.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:301-322):

@external
@nonreentrant
def deposit(assets: uint256, receiver: address) -> uint256:
    self._only_vault()
    assert not self.deposits_paused
    assert receiver == vault
    shares = self._convert_to_shares(assets, False)
    IERC20(asset).transferFrom(vault, self, assets)
    ICompoundV3(comet).supply(asset, assets)
    self.total_shares += shares
    self.shares_held[receiver] += shares
    return shares

Cross-links: previewDeposit · withdraw

mint

CompoundV3Strategy.mint(shares, receiver) -> uint256

Mint exact shares; vault pays ceil base asset → Comet 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/CompoundV3Strategy.vy:324-344):

@external
@nonreentrant
def mint(shares: uint256, receiver: address) -> uint256:
    assets = self._convert_to_assets(shares, True)
    IERC20(asset).transferFrom(vault, self, assets)
    ICompoundV3(comet).supply(asset, assets)
    self.total_shares += shares
    self.shares_held[receiver] += shares
    return assets

Cross-links: deposit · maxMint

withdraw

CompoundV3Strategy.withdraw(assets, receiver, owner) -> uint256

Burn shares; pull base asset from Comet to receiver.

Param Type Description
assets 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/CompoundV3Strategy.vy:346-364):

@external
@nonreentrant
def withdraw(assets, receiver, owner) -> uint256:
    shares = self._convert_to_shares(assets, True)
    if shares > held:
        shares = held
        assets_to_pull = self._convert_to_assets(shares, False)
    ICompoundV3(comet).withdrawTo(receiver, asset, assets_to_pull)
    return shares

Cross-links: redeem · previewWithdraw

redeem

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

Burn exact shares; pull base asset from Comet 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/CompoundV3Strategy.vy:366-379):

@external
@nonreentrant
def redeem(shares: uint256, receiver: address, owner: address) -> uint256:
    assert shares <= self.shares_held[owner]
    assets = self._convert_to_assets(shares, False)
    self.shares_held[owner] -= shares
    self.total_shares -= shares
    ICompoundV3(comet).withdrawTo(receiver, asset, assets)
    return assets

Cross-links: withdraw · previewRedeem

Admin

set_apr_bps

CompoundV3Strategy.set_apr_bps(new_bps)

Update cached APR for smart routing (from off-chain Compound supply rate).

Param Type Description
new_bps uint256 APR bps (≤ 5000)

Access: DEFAULT_ADMIN_ROLE only.

Events: AprUpdated(new_bps).

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:385-390):

@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_swap_config

CompoundV3Strategy.set_swap_config(router_, pool_fee_, min_out_rate_, slippage_bps_)

Atomic update of Uni V3 harvest swap params.

Param Type Description
router_ address Uniswap V3 router (non-zero)
pool_fee_ uint24 V3 fee tier
min_out_rate_ uint256 Fair COMP price floor (asset per 1e18 COMP)
slippage_bps_ uint256 Max slippage below fair price (≤ 1000)

Access: DEFAULT_ADMIN_ROLE only.

Events: SwapConfigUpdated.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:392-412):

@external
def set_swap_config(router_, pool_fee_, min_out_rate_, slippage_bps_):
    access_control._check_role(DEFAULT_ADMIN_ROLE, msg.sender)
    assert router_ != empty(address)
    assert slippage_bps_ <= MAX_SLIPPAGE_CAP
    self.swap_router = router_
    self.min_out_rate = min_out_rate_
    self.max_slippage_bps = slippage_bps_

Cross-links: harvest

set_deposits_paused

CompoundV3Strategy.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/CompoundV3Strategy.vy:414-418):

@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

CompoundV3Strategy.sweep(token, to)

Recover stray ERC-20. Cannot sweep base asset (held in Comet), COMP, or Comet itself.

Param Type Description
token address Token to sweep
to address Recipient

Access: DEFAULT_ADMIN_ROLE only.

Source (khomdev-keep/src/strategies/CompoundV3Strategy.vy:420-430):

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