Skip to content
Lattice

VaultCoreLib

Git Source

Title: VaultCoreLib

Authors: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80), Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol)

Library extending ERC-4626 vaults with strategy hooks for yield aggregation.

All logic lives here; the VaultCore facet is a pure delegator. Storage is accessed via ERC-7201 namespaced slot to avoid collisions in the Diamond proxy.

Architecture:

  • The vault holds "idle" assets (its own ERC-20 balance of the underlying).
  • When a StrategyManager is configured, it can direct the vault to PUSH assets to external strategies via allocateToStrategy. The strategy later PUSHES back via its own withdraw call.
  • totalAssets() is overridden to include both idle and allocated assets. ERC4626Lib prices shares by self-staticcalling the diamond's totalAssets(), so conversions, previews and mutators all use this full NAV, while maxWithdraw/maxRedeem stay capped at idle assets.
  • After a strategy force removal the manager latches deposits closed (depositsLatched): deposit/mint revert and maxDeposit/maxMint return 0 until the manager admin clears the latch (#270).
  • A strategy-manager swap that may strand funds (the old manager is latched, still reports allocations, or cannot answer) latches deposits on the vault itself until the vault admin clears it (#305).

Functions

vaultCoreStorage

function vaultCoreStorage() internal pure returns (VaultCoreStorage storage $);

Returns

NameTypeDescription
$VaultCoreStorage

vaultCoreRecoveryStorage

function vaultCoreRecoveryStorage() internal pure returns (VaultCoreRecoveryStorage storage $);

Returns

NameTypeDescription
$VaultCoreRecoveryStorage

__VaultCore_init

Initializes the VaultCore module.

Must be called inside a pre/postInitializer block, after ERC4626Lib.__ERC4626_init. AccessControl must already be initialized to support the DEFAULT_ADMIN_ROLE check.

function __VaultCore_init() internal;

registerInterfaces

Registers support for the IVaultCore and IVaultCoreRecovery interfaces via ERC-165.

function registerInterfaces() internal;

strategyManager

Returns the configured strategy manager address, or address(0) if not set.

function strategyManager() internal view returns (address);

Returns

NameTypeDescription
<none>address

idleAssets

Returns the vault's current idle asset balance.

Defined as the ERC-20 balance of the underlying asset held by this contract.

function idleAssets() internal view returns (uint256);

Returns

NameTypeDescription
<none>uint256

totalAssets

Returns the total assets held by the vault, including strategy allocations.

Backs the diamond's totalAssets() selector (replacing ERC4626Lib.totalAssets), which ERC4626Lib reads for all share pricing. When a manager is set, adds the manager's totalAllocated() view (which sums each strategy's self-reported balance). Trust assumption: strategy balance reports are accurate. Fails closed: if the manager read reverts or returns malformed data, this reverts with IVaultCore.VaultCoreStrategyNavUnavailable rather than under-reporting the NAV as idle, which would let deposits mint shares cheaply and exits redeem at a discount. allocatedAssets() reverts too.

function totalAssets() internal view returns (uint256);

Returns

NameTypeDescription
<none>uint256

allocatedAssets

Returns total assets allocated to strategies (totalAssets - idleAssets).

function allocatedAssets() internal view returns (uint256);

Returns

NameTypeDescription
<none>uint256

setStrategyManager

Sets the strategy manager address. Restricted to DEFAULT_ADMIN_ROLE.

Latches deposits on the vault when the swap may strand funds (see _setStrategyManager).

function setStrategyManager(address manager) internal;

Parameters

NameTypeDescription
manageraddressThe new strategy manager address.

_setStrategyManager

Inner logic for setStrategyManager (no auth check — auth in outer). Swapping away from a manager drops its strategies from the NAV and its own deposit latch with it, so funds those strategies return later would move value to whoever deposited after the swap (#305). Unless the previous manager verifiably releases cleanly (_releasesCleanly), the swap sets the vault-side latch and emits IVaultCoreRecovery.ManagerSwapLatchSet. The reads never revert the swap: it is the vault admin's last-resort recovery when the old manager's NAV read is broken. Setting the first manager, or the same manager again, strands nothing and latches nothing.

function _setStrategyManager(address manager) internal;

Parameters

NameTypeDescription
manageraddress

clearManagerSwapLatch

Clears the manager-swap latch, reopening deposits unless the configured manager's own latch is set. Restricted to DEFAULT_ADMIN_ROLE (the role that gates setStrategyManager).

function clearManagerSwapLatch() internal;

_clearManagerSwapLatch

Inner logic for clearManagerSwapLatch (no auth check).

function _clearManagerSwapLatch() internal;

allocateToStrategy

Transfers idle assets to a strategy. Only callable by the strategy manager.

Empty return data counts as success only when the asset has code (matches OpenZeppelin SafeERC20).

function allocateToStrategy(address strategy, uint256 amount) internal;

Parameters

NameTypeDescription
strategyaddressDestination strategy address.
amountuint256Amount of underlying asset to transfer.

recallFromStrategy

Acknowledges a recall event from a strategy.

The strategy itself is responsible for pushing assets back to the vault. This function exists to emit the event and allow the StrategyManager to coordinate the accounting.

function recallFromStrategy(address strategy, uint256 amount) internal;

Parameters

NameTypeDescription
strategyaddressSource strategy address.
amountuint256Amount expected to be returned by the strategy.

_checkManager

Reverts with VaultCoreUnauthorizedManager if the caller is not the strategy manager.

function _checkManager() internal view;

requireManagerNotRebalancing

Reverts if the configured strategy manager is currently executing rebalance().

During a rebalance the vault's idle balance and the strategies' reported balances are transiently inconsistent, so totalAssets() (and therefore the share price) cannot be trusted. The vault's deposit/mint/withdraw/redeem entry points call this to reject read-only reentrancy via a strategy callback. The manager is an external contract, so we query its reentrancy status by staticcall — mirroring how totalAssets() reads it.

function requireManagerNotRebalancing() internal view;

depositsLatched

Returns true while deposits are latched closed, by the vault (managerSwapLatched) or by the configured strategy manager (managerDepositsLatched).

function depositsLatched() internal view returns (bool);

Returns

NameTypeDescription
<none>bool

managerSwapLatched

Returns true while a strategy-manager swap keeps the vault's deposits closed (#305).

function managerSwapLatched() internal view returns (bool);

Returns

NameTypeDescription
<none>bool

managerDepositsLatched

Returns true while the configured strategy manager latches deposits closed.

Reads the manager's depositsLatched() (IStrategyManagerRecovery), which a strategy force removal sets (#270). No manager, or a manager whose read fails or returns short data (one without the latch selector), counts as unlatched, mirroring requireManagerNotRebalancing. Any nonzero word counts as latched, so a malformed answer keeps deposits closed rather than reverting the views. A swap away from such a manager is stricter: there a failed read latches (_releasesCleanly).

function managerDepositsLatched() internal view returns (bool);

Returns

NameTypeDescription
<none>bool

requireDepositsOpen

Reverts with IVaultCore.VaultCoreDepositsLatched while deposits are latched closed.

Called by the deposit/mint entry points. A force removal or a manager swap drops funds from the NAV, so a depositor entering at the lower NAV would capture part of any funds that later return; the latch keeps entries closed until an admin clears it. The error names the latch holder whose clear is due: this vault for the manager-swap latch (checked first), else the strategy manager. Exits are not gated.

function requireDepositsOpen() internal view;

_releasesCleanly

True only when manager verifiably strands nothing on a swap: both its depositsLatched() and its totalAllocated() read succeed with a full word, and they report false and 0. Any other answer, including a revert, short return data or running out of gas, returns false, so the swap latches. The old manager cannot then show that its strategies, or the strategies it force-removed, hold nothing. On the last-resort route (a broken NAV read) the old manager still reports allocations or its own sum overflows, so it latches either way. A false latch costs one admin call; a missed one reopens the #270 donation capture.

function _releasesCleanly(address manager) private view returns (bool);

Parameters

NameTypeDescription
manageraddress

Returns

NameTypeDescription
<none>bool

_readWord

Staticcalls selector on target and returns the first word of its answer. ok is false when the call reverts or returns fewer than 32 bytes. At most 32 bytes of return data are copied, so an oversized answer cannot exhaust the caller's gas.

function _readWord(address target, bytes4 selector) private view returns (bool ok, uint256 word);

Parameters

NameTypeDescription
targetaddress
selectorbytes4

Returns

NameTypeDescription
okbool
worduint256