StrategyManagerLib
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 callsIVaultCore.allocateToStrategy(vault pushes excess) andIStrategy.withdraw(strategy pushes back to vault) as needed, thenIProtocolAdapter.deployso an adapter's idle does not stay undeployed.
Functions
strategyManagerStorage
function strategyManagerStorage() internal pure returns (StrategyManagerStorage storage $);Returns
| Name | Type | Description |
|---|---|---|
| $ | 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
| Name | Type | Description |
|---|---|---|
| <none> | address |
getStrategies
Returns all registered strategy addresses.
function getStrategies() internal view returns (address[] memory);Returns
| Name | Type | Description |
|---|---|---|
| <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
| Name | Type | Description |
|---|---|---|
| strategy | address |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint16 |
totalTargetBps
Returns the current sum of all target allocations in basis points.
function totalTargetBps() internal view returns (uint256);Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint256 |
depositsLatched
Returns true while a force removal keeps the vault's deposits closed (see clearDepositLatch).
function depositsLatched() internal view returns (bool);Returns
| Name | Type | Description |
|---|---|---|
| <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
| Name | Type | Description |
|---|---|---|
| total | uint256 |
setVault
Sets the vault address. Admin-only (DEFAULT_ADMIN_ROLE).
function setVault(address _vault) internal;Parameters
| Name | Type | Description |
|---|---|---|
| _vault | address | Address of the ERC-4626 vault this manager serves. |
_setVault
Inner logic for setVault (no auth check).
function _setVault(address _vault) internal;Parameters
| Name | Type | Description |
|---|---|---|
| _vault | address |
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
| Name | Type | Description |
|---|---|---|
| strategy | address | Strategy contract address. |
| targetBps | uint16 | Target allocation in basis points (0–10 000). |
_addStrategy
Inner logic for addStrategy.
function _addStrategy(address strategy, uint16 targetBps) internal;Parameters
| Name | Type | Description |
|---|---|---|
| strategy | address | |
| targetBps | uint16 |
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
| Name | Type | Description |
|---|---|---|
| strategy | address | Strategy 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
| Name | Type | Description |
|---|---|---|
| strategy | address |
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
| Name | Type | Description |
|---|---|---|
| strategy | address | Registered strategy address. |
| newBps | uint16 | New target allocation in basis points. |
_updateStrategyTarget
Inner logic for updateStrategyTarget.
function _updateStrategyTarget(address strategy, uint16 newBps) internal;Parameters
| Name | Type | Description |
|---|---|---|
| strategy | address | |
| newBps | uint16 |
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
| Name | Type | Description |
|---|---|---|
| strategy | address | |
| current | uint256 | |
| requested | uint256 | |
| vaultAddr | address | |
| 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
| Name | Type | Description |
|---|---|---|
| strategy | address | |
| asset_ | address |