Skip to content
Lattice

StrategyManagerLib

Git Source

Title: StrategyManagerLib

Authors: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80), Modified from Yearn V3 (https://github.com/yearn/yearn-vaults-v3/blob/master/contracts/VaultV3.vy)

Library implementing yield strategy management for a single ERC-4626 vault.

All logic lives here; the StrategyManager facet is a pure delegator.

Architecture:

  • The StrategyManager holds a registry of trusted external strategies and their target allocations (in basis points, sum <= 10 000).
  • totalAllocated() sums each strategy's self-reported balance (trust assumption: strategies report accurate values).
  • harvest() is a public snapshotting function that emits the current total for off-chain indexers without moving any funds.
  • rebalance() drives assets to/from strategies to match target allocations. It calls IVaultCore.allocateToStrategy (vault pushes excess) and IStrategy.withdraw (strategy pushes back to vault) as needed, then IProtocolAdapter.deploy so an adapter's idle does not stay undeployed.

Functions

strategyManagerStorage

function strategyManagerStorage() internal pure returns (StrategyManagerStorage storage $);

Returns

NameTypeDescription
$StrategyManagerStorage

__StrategyManager_init

Initializes the StrategyManager module.

Must be called inside a pre/postInitializer block. AccessControl must already be initialized.

function __StrategyManager_init() internal;

registerInterfaces

Registers support for the IStrategyManager and IStrategyManagerRecovery interfaces via ERC-165.

function registerInterfaces() internal;

vault

Returns the address of the associated vault.

function vault() internal view returns (address);

Returns

NameTypeDescription
<none>address

getStrategies

Returns all registered strategy addresses.

function getStrategies() internal view returns (address[] memory);

Returns

NameTypeDescription
<none>address[]

getStrategyTarget

Returns the target allocation in bps for a strategy (0 if not registered).

function getStrategyTarget(address strategy) internal view returns (uint16);

Parameters

NameTypeDescription
strategyaddress

Returns

NameTypeDescription
<none>uint16

totalTargetBps

Returns the current sum of all target allocations in basis points.

function totalTargetBps() internal view returns (uint256);

Returns

NameTypeDescription
<none>uint256

depositsLatched

Returns true while a force removal keeps the vault's deposits closed (see clearDepositLatch).

function depositsLatched() internal view returns (bool);

Returns

NameTypeDescription
<none>bool

totalAllocated

Returns the sum of all strategies' self-reported managed balances.

Trust assumption: each registered strategy must accurately report totalAssetsManaged(). A malicious strategy could inflate this value, causing the vault to miscalculate share prices. Only add audited strategies. Reverts if any strategy's read reverts (or the sum overflows); the vault then fails closed until the admin force-removes a reverting strategy through removeStrategy (which latches deposits closed until clearDepositLatch), or, as a last resort, the vault admin points VaultCore.setStrategyManager at a fresh manager (which latches the vault's deposits, #305).

function totalAllocated() internal view returns (uint256 total);

Returns

NameTypeDescription
totaluint256

setVault

Sets the vault address. Admin-only (DEFAULT_ADMIN_ROLE).

function setVault(address _vault) internal;

Parameters

NameTypeDescription
_vaultaddressAddress of the ERC-4626 vault this manager serves.

_setVault

Inner logic for setVault (no auth check).

function _setVault(address _vault) internal;

Parameters

NameTypeDescription
_vaultaddress

addStrategy

Registers a new strategy with a target allocation. Admin-only.

Reverts with IStrategyManager.StrategyManagerStrategyNotEmpty if the strategy already reports a balance, and bubbles the revert if its totalAssetsManaged() read fails.

function addStrategy(address strategy, uint16 targetBps) internal;

Parameters

NameTypeDescription
strategyaddressStrategy contract address.
targetBpsuint16Target allocation in basis points (0–10 000).

_addStrategy

Inner logic for addStrategy.

function _addStrategy(address strategy, uint16 targetBps) internal;

Parameters

NameTypeDescription
strategyaddress
targetBpsuint16

removeStrategy

Removes a registered strategy. Admin-only. Uses swap-and-pop.

A strategy whose totalAssetsManaged() read fails is force-removed and latches deposits closed (see _removeStrategy).

function removeStrategy(address strategy) internal;

Parameters

NameTypeDescription
strategyaddressStrategy address to remove.

_removeStrategy

Inner logic for removeStrategy. Force removal: a failing balance read makes totalAllocated revert, which makes the vault's totalAssets() revert and freezes the vault (fail closed). To recover, a strategy whose read reverts or returns malformed data is removed without the balance check and emits IStrategyManager.StrategyForceRemoved. Any funds it still holds leave the vault's NAV, and funds it later returns to the vault, by any path, accrue to whoever holds shares at that moment. So that a depositor entering at the lower NAV cannot capture them (#270), the removal also latches deposits closed and emits IStrategyManagerRecovery.DepositLatchSet: exits reopen (capped at idle), entries stay closed until the admin calls clearDepositLatch. Funds returned after the latch is cleared accrue to whoever holds shares then. _addStrategy rejects re-adding the strategy while it still reports a balance. A strategy that reports a well-formed but overflowing balance also freezes the vault, yet is not a failed read here and cannot be force-removed; the last resort is VaultCore.setStrategyManager with a fresh manager, which drops all of this manager's strategies from the NAV and, since this manager still reports allocations (or cannot answer, when its own sum overflows), latches the vault's deposits until the vault admin clears them (IVaultCoreRecovery, #305).

function _removeStrategy(address strategy) internal;

Parameters

NameTypeDescription
strategyaddress

clearDepositLatch

Clears the deposit latch a force removal set, reopening the vault's deposits unless the vault's own manager-swap latch (IVaultCoreRecovery) is also set. Admin-only (DEFAULT_ADMIN_ROLE, the role that gates removeStrategy).

function clearDepositLatch() internal;

_clearDepositLatch

Inner logic for clearDepositLatch (no auth check).

function _clearDepositLatch() internal;

updateStrategyTarget

Updates the target allocation for a registered strategy. Admin-only.

function updateStrategyTarget(address strategy, uint16 newBps) internal;

Parameters

NameTypeDescription
strategyaddressRegistered strategy address.
newBpsuint16New target allocation in basis points.

_updateStrategyTarget

Inner logic for updateStrategyTarget.

function _updateStrategyTarget(address strategy, uint16 newBps) internal;

Parameters

NameTypeDescription
strategyaddress
newBpsuint16

harvest

Snapshots the current allocated balance across all strategies and emits Harvested.

Anyone can call; no funds move. Useful for off-chain indexers tracking yield accrual.

function harvest() internal;

rebalance

Rebalances the vault's asset distribution to match strategy target allocations.

Uses a two-pass approach to avoid order-dependent atomicity failures (M-1): Pass 1 — process all over-allocated strategies (withdrawals back to vault) first. Pass 2 — process all under-allocated strategies (allocations from vault) second. This guarantees the vault holds maximum idle balance before any allocation is attempted, regardless of strategy registration order. A final pass then deploys each protocol adapter's idle (#221).

For each strategy in pass 1:

  • If current > target: calls IStrategy.withdraw and checks the recall (see _recall). For each strategy in pass 2:
  • If current < target: calls IVaultCore.allocateToStrategy, capped at the vault's actual idle. For each strategy in the deploy pass:
  • If it holds idle asset and advertises IProtocolAdapter: calls its deploy() (see _deployIdle).

Anyone can call. Protected against reentrancy (M-2): the guard is held for the whole rebalance, and VaultCore rejects deposit/mint/withdraw/redeem while it is held (reentrancyGuardEntered()), closing the read-only-reentrancy window.

vaultTotal is read once and intentionally includes the vault's idle balance (a raw ERC-20 balance). A direct token donation to the vault therefore raises every target — this is correct: a donation is NAV that belongs to share holders and is simply deployed per the configured allocation. Funds only move to pre-vetted strategies (trust model: add audited strategies only), and share-price manipulation from donations is bounded by ERC-4626's virtual-shares offset. AUM is conserved across idle<->strategy moves (pass 1 rejects any recall that loses more than REBALANCE_SHORTFALL_TOLERANCE), so the single vaultTotal snapshot stays valid for sizing targets. An honest partial recall leaves the vault with less idle than the snapshot implies, so pass 2 allocates from the idle it actually holds and, when that runs out, later strategies in registration order wait for the next rebalance.

function rebalance() internal;

_rebalance

Inner rebalance logic (called after reentrancy lock is acquired).

function _rebalance() private;

_recall

Recalls requested from strategy (which reported current) and checks it by value, not by the amount asked (H-3, #221). received is the vault's actual idle delta and released the drop in the strategy's reported balance. Reverts with IStrategyManager.StrategyManagerWithdrawShortfall when released exceeds received by more than REBALANCE_SHORTFALL_TOLERANCE: the recall lost value (slippage, an exit fee, or a strategy writing off more than it paid), which a permissionless caller must not be able to realize. A recall that delivers less than asked while the strategy still reports the remainder (the Lido buffer, Aave/Compound short of cash, a UniswapV3 position) is an honest partial recall: it completes, emits IStrategyManager.StrategyPartiallyRecalled, and the strategy stays over target until a later rebalance.

function _recall(address strategy, uint256 current, uint256 requested, address vaultAddr, address asset_) private;

Parameters

NameTypeDescription
strategyaddress
currentuint256
requesteduint256
vaultAddraddress
asset_address

_deployIdle

Calls deploy() on strategy when it holds idle asset_ and advertises IProtocolAdapter through ERC-165, so allocations do not sit idle in the adapter (#221; the adapters only accept deploy from their operator, this manager). Plain IStrategys are skipped. A reverting deploy (paused adapter or protocol, a supply cap, an operator not yet wired) is caught and reported with IStrategyManager.StrategyDeployFailed: the idle stays in the adapter, counted in its NAV, and never blocks the rebalance.

function _deployIdle(address strategy, address asset_) private;

Parameters

NameTypeDescription
strategyaddress
asset_address