ConcretePredepositVaultImpl

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/implementation/ConcretePredepositVaultImpl.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {ConcreteStandardVaultImpl} from "./ConcreteStandardVaultImpl.sol";
import {IConcreteStandardVaultImpl} from "../interface/IConcreteStandardVaultImpl.sol";
import {IConcretePredepositVaultImpl} from "../interface/IConcretePredepositVaultImpl.sol";
import {
    ConcretePredepositVaultImplStorageLib as PDVLib
} from "../lib/storage/ConcretePredepositVaultImplStorageLib.sol";
import {ConcreteV2RolesLib as RolesLib} from "../lib/Roles.sol";
import {IPredepostVaultOApp} from "../periphery/interface/IPredepostVaultOApp.sol";
import {ConcreteV2ConversionLib as ConversionLib} from "../lib/Conversion.sol";
import {Math} from "@openzeppelin-contracts/utils/math/Math.sol";
import {
    ConcreteCachedVaultStateStorageLib as CachedVaultStateLib
} from "../lib/storage/ConcreteCachedVaultStateStorageLib.sol";

/**
 * @title ConcretePredepositVaultImpl
 * @notice A vault implementation that extends ConcreteStandardVaultImpl with cross-chain share claiming via a standalone OApp.
 * @dev This is useful for pre-deposit phases where users can claim their shares on a different chain.
 *      Use setWithdrawLimits(0, 0) to disable withdrawals during the predeposit phase.
 *      Claims can only occur when cachedTotalAssets() == 0 (all assets allocated away).
 *      The vault uses a separate OApp contract for cross-chain messaging.
 *
 * @custom:warning IMPORTANT: Assets deposited into this vault are intended to be bridged to a remote chain.
 *                 Users MUST have custody/control of their address on the destination chain to receive shares.
 *                 Shares are sent to the same address on the remote chain - ensure you control this address
 *                 before depositing or claiming. Loss of custody on the destination chain means loss of funds.
 */
contract ConcretePredepositVaultImpl is ConcreteStandardVaultImpl, IConcretePredepositVaultImpl {
    using ConversionLib for uint256;

    // Message type identifier for cross-chain claims
    uint16 public constant MSG_TYPE_CLAIM = 1;
    uint16 public constant MSG_TYPE_BATCH_CLAIM = 2;

    /// @notice Event emitted when OApp address is set
    event OAppSet(address indexed oapp);

    /**
     * @dev Constructor
     * @param factory The address of the factory
     */
    constructor(address factory) ConcreteStandardVaultImpl(factory) {}

    /**
     * @dev Initialization function that will be called when a proxy vault is deployed through `ConcreteFactory`.
     * @param initialVersion The initial version of the vault
     * @param owner The owner of the vault
     * @param data Encoded initialization data (allocateModule, asset, initialVaultManager, name, symbol)
     */
    function _initialize(uint64 initialVersion, address owner, bytes memory data) internal virtual override {
        (
            address allocateModuleAddr,
            address asset,
            address initialVaultManager,
            string memory name,
            string memory symbol
        ) = abi.decode(data, (address, address, address, string, string));

        // Call parent initialization
        super._initialize(
            initialVersion, owner, abi.encode(allocateModuleAddr, asset, initialVaultManager, name, symbol)
        );

        // Initialize self claims setting to false (can be enabled via setSelfClaimsEnabled)
        PDVLib.ConcretePredepositVaultImplStorage storage $ = PDVLib.fetch();
        $.selfClaimsEnabled = false;
    }

    /// @inheritdoc IConcretePredepositVaultImpl
    function claimOnTargetChain(bytes calldata options) external payable nonReentrant withYieldAccrual {
        PDVLib.ConcretePredepositVaultImplStorage storage $ = PDVLib.fetch();

        // Ensure self claims are enabled
        require($.selfClaimsEnabled, SelfClaimsDisabled());

        _validateClaimConditions($);

        // Get user's current share balance
        uint256 userShares = balanceOf(msg.sender);
        require(userShares != 0, NoSharesToClaim());

        // decrease cached totalAssets proportionally to the user's shares to maintain the share price
        uint256 assets = userShares.calcConvertToAssets(totalSupply(), cachedTotalAssets(), Math.Rounding.Floor, false);
        CachedVaultStateLib.fetch().cachedTotalAssets = cachedTotalAssets() - assets;

        _burn(msg.sender, userShares);

        // Store locked shares
        $.lockedShares[msg.sender] += userShares;

        bytes memory payload = abi.encode(MSG_TYPE_CLAIM, msg.sender, userShares);

        // Send the message via the OApp (quote and fee validation done internally)
        IPredepostVaultOApp($.oapp).send{value: msg.value}(payload, options, msg.sender);

        emit SharesClaimedOnTargetChain(msg.sender, userShares);
    }

    /// @inheritdoc IConcretePredepositVaultImpl
    function batchClaimOnTargetChain(bytes calldata addressesData, bytes calldata options)
        external
        payable
        nonReentrant
        withYieldAccrual
        onlyRole(RolesLib.VAULT_MANAGER)
    {
        PDVLib.ConcretePredepositVaultImplStorage storage $ = PDVLib.fetch();

        _validateClaimConditions($);

        // Decode addresses array
        address[] memory addresses = abi.decode(addressesData, (address[]));
        require(addresses.length > 0 && addresses.length <= 150, BadAddressArrayLength(addresses.length));

        uint256[] memory sharesArray = new uint256[](addresses.length);
        uint256 totalShares = 0;

        for (uint256 i = 0; i < addresses.length; i++) {
            address user = addresses[i];
            require(user != address(0), InvalidUserAddress());

            uint256 userShares = balanceOf(user);
            if (userShares == 0) continue; // Skip users with no shares, already claimed, duplicates in list

            // decrease cached totalAssets proportionally to the user's shares to maintain the share price
            uint256 assets =
                userShares.calcConvertToAssets(totalSupply(), cachedTotalAssets(), Math.Rounding.Floor, false);
            CachedVaultStateLib.fetch().cachedTotalAssets = cachedTotalAssets() - assets;

            _burn(user, userShares);

            // Store locked shares
            $.lockedShares[user] += userShares;

            // Store in batch arrays
            sharesArray[i] = userShares;
            totalShares += userShares;

            emit SharesClaimedOnTargetChain(user, userShares);
        }

        require(totalShares > 0, NoSharesInBatch());

        bytes memory payload = abi.encode(MSG_TYPE_BATCH_CLAIM, addresses, sharesArray);

        // Send the message via the OApp (quote and fee validation done internally)
        IPredepostVaultOApp($.oapp).send{value: msg.value}(payload, options, msg.sender);
    }

    /// @inheritdoc IConcretePredepositVaultImpl
    function getLockedShares(address user) external view returns (uint256) {
        return PDVLib.fetch().lockedShares[user];
    }

    /// @inheritdoc IConcretePredepositVaultImpl
    function setSelfClaimsEnabled(bool enabled) external onlyRole(RolesLib.VAULT_MANAGER) {
        PDVLib.ConcretePredepositVaultImplStorage storage $ = PDVLib.fetch();
        $.selfClaimsEnabled = enabled;

        emit SelfClaimsEnabledUpdated(enabled);
    }

    /// @inheritdoc IConcretePredepositVaultImpl
    function getSelfClaimsEnabled() external view returns (bool) {
        return PDVLib.fetch().selfClaimsEnabled;
    }

    /// @inheritdoc IConcretePredepositVaultImpl
    function setOApp(address oappAddress) external onlyRole(RolesLib.VAULT_MANAGER) {
        PDVLib.ConcretePredepositVaultImplStorage storage $ = PDVLib.fetch();
        $.oapp = oappAddress;

        emit OAppSet(oappAddress);
    }

    /// @inheritdoc IConcretePredepositVaultImpl
    function getOApp() external view returns (address) {
        return PDVLib.fetch().oapp;
    }

    /**
     * @dev Upgrade function that handles migration from ConcreteStandardVaultImpl to ConcretePredepositVaultImpl
     * @dev Sets selfClaimsEnabled to false by default (can be enabled via setSelfClaimsEnabled)
     */
    function _upgrade(
        uint64,
        /* oldVersion */
        uint64,
        /* newVersion */
        bytes calldata /* data */
    )
        internal
        virtual
        override
    {
        // Initialize self claims setting to false (can be enabled via setSelfClaimsEnabled)
        PDVLib.ConcretePredepositVaultImplStorage storage $ = PDVLib.fetch();
        $.selfClaimsEnabled = false;
    }

    /**
     * @dev Internal function to validate claim conditions
     * @param $ Storage reference to ConcretePredepositVaultImplStorage
     */
    function _validateClaimConditions(PDVLib.ConcretePredepositVaultImplStorage storage $) internal view {
        // Ensure OApp is set
        require($.oapp != address(0), OAppNotSet());

        // Ensure deposits are locked
        (uint256 maxDepositAmount,) = getDepositLimits();
        require(maxDepositAmount == 0, DepositsNotLocked());

        // Ensure withdrawals are locked
        (uint256 maxWithdrawAmount,) = getWithdrawLimits();
        require(maxWithdrawAmount == 0, WithdrawalsNotLocked());
    }
}
"
    },
    "src/implementation/ConcreteStandardVaultImpl.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.24;

/**
 * @title ConcreteStandardVaultImpl
 * @notice ERC-4626 upgradeable standard vault implementation for the Concrete Earn V2 protocol.
 *         Holds an underlying ERC20 asset and exposes deposit/mint/withdraw/redeem flows.
 *         Integrates with strategy modules to route assets to yield sources.
 *
 * @author Blueprint Finance
 * @custom:protocol Concrete Earn V2
 * @custom:oz-upgrades Use OZ Upgradeable patterns and eip7201 storage layout
 * @custom:source on request
 * @custom:audits on request
 * @custom:license AGPL-3.0
 */

// ─────────────────────────────────────────────────────────────────────────────
// External dependencies
// ─────────────────────────────────────────────────────────────────────────────
import {
    AccessControlEnumerableUpgradeable
} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
import {
    ERC4626Upgradeable,
    IERC20,
    IERC4626,
    Math,
    SafeERC20
} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {Address} from "@openzeppelin-contracts/utils/Address.sol";
import {EnumerableSet} from "@openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import {Ownable} from "@openzeppelin-contracts/access/Ownable.sol";
import {SafeCast} from "@openzeppelin-contracts/utils/math/SafeCast.sol";

// ─────────────────────────────────────────────────────────────────────────────
// Protocol-facing interfaces
// ─────────────────────────────────────────────────────────────────────────────
import {IConcreteStandardVaultImpl} from "../interface/IConcreteStandardVaultImpl.sol";
import {IStrategyTemplate} from "../interface/IStrategyTemplate.sol";

// ─────────────────────────────────────────────────────────────────────────────
// Internal modules/contracts
// ─────────────────────────────────────────────────────────────────────────────
import {IAllocateModule} from "../module/AllocateModule.sol";
import {UpgradeableVault} from "../common/UpgradeableVault.sol";

// ─────────────────────────────────────────────────────────────────────────────
// Internal libraries
// ─────────────────────────────────────────────────────────────────────────────
import {ConcreteV2ConstantsLib as ConstantsLib} from "../lib/Constants.sol";
import {ConcreteV2ConversionLib as ConversionLib} from "../lib/Conversion.sol";
import {Hooks, HooksLibV1 as HooksLib} from "../lib/Hooks.sol";
import {ConcreteV2RolesLib as RolesLib} from "../lib/Roles.sol";
import {StateInitLib} from "../lib/StateInitLib.sol";
import {StateSetterLib} from "../lib/StateSetterLib.sol";
import {Time} from "../lib/Time.sol";

// ─────────────────────────────────────────────────────────────────────────────
// Storage layout libraries
// ─────────────────────────────────────────────────────────────────────────────
import {
    ConcreteCachedVaultStateStorageLib as CachedVaultStateLib
} from "../lib/storage/ConcreteCachedVaultStateStorageLib.sol";
import {ConcreteStandardVaultImplStorageLib as SVLib} from "../lib/storage/ConcreteStandardVaultImplStorageLib.sol";

contract ConcreteStandardVaultImpl is
    ERC4626Upgradeable,
    UpgradeableVault,
    AccessControlEnumerableUpgradeable,
    IConcreteStandardVaultImpl
{
    using Address for address;
    using EnumerableSet for EnumerableSet.AddressSet;
    using SafeCast for uint256;
    using ConversionLib for uint256;

    using HooksLib for Hooks;

    event HooksSet(Hooks hooks);

    /// @dev Modifier to accrue yield before function execution
    modifier withYieldAccrual() {
        _accrueYield();
        _;
    }
    /**
     * @dev Constructor
     * @param factory The address of the factory
     */

    constructor(address factory) UpgradeableVault(factory) {}

    /// @inheritdoc IConcreteStandardVaultImpl
    function allocate(bytes calldata data) public virtual nonReentrant onlyRole(RolesLib.ALLOCATOR) withYieldAccrual {
        // delegatecall allocate module
        bytes memory delegateData = abi.encodeWithSelector(IAllocateModule.allocateFunds.selector, data);
        allocateModule().functionDelegateCall(delegateData);
        require(IERC20(asset()).balanceOf(address(this)) >= _lockedAssets(), InsufficientBalance());
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function accrueYield() external virtual nonReentrant {
        _accrueYield();
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function updateManagementFee(uint16 managementFee_)
        external
        nonReentrant
        onlyRole(RolesLib.VAULT_MANAGER)
        withYieldAccrual
    {
        StateSetterLib.updateManagementFee(managementFee_);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function updateManagementFeeRecipient(address recipient) external nonReentrant withYieldAccrual {
        require(_msgSender() == Ownable(factory).owner(), InvalidFactoryOwner());

        StateSetterLib.updateManagementFeeRecipient(recipient);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function updatePerformanceFee(uint16 performanceFee_)
        external
        nonReentrant
        onlyRole(RolesLib.VAULT_MANAGER)
        withYieldAccrual
    {
        StateSetterLib.updatePerformanceFee(performanceFee_);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function updatePerformanceFeeRecipient(address recipient) external nonReentrant withYieldAccrual {
        require(msg.sender == Ownable(factory).owner(), InvalidFactoryOwner());

        StateSetterLib.updatePerformanceFeeRecipient(recipient);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function setDepositLimits(uint256 minDepositAmount, uint256 maxDepositAmount)
        external
        nonReentrant
        onlyRole(RolesLib.VAULT_MANAGER)
    {
        StateSetterLib.setDepositLimits(minDepositAmount, maxDepositAmount);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function setWithdrawLimits(uint256 minWithdrawAmount, uint256 maxWithdrawAmount)
        external
        nonReentrant
        onlyRole(RolesLib.VAULT_MANAGER)
    {
        StateSetterLib.setWithdrawLimits(minWithdrawAmount, maxWithdrawAmount);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function getFeeConfig()
        external
        view
        override
        returns (
            uint16 currentManagementFee,
            address currentManagementFeeRecipient,
            uint32 currentLastManagementFeeAccrual,
            uint16 currentPerformanceFee,
            address currentPerformanceFeeRecipient
        )
    {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        return (
            $.managementFee,
            $.managementFeeRecipient,
            $.lastManagementFeeAccrual,
            $.performanceFee,
            $.performanceFeeRecipient
        );
    }

    /**
     * @inheritdoc IERC4626
     */
    function deposit(uint256 assets, address receiver)
        public
        virtual
        override(IERC4626, ERC4626Upgradeable)
        nonReentrant
        withYieldAccrual
        returns (uint256)
    {
        require(receiver != address(0), InvalidReceiver());

        Hooks memory h = SVLib.fetch().hooks;
        uint256 totalAssetsBeforeDeposit = cachedTotalAssets();
        // invoke pre-deposit hook if enabled
        if (h.checkIsValid(HooksLib.PRE_DEPOSIT)) {
            h.preDeposit(_msgSender(), assets, receiver, totalAssetsBeforeDeposit);
        }

        // deposit assets
        uint256 maxAssets = maxDeposit(receiver);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
        }

        uint256 shares = assets.calcConvertToShares(totalSupply(), totalAssetsBeforeDeposit, Math.Rounding.Floor, true);

        (uint256 maxDepositAmount, uint256 minDepositAmount) = getDepositLimits();
        require(
            assets + totalAssetsBeforeDeposit <= maxDepositAmount && assets >= minDepositAmount,
            AssetAmountOutOfBounds(msg.sender, assets, minDepositAmount, maxDepositAmount)
        );

        _deposit(_msgSender(), receiver, assets, shares);

        // invoke post-deposit hook if enabled
        if (h.checkIsValid(HooksLib.POST_DEPOSIT)) {
            h.postDeposit(_msgSender(), assets, shares, receiver, cachedTotalAssets());
        }

        return shares;
    }

    /**
     * @inheritdoc IERC4626
     */
    function mint(uint256 shares, address receiver)
        public
        virtual
        override(IERC4626, ERC4626Upgradeable)
        nonReentrant
        withYieldAccrual
        returns (uint256)
    {
        require(receiver != address(0), InvalidReceiver());

        Hooks memory h = SVLib.fetch().hooks;
        uint256 totalAssetsBeforeDeposit = cachedTotalAssets();
        // invoke pre-mint hook if enabled
        if (h.checkIsValid(HooksLib.PRE_MINT)) h.preMint(_msgSender(), shares, receiver, totalAssetsBeforeDeposit);

        uint256 maxShares = maxMint(receiver);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxMint(receiver, shares, maxShares);
        }

        uint256 assets = shares.calcConvertToAssets(totalSupply(), totalAssetsBeforeDeposit, Math.Rounding.Ceil, true);

        (uint256 maxDepositAmount, uint256 minDepositAmount) = getDepositLimits();
        require(
            assets + totalAssetsBeforeDeposit <= maxDepositAmount && assets >= minDepositAmount,
            AssetAmountOutOfBounds(_msgSender(), assets, minDepositAmount, maxDepositAmount)
        );

        _deposit(_msgSender(), receiver, assets, shares);

        // invoke post-mint hook if enabled
        if (h.checkIsValid(HooksLib.POST_MINT)) {
            h.postMint(_msgSender(), assets, shares, receiver, cachedTotalAssets());
        }

        return assets;
    }

    /**
     * @inheritdoc IERC4626
     */
    function withdraw(uint256 assets, address receiver, address owner)
        public
        virtual
        override(IERC4626, ERC4626Upgradeable)
        nonReentrant
        withYieldAccrual
        returns (uint256)
    {
        require(receiver != address(0), InvalidReceiver());

        Hooks memory h = SVLib.fetch().hooks;
        uint256 totalAssetsBeforeWithdrawal = cachedTotalAssets();

        if (h.checkIsValid(HooksLib.PRE_WITHDRAW)) {
            h.preWithdraw(_msgSender(), assets, receiver, owner, totalAssetsBeforeWithdrawal);
        }
        // Optimistic maxAssets that does not account for the withdrawals from strategies, this is to avoid the need to call _simulateWithdraw().
        // If maxAssets is greater than the actual withdrawable amount, _executeWithdraw() will revert.
        uint256 totalSupply_ = totalSupply();
        uint256 maxAssets =
            balanceOf(owner).calcConvertToAssets(totalSupply_, totalAssetsBeforeWithdrawal, Math.Rounding.Floor, false);

        uint256 shares = assets.calcConvertToShares(totalSupply_, totalAssetsBeforeWithdrawal, Math.Rounding.Ceil, true);
        if (assets > maxAssets || _executeWithdraw(_msgSender(), receiver, owner, assets, shares) < assets) {
            revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
        }

        (uint256 maxWithdrawAmount, uint256 minWithdrawAmount) = getWithdrawLimits();
        require(
            assets <= maxWithdrawAmount && assets >= minWithdrawAmount,
            AssetAmountOutOfBounds(_msgSender(), assets, minWithdrawAmount, maxWithdrawAmount)
        );

        // invoke post-withdraw hook if enabled
        if (h.checkIsValid(HooksLib.POST_WITHDRAW)) {
            h.postWithdraw(_msgSender(), assets, shares, receiver, cachedTotalAssets());
        }

        return shares;
    }

    /**
     * @inheritdoc IERC4626
     */
    function redeem(uint256 shares, address receiver, address owner)
        public
        virtual
        override(IERC4626, ERC4626Upgradeable)
        nonReentrant
        withYieldAccrual
        returns (uint256)
    {
        require(receiver != address(0), InvalidReceiver());

        Hooks memory h = SVLib.fetch().hooks;
        uint256 totalAssetsBeforeWithdrawal = cachedTotalAssets();

        // invoke pre-redeem hook if enabled
        if (h.checkIsValid(HooksLib.PRE_REDEEM)) {
            h.preRedeem(_msgSender(), shares, receiver, owner, totalAssetsBeforeWithdrawal);
        }

        // Optimistic maxShares that does not account for the withdrawals from strategies, this is to avoid the need to call _simulateWithdraw().
        // If maxShares is greater than the actual redeemable amount, _executeWithdraw() will revert.
        uint256 maxShares = balanceOf(owner);

        uint256 assets =
            shares.calcConvertToAssets(totalSupply(), totalAssetsBeforeWithdrawal, Math.Rounding.Floor, true);

        if (shares > maxShares || _executeWithdraw(_msgSender(), receiver, owner, assets, shares) < assets) {
            revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
        }

        (uint256 maxWithdrawAmount, uint256 minWithdrawAmount) = getWithdrawLimits();
        require(
            assets <= maxWithdrawAmount && assets >= minWithdrawAmount,
            AssetAmountOutOfBounds(_msgSender(), assets, minWithdrawAmount, maxWithdrawAmount)
        );

        // invoke post-redeem hook if enabled
        if (h.checkIsValid(HooksLib.POST_REDEEM)) {
            h.postRedeem(_msgSender(), assets, shares, receiver, cachedTotalAssets());
        }

        return assets;
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function setHooks(Hooks memory hooks) external virtual nonReentrant onlyRole(RolesLib.HOOK_MANAGER) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        $.hooks = hooks;

        emit HooksSet(hooks);
    }

    /**
     * @notice overwrites the deallocation order from strategies;
     */
    function setDeallocationOrder(address[] calldata order) external virtual nonReentrant onlyRole(RolesLib.ALLOCATOR) {
        StateSetterLib.setDeallocationOrder(order);
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function addStrategy(address strategy) public virtual nonReentrant onlyRole(RolesLib.STRATEGY_MANAGER) {
        require(IStrategyTemplate(strategy).asset() == asset(), InvalidStrategyAsset());
        StateSetterLib.addStrategy(strategy);
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function removeStrategy(address strategy) public virtual nonReentrant onlyRole(RolesLib.STRATEGY_MANAGER) {
        StateSetterLib.removeStrategy(strategy);
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function toggleStrategyStatus(address strategy) public virtual nonReentrant onlyRole(RolesLib.STRATEGY_MANAGER) {
        StateSetterLib.toggleStrategyStatus(strategy);
    }

    /**
     * @notice Returns the deallocation order from strategies.
     */
    function getDeallocationOrder() external view returns (address[] memory order) {
        return SVLib.fetch().deallocationOrder;
    }

    /**
     * @dev See {IERC4626-previewDeposit}.
     */
    function previewDeposit(uint256 assets)
        public
        view
        virtual
        override(IERC4626, ERC4626Upgradeable)
        returns (uint256)
    {
        (uint256 totalAssetsPreview, uint256 totalSupply) = _previewAccrueYieldAndFees();
        return assets.calcConvertToShares(totalSupply, totalAssetsPreview, Math.Rounding.Floor, false);
    }

    /**
     * @inheritdoc IERC4626
     */
    function previewMint(uint256 shares) public view virtual override(IERC4626, ERC4626Upgradeable) returns (uint256) {
        (uint256 totalAssetsPreview, uint256 totalSupply) = _previewAccrueYieldAndFees();

        return shares.calcConvertToAssets(totalSupply, totalAssetsPreview, Math.Rounding.Ceil, false);
    }

    /**
     * @inheritdoc IERC4626
     */
    function previewWithdraw(uint256 assets)
        public
        view
        virtual
        override(IERC4626, ERC4626Upgradeable)
        returns (uint256)
    {
        (uint256 totalAssetsPreview, uint256 totalSupply) = _previewAccrueYieldAndFees();

        return assets.calcConvertToShares(totalSupply, totalAssetsPreview, Math.Rounding.Ceil, false);
    }

    /**
     * @inheritdoc IERC4626
     */
    function previewRedeem(uint256 shares)
        public
        view
        virtual
        override(IERC4626, ERC4626Upgradeable)
        returns (uint256)
    {
        (uint256 totalAssetsPreview, uint256 totalSupply) = _previewAccrueYieldAndFees();

        return shares.calcConvertToAssets(totalSupply, totalAssetsPreview, Math.Rounding.Floor, false);
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function previewAccrueYield() public view virtual returns (uint256, uint256) {
        return _previewAccrueYieldAndFees();
    }

    /**
     * @inheritdoc IERC4626
     */
    function totalAssets()
        public
        view
        virtual
        override(IERC4626, ERC4626Upgradeable)
        returns (uint256 totalAssetsWithYield)
    {
        (totalAssetsWithYield,) = _previewAccrueYieldAndFees();
    }

    /**
     * @inheritdoc IERC4626
     */
    function maxRedeem(address owner) public view virtual override(IERC4626, ERC4626Upgradeable) returns (uint256) {
        (uint256 maxAssets, uint256 expectedTotalAssets, uint256 expectedTotalSupply) = _maxWithdraw(owner);

        return maxAssets.calcConvertToShares(expectedTotalSupply, expectedTotalAssets, Math.Rounding.Floor, false);
    }

    /**
     * @inheritdoc IERC4626
     */
    function maxWithdraw(address owner)
        public
        view
        virtual
        override(IERC4626, ERC4626Upgradeable)
        returns (uint256 maxAssets)
    {
        (maxAssets,,) = _maxWithdraw(owner);
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function getStrategyData(address strategy) public view returns (StrategyData memory) {
        return SVLib.fetch().strategyData[strategy];
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function getStrategies() public view returns (address[] memory) {
        return SVLib.fetch().strategies.values();
    }

    /**
     * @inheritdoc IConcreteStandardVaultImpl
     */
    function allocateModule() public view returns (address) {
        return SVLib.fetch().allocateModule;
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function getDepositLimits() public view returns (uint256, uint256) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();
        return ($.maxDepositAmount, $.minDepositAmount);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function getWithdrawLimits() public view returns (uint256, uint256) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();
        return ($.maxWithdrawAmount, $.minWithdrawAmount);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function managementFee() public view returns (address, uint16, uint32) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();
        return ($.managementFeeRecipient, $.managementFee, $.lastManagementFeeAccrual);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function performanceFee() public view returns (address, uint16) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();
        return ($.performanceFeeRecipient, $.performanceFee);
    }

    /// @inheritdoc IConcreteStandardVaultImpl
    function getTotalAllocated() public view returns (uint256 totalAllocated) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        address[] memory strategies = $.strategies.values();

        for (uint256 i = 0; i < strategies.length; i++) {
            totalAllocated += $.strategyData[strategies[i]].allocated;
        }
    }

    function cachedTotalAssets() public view returns (uint256) {
        return CachedVaultStateLib.fetch().cachedTotalAssets;
    }

    /**
     * @dev Initialization function that will be called when a proxy vault is deployed through `ConcreteFactory`.
     */
    function _initialize(
        uint64,
        /*initialVersion*/
        address,
        /*owner*/
        bytes memory data
    )
        internal
        virtual
        override
    {
        (
            address allocateModuleAddr,
            address asset,
            address initialVaultManager,
            string memory name,
            string memory symbol
        ) = abi.decode(data, (address, address, address, string, string));

        require(allocateModuleAddr != address(0), InvalidAllocateModule());
        require(asset != address(0), InvalidAsset());
        require(initialVaultManager != address(0), InvalidInitialVaultManager());

        require(bytes(name).length > 0, InvalidName());
        require(bytes(symbol).length > 0, InvalidSymbol());

        __ERC20_init_unchained(name, symbol);
        __ERC4626_init_unchained(IERC20(asset));
        __AccessControlEnumerable_init_unchained();

        StateInitLib.stateInitStandardVaultImpl(allocateModuleAddr, initialVaultManager, _msgSender());
    }

    /**
     * @dev Upgrade function that will be called when a proxy vault upgrades to this implementation
     */
    function _upgrade(
        uint64,
        /* oldVersion */
        uint64,
        /* newVersion */
        bytes calldata /* data */
    )
        internal
        virtual
        override
    {}

    /**
     * @dev Internal function that executes the yield accrual operation across all active strategies.
     * @dev This function iterates through all strategies managed by the vault, calculates
     *      yield generated and losses incurred since the last yield accrual, and updates the vault's
     *      internal accounting accordingly.
     * @dev This function does not trigger actual fund movements, it only updates accounting
     *      to reflect the current state of strategy allocations.
     */
    function _accrueYield() internal virtual returns (uint256) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        uint256 totalPositiveYield;
        uint256 totalNegativeYield;
        {
            address[] memory strategies = $.strategies.values();
            uint256 strategiesCounter = strategies.length;

            for (uint256 i; i < strategiesCounter; ++i) {
                (uint256 positiveYield, uint256 loss, uint256 strategyTotalAllocatedValue) =
                    _previewStrategyYield(strategies[i]);

                // update the strategy allocated amount only if there is yield or loss, otherwise it's the same amount as when we called `allocate()`.
                // we do update the lastTotalAssets after netting the yield and loss
                if (positiveYield != 0 || loss != 0) {
                    $.strategyData[strategies[i]].allocated = strategyTotalAllocatedValue.toUint120();

                    totalPositiveYield += positiveYield;
                    totalNegativeYield += loss;

                    emit StrategyYieldAccrued(strategies[i], strategyTotalAllocatedValue, positiveYield, loss);
                }
            }
        }

        CachedVaultStateLib.ConcreteCachedVaultStateStorage storage $cached = CachedVaultStateLib.fetch();

        uint256 totalAssetsCached = $cached.cachedTotalAssets + totalPositiveYield - totalNegativeYield;
        // update the lastTotalAssets
        $cached.cachedTotalAssets = totalAssetsCached;

        // Accrue management fees after accruing yield to calculate fee asset amount on total vault AUM
        accrueManagementFee(totalAssetsCached);

        // Accrue performance fees on net yield amount
        accruePerformanceFee(totalAssetsCached, totalPositiveYield, totalNegativeYield);

        emit YieldAccrued(totalPositiveYield, totalNegativeYield);

        return totalAssetsCached;
    }

    /**
     * @dev Internal function that handles withdrawal operations, including strategy deallocation when needed.
     * @dev This function implements the core withdrawal logic for the vault, automatically managing
     *      fund retrieval from both idle vault balance and allocated strategies to fulfill withdrawal requests.
     * @dev Withdrawal Process:
     *      1. First attempts to use idle funds (assets sitting in the vault contract)
     *      2. If idle funds are insufficient, iterates through active strategies to deallocate funds
     *      3. For each strategy, respects the strategy's maxWithdraw() limit
     *      4. Updates strategy allocation accounting after successful deallocations
     *      5. Delegates to parent contract for final ERC4626 withdrawal execution
     * @dev Requirements:
     *      - Combined idle funds and strategy liquidity must be sufficient for withdrawal amount
     *      - All deallocated strategies must be in Active status
     *      - Strategy onWithdraw() calls must succeed and return expected amounts
     * @param caller The address that initiated the withdrawal (for access control)
     * @param receiver The address that will receive the withdrawn assets
     * @param owner The address whose shares are being burned for the withdrawal
     * @param assets The amount of assets to withdraw from the vault
     * @param shares The amount of shares to burn in exchange for the assets
     */
    function _executeWithdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
        internal
        virtual
        returns (uint256)
    {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        uint256 floatingFunds = IERC20(asset()).balanceOf(address(this));
        uint256 lockedAssets = _lockedAssets();
        uint256 totalWithdrawableAmount = floatingFunds >= lockedAssets ? floatingFunds - lockedAssets : 0;
        if (totalWithdrawableAmount < assets) {
            address[] memory deallocationOrder = $.deallocationOrder;
            uint256 strategiesCounter = deallocationOrder.length;

            uint256 desiredAssets;
            for (uint256 i; i < strategiesCounter; ++i) {
                if (
                    ($.strategyData[deallocationOrder[i]].status != IConcreteStandardVaultImpl.StrategyStatus.Active)
                        || !$.strategies.contains(deallocationOrder[i])
                ) continue;

                unchecked {
                    desiredAssets = assets - totalWithdrawableAmount;
                }

                uint256 withdrawableAmountFromStrategy = IStrategyTemplate(deallocationOrder[i]).maxWithdraw();
                uint256 withdrawAmount =
                    (withdrawableAmountFromStrategy >= desiredAssets) ? desiredAssets : withdrawableAmountFromStrategy;

                if (withdrawAmount > 0) {
                    // Actually withdraw from the strategy
                    uint256 actualWithdrawn = IStrategyTemplate(deallocationOrder[i]).onWithdraw(withdrawAmount);

                    // Update strategy allocated amount
                    $.strategyData[deallocationOrder[i]].allocated -= actualWithdrawn.toUint120();

                    totalWithdrawableAmount += actualWithdrawn;
                }

                if (totalWithdrawableAmount >= assets) break;
            }
        }

        _withdraw(caller, receiver, owner, assets, shares);

        return totalWithdrawableAmount;
    }

    function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)
        internal
        virtual
        override
    {
        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        // If asset() is ERC-777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
        // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
        // shares are burned and after the assets are transferred, which is a valid state.

        CachedVaultStateLib.fetch().cachedTotalAssets -= assets;
        _burn(owner, shares);
        SafeERC20.safeTransfer(IERC20(asset()), receiver, assets);

        emit Withdraw(caller, receiver, owner, assets, shares);
    }

    /**
     * @dev Deposit/mint common workflow.
     */
    function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override {
        // If asset() is ERC-777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
        // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
        // calls the vault, which is assumed not malicious.
        //
        // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
        // assets are transferred and before the shares are minted, which is a valid state.
        // slither-disable-next-line reentrancy-no-eth
        SafeERC20.safeTransferFrom(IERC20(asset()), caller, address(this), assets);
        CachedVaultStateLib.fetch().cachedTotalAssets += assets;
        _mint(receiver, shares);

        emit Deposit(caller, receiver, assets, shares);
    }

    /**
     * @dev Accrue management fees by minting shares to the fee recipient.
     * @param totalAssetsAmount The total assets in the vault to calculate fee on
     */
    function accrueManagementFee(uint256 totalAssetsAmount) internal {
        (uint256 feeShares, uint256 feeAmount) = previewManagementFee(totalAssetsAmount);

        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        // Update last accrual timestamp
        $.lastManagementFeeAccrual = Time.timestamp();

        // Mint shares to management fee recipient
        address managementFeeRecipient = $.managementFeeRecipient;
        if (feeShares != 0 && managementFeeRecipient != address(0)) {
            // Mint shares to management fee recipient
            _mint(managementFeeRecipient, feeShares);

            emit ManagementFeeAccrued(managementFeeRecipient, feeShares, feeAmount);
        }
    }

    /**
     * @dev Accrue performance fees by minting shares to the fee recipient.
     * @param totalAssetsAmount The total assets in the vault to calculate fee on
     * @param positiveYield The total positive yield generated by all strategies
     * @param loss The total losses incurred by all strategies
     */
    function accruePerformanceFee(uint256 totalAssetsAmount, uint256 positiveYield, uint256 loss) internal {
        (uint256 performanceFeeShares, uint256 feeAmount) =
            previewPerformanceFee(totalAssetsAmount, positiveYield, loss, totalSupply());

        if (performanceFeeShares == 0) return;

        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        // Mint shares to performance fee recipient
        address performanceFeeRecipient = $.performanceFeeRecipient;
        if (performanceFeeRecipient != address(0)) {
            _mint(performanceFeeRecipient, performanceFeeShares);
            emit PerformanceFeeAccrued(performanceFeeRecipient, performanceFeeShares, feeAmount);
        }
    }

    /**
     * @dev Simulates the yield accrual operation across all strategies including fees.
     * @return totalAssets The projected total assets after yield accrual (current + yield - losses)
     * @return totalSupply The projected total supply after yield accrual (current + management fee shares + performance fee shares)
     */
    function _previewAccrueYieldAndFees() internal view virtual returns (uint256, uint256) {
        (uint256 totalPositiveYield, uint256 totalNegativeYield) = _previewYieldNoFees();

        uint256 totalSupplyCached = totalSupply();
        uint256 totalAssetsCached = cachedTotalAssets() + totalPositiveYield - totalNegativeYield;

        (uint256 managementFeeShares,) = previewManagementFee(totalAssetsCached);
        totalSupplyCached += managementFeeShares;

        (uint256 performanceFeeShares,) =
            previewPerformanceFee(totalAssetsCached, totalPositiveYield, totalNegativeYield, totalSupplyCached);
        totalSupplyCached += performanceFeeShares;

        return (totalAssetsCached, totalSupplyCached);
    }

    /**
     * @dev Calculates total positive and negative yield across all strategies.
     * @return totalPositiveYield The sum of all positive yields from strategies
     * @return totalNegativeYield The sum of all losses from strategies
     */
    function _previewYieldNoFees()
        internal
        view
        virtual
        returns (uint256 totalPositiveYield, uint256 totalNegativeYield)
    {
        address[] memory strategies = SVLib.fetch().strategies.values();
        uint256 strategiesCounter = strategies.length;

        for (uint256 i; i < strategiesCounter; ++i) {
            (uint256 positiveYield, uint256 loss,) = _previewStrategyYield(strategies[i]);

            if (positiveYield != 0 || loss != 0) {
                totalPositiveYield += positiveYield;
                totalNegativeYield += loss;
            }
        }
    }

    /**
     * @dev Accrues yield and accounts for losses for a single strategy.
     * @dev This function queries the current total allocated value from a strategy,
     *      compares it against the previously recorded allocated amount, and calculates
     *      the yield generated or loss incurred since the last yield accrual.
     * @param strategy The address of the strategy contract to accrue yield from.
     * @return yield The amount of positive yield generated by the strategy since last accrual.
     * @return loss The amount of loss incurred by the strategy since last accrual.
     */
    function _previewStrategyYield(address strategy) internal view returns (uint256, uint256, uint256) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        uint120 strategyAllocatedAmount = $.strategyData[strategy].allocated;

        if ($.strategyData[strategy].status != IConcreteStandardVaultImpl.StrategyStatus.Active) {
            return (0, 0, 0);
        }

        uint256 currentTotalAllocatedValue = IStrategyTemplate(strategy).totalAllocatedValue();
        currentTotalAllocatedValue =
            (currentTotalAllocatedValue >= type(uint120).max) ? type(uint120).max : currentTotalAllocatedValue;

        uint256 yield;
        uint256 loss;
        if (currentTotalAllocatedValue == strategyAllocatedAmount) {
            return (yield, loss, currentTotalAllocatedValue);
        } else if (currentTotalAllocatedValue > strategyAllocatedAmount) {
            yield = currentTotalAllocatedValue - strategyAllocatedAmount;
        } else {
            loss = strategyAllocatedAmount - currentTotalAllocatedValue;
        }

        return (yield, loss, currentTotalAllocatedValue);
    }

    /**
     * @dev Preview management fee accrual.
     * @param _lastTotalAssets The total assets deposited in the vault to calculate fee on
     * @return feeShares The number of shares to mint as management fee
     * @return feeAmount The asset value of the management fee
     */
    function previewManagementFee(uint256 _lastTotalAssets)
        internal
        view
        returns (uint256 feeShares, uint256 feeAmount)
    {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        if ($.managementFee == 0) return (0, 0);

        uint32 currentTime = Time.timestamp();
        uint32 lastAccrual = $.lastManagementFeeAccrual;

        if (currentTime == lastAccrual) return (0, 0);

        uint256 timeElapsed = currentTime - lastAccrual;
        // management fee is calculated on total vault AUM(after yield accrual)
        uint256 annualFeeAmount = (_lastTotalAssets * $.managementFee) / ConstantsLib.BASIS_POINTS_DENOMINATOR;
        feeAmount = (annualFeeAmount * timeElapsed) / (365 days);

        if (feeAmount == 0) return (0, 0);

        // sanity check - clamp the fee amount to the last total assets
        if (feeAmount > _lastTotalAssets) {
            feeAmount = _lastTotalAssets;
        }

        // convert fee amount to shares using total assets net of the fee to prevent dilution. of the fee amount
        feeShares =
            feeAmount.calcConvertToShares(totalSupply(), _lastTotalAssets - feeAmount, Math.Rounding.Floor, false);

        return (feeShares, feeAmount);
    }

    /**
     * @dev Preview performance fee accrual.
     * @param totalAssetsAmount The total assets in the vault to calculate fee on
     * @param positiveYield The total positive yield generated by all strategies
     * @param loss The total losses incurred by all strategies
     * @param totalSupply The current total supply of vault shares
     * @return shares The number of shares to mint as performance fee
     * @return feeAmount The asset value of the performance fee
     */
    function previewPerformanceFee(uint256 totalAssetsAmount, uint256 positiveYield, uint256 loss, uint256 totalSupply)
        internal
        view
        returns (uint256, uint256)
    {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        if ($.performanceFee == 0 || (loss >= positiveYield)) return (0, 0);

        uint256 netPositiveYield = positiveYield - loss;
        uint256 feeAmount = Math.mulDiv(netPositiveYield, $.performanceFee, ConstantsLib.BASIS_POINTS_DENOMINATOR);

        if (feeAmount == 0) return (0, 0);

        uint256 feeShares =
            feeAmount.calcConvertToShares(totalSupply, totalAssetsAmount - feeAmount, Math.Rounding.Floor, false);

        return (feeShares, feeAmount);
    }

    /**
     * @dev Internal function to calculate the maximum amount of assets that can be withdrawn by an owner.
     * @dev The calculation considers:
     *      - Owner's current share balance converted to equivalent assets
     *      - Current vault liquidity (idle assets + withdrawable amounts from strategies)
     *      - Strategy withdrawal limitations and availability
     * @param owner The address of the account for which to calculate maximum withdrawal.
     * @return The maximum amount of assets that can actually be withdrawn by the owner,
     *         considering both ownership rights and liquidity constraints.
     * @return totalAssets The total amount of assets in the vault after previewing the yield accrual
     */
    function _maxWithdraw(address owner) internal view virtual returns (uint256, uint256, uint256) {
        uint256 ownerShares = balanceOf(owner);
        (uint256 totalAssetsPreview, uint256 totalSupplyPreview) = _previewAccrueYieldAndFees();

        uint256 maxAssets =
            ownerShares.calcConvertToAssets(totalSupplyPreview, totalAssetsPreview, Math.Rounding.Floor, false);

        return (_simulateWithdraw(maxAssets), totalAssetsPreview, totalSupplyPreview);
    }

    /// @dev Simulate withdrawing an amount of assets from the vault.
    /// @param requestedAssets Amount of assets to withdraw.
    /// @return Amount of assets filled.
    function _simulateWithdraw(uint256 requestedAssets) internal view virtual returns (uint256) {
        SVLib.ConcreteStandardVaultImplStorage storage $ = SVLib.fetch();

        uint256 totalWithdrawableAmount = IERC20(asset()).balanceOf(address(this));
        if (totalWithdrawableAmount < requestedAssets) {
            address[] memory deallocationOrder = $.deallocationOrder;
            uint256 strategiesCounter = deallocationOrder.length;

            for (uint256 i; i < strategiesCounter; ++i) {
                if (
                    ($.strategyData[deallocationOrder[i]].status != IConcreteStandardVaultImpl.StrategyStatus.Active)
                        || !$.strategies.contains(deallocationOrder[i])
                ) continue;

                uint256 desiredAssets;
                unchecked {
                    desiredAssets = requestedAssets - totalWithdrawableAmount;
                }

                uint256 withdrawableAmountFromStrategy = IStrategyTemplate(deallocationOrder[i]).maxWithdraw();
                uint256 withdrawAmount =
                    (withdrawableAmountFromStrategy >= desiredAssets) ? desiredAssets : withdrawableAmountFromStrategy;

                totalWithdrawableAmount += withdrawAmount;

                if (totalWithdrawableAmount >= requestedAssets) break;
            }
        } else {
            totalWithdrawableAmount = requestedAssets;
        }

        return totalWithdrawableAmount;
    }

    /**
     * @dev Internal function used only by the public convertToShares() function.
     * @dev This function returns share amounts NOT inclusive of management or performance fees,
     *      as required by the ERC4626 specification. The convertToShares() function should return
     *      the theoretical share amount for a given asset amount without considering fee deductions.
     * @param assets The amount of assets to convert to shares
     * @param rounding The rounding direction for the conversion
     * @return The equivalent amount of shares, NOT inclusive of fees
     */
    function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual override returns (uint256) {
        (uint256 totalPositiveYield, uint256 totalNegativeYield) = _previewYieldNoFees();
        return assets.calcConvertToShares(
            totalSupply(), cachedTotalAssets() + totalPositiveYield - totalNegativeYield, rounding, false
        );
    }

    /**
     * @dev Internal function used only by the public convertToAssets() function.
     * @dev This function returns asset amounts NOT inclusive of management or performance fees,
     *      as required by the ERC4626 specification. The convertToAssets() function should return
     *      the theoretical asset amount for a given share amount without considering fee deductions.
     * @param shares The amount of shares to convert to assets
     * @param rounding The rounding direction for the conversion
     * @return The equivalent amount of assets, NOT inclusive of fees
     */
    function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual override returns (uint256) {
        (uint256 totalPositiveYield, uint256 totalNegativeYield) = _previewYieldNoFees();
        return shares.calcConvertToAssets(
            totalSupply(), cachedTotalAssets() + totalPositiveYield - totalNegativeYield, rounding, false
        );
    }

    function _lockedAssets() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "src/interface/IConcreteStandardVaultImpl.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {IUpgradeableVault} from "./IUpgradeableVault.sol";
import {Hooks} from "./IHook.sol";
import {IERC4626} from "@openzeppelin-contracts/interfaces/IERC4626.sol";
import {IAccessControlEnumerable} from "@openzeppelin-contracts/access/extensions/IAccessControlEnumerable.sol";

/**
 * @title IConcreteStandardVaultImpl
 * @dev Interface for the standard vault implementation that manages multiple investment strategies.
 * @dev This interface extends the base tokenized vault functionality with strategy management capabilities.
 * @dev Strategies are external contracts that implement the IStrategyTemplate interface and handle
 * fund allocation to different yield-generating protocols or investment opportunities.
 */
interface IConcreteStandardVaultImpl is IUpgradeableVault, IERC4626, IAccessControlEnumerable {
    /**
     * @dev Thrown when attempting to withdraw to the zero address.
     */
    error InvalidReceiver();

    /**
     * @dev Thrown when attempting to add a strategy that uses a different asset than the vault.
     */
    error InvalidStrategyAsset();

    /**
     * @dev Thrown when attempting to add a strategy that has already been added to the vault.
     */
    error StrategyAlreadyAdded();

    /**
     * @dev Thrown when attempting to operate on a strategy that doesn't exist in the vault.
     */
    error StrategyDoesNotExist();

    /**
     * @dev Thrown when attempting to interact with a strategy that is halted.
     */
    error StrategyIsHalted();

    /**
     * @dev Thrown when attempting to halt a strategy that is already halted.
     */
    error StrategyAlreadyHalted();

    /**
     * @dev Thrown when attempting to toggle the status of an inactive strategy.
     */
    error CannotToggleInactiveStrategy();

    /**
     * @dev Thrown when attempting to set a management fee without setting a recipient first.
     */
    error FeeRecipientNotSet();

    /**
     * @dev Thrown when attempting to set a management fee that exceeds the maximum allowed.
     */
    error ManagementFeeExceedsMaximum();

    /**
     * @dev Thrown when attempting to set a performance fee that exceeds the maximum allowed.
     */
    error PerformanceFeeExceedsMaximum();

    /**
     * @dev Thrown when attempting to set an invalid fee recipient address (address(0)).
     */
    error InvalidFeeRecipient();

    /**
     * @dev Thrown when the allocate module is invalid.
     */
    error InvalidAllocateModule();

    /**
     * @dev Thrown when the asset is invalid.
     */
    error InvalidAsset();

    /**
     * @dev Thrown when the initial vault manager is invalid.
     */
    error InvalidInitialVaultManager();

    /**
     * @dev Thrown when the name is invalid.
     */
    error InvalidName();

    /**
     * @dev Thrown when the symbol is invalid.
     */
    error InvalidSymbol();

    /**
     * @dev Thrown when the deposit limits are invalid.
     */
    error InvalidDepositLimits();

    /**
     * @dev Thrown when the withdraw limits are invalid.
     */
    error InvalidWithdrawLimits();

    /**
     * @dev Thrown when the asset amount is out of bounds.
     */
    error AssetAmountOutOfBounds(address sender, uint256 assets, uint256 minDepositAmount, uint256 maxDepositAmount);

    /**
     * @dev Thrown when attempting to remove a strategy that has allocation or is in the deallocation order.
     */
    error StrategyHasAllocation();

    /**
     * @dev Thrown when the vault has insufficient balance to process the epoch.
     */
    error InsufficientBalance();

    /**
     * @dev Thrown when calculated shares are zero.
     */
    error InsufficientShares();

    /**
     * @dev Thrown when calculated assets are zero.
     */
    error InsufficientAssets();

    /**
     * @dev Emitted when a new strategy is successfully added to the vault.
     * @param strategy The address of the strategy contract that was added.
     */
    event StrategyAdded(address strategy);

    /**
     * @dev Emitted when a strategy is successfully removed from the vault.
     * @param strategy The address of the strategy contract that was removed.
     */
    event StrategyRemoved(address strategy);

    /**
     * @dev Emitted when a strategy is set to Halted status.
     * @param strategy The address of the strategy contract that was halted.
     */
    event StrategyHalted(address strategy);

    /**
     * @dev Emitted when a strategy's status is toggled between Active and Halted.
     * @param strategy The address of the strategy contract whose status was toggled.
     */
    event StrategyStatusToggled(address indexed strategy);

    /**
     * @dev Emitted when the yield accrual operation is completed across all strategies.
     *
     * @param totalPositiveYield The total amount of positive yield generated across all strategies.
     * @param totalNegativeYield The total amount of losses incurred across all strategies.
     */
    event YieldAccrued(uint256 totalPositiveYield, uint256 totalNegativeYield);

    /**
     * @dev Emitted when management fee is accrued.
     * @param recipient The address that received the management fee shares.
     * @param shares The number of shares minted as management fee.
     * @param feeAmount The asset value of the management fee.
     */
    event ManagementFeeAccrued(address indexed recipient, uint256 shares, uint256 feeAmount);

    /**
     * @dev Emitted when performance fee is accrued.
     * @param recipient The address that received the performance fee shares.
     * @param shares The number of shares minted as performance fee.
     * @param feeAmount The asset value of the performance fee.
     */
    event PerformanceFeeAccrued(address indexed recipient, uint256 shares, uint256 feeAmount);

    /**
     * @dev Emitted when management fee is updated.
     * @param managementFee The new management fee rate in basis points.
     */
    event ManagementFeeUpdated(uint16 managementFee);

    /**
     * @dev Emitted when management fee recipient is updated.
     * @param managementFeeRecipient The new management fee recipient address.
     */
    event ManagementFeeRecipientUpdated(address managementFeeRecipient);

    /**
     * @dev Emitted when performance fee is updated.
     * @param performanceFee The new performance fee rate in basis points.
     */
    event PerformanceFeeUpdated(uint16 performanceFee);

    /**
     * @dev Emitted when performance fee recipient is updated.
     * @param performanceFeeRecipient The new performance fee recipient address.
     */
    event PerformanceFeeRecipientUpdated(address performanceFeeRecipient);

    /**
     * @dev Emitted when deposit limits are updated.
     * @param maxDepositAmount The new maximum deposit amount.
     * @param minDepositAmount The new minimum deposit amount.
     */
    event DepositLimitsUpdated(uint256 maxDepositAmount, uint256 minDepositAmount);

    /**
     * @dev Emitted when withdraw limits are updated.
     * @param maxWithdrawAmount The new maximum withdraw amount.
     * @param minWithdrawAmount The new minimum withdraw amount.
     */
    event WithdrawLimitsUpdated(uint256 maxWithdrawAmount, uint256 minWithdrawAmount);

    /**
     * @dev Emitted when the deallocation order is updated.
     */
    event DeallocationOrderUpdated();

    /**
     * @dev Emitted when an individual strategy's yield is accrued.
     *
     * @param strategy The address of the strategy contract whose yield was accrued.
     * @param currentTotalAllocatedValue The current total allocated value reported by the strategy.
     * @param yield The amount of positive yield generated by this strategy since last accrual.
     * @param loss The amount of loss incurred by this strategy since last accrual.
     */
    event StrategyYieldAccrued(
        address indexed strategy, uint256 currentTotalAllocatedValue, uint256 yield, uint256 loss
    );

    /**
     * @dev Enumeration of possible strategy statuses within the vault.
     * @dev Inactive: Strategy is inactive and cannot receive new allocations.
     * @dev Active: Strategy is active and can receive allocations and process withdrawals normally.
     * @dev Halted: Strategy is halted, typically due to detected issues or failures.
     * In this state, the strategy can be removed even if it has allocated funds
     */
    enum StrategyStatus {
        Inactive,
        Active,
        Halted
    }

    /**
     * @dev Structure containing metadata and state information for each strategy.
     * @dev status: Current operational status of the strategy.
     * @dev allocated: Total amount of vault assets currently allocated to this strategy, denominated in the vault's underlying asset token.
     */
    struct StrategyData {
        StrategyStatus status;
        uint120 allocated;
    }

    /**
     * @dev Adds a new strategy to the vault.
     * @dev The strategy must implement the IStrategyTemplate interface and use the same underlying asset as the vault.
     * @dev Only callable by accounts with the STRATEGY_MANAGER role.
     *
     * @param strategy The address of the strategy contract to add.
     *
     * Requirements:
     * - The strategy's asset() must match the vault's asset()
     * - The strategy must not already be added to the vault
     * - Caller must have STRATEGY_MANAGER role
     *
     * Emits:
     * - StrategyAdded event
     *
     * Reverts:
     * - InvalidStrategyAsset if strategy uses different asset
     * - StrategyAlreadyAdded if strategy is already in the vault
     */
    function addStrategy(address strategy) external;

    /**
     * @dev Removes a strategy from the vault.
     * @dev The strategy can only be removed if it has no allocated funds, unless it's in Halted status.
     * @dev Only callable by accounts with the STRATEGY_MANAGER role.
     *
     * @param strategy The address of the strategy contract to remove.
     *
     * Requirements:
     * - Strategy must exist in the vault
     * - Strategy must have zero alloca

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Swap, Liquidity, Yield, Upgradeable, Multi-Signature, Factory|addr:0xb1119e9ba782c04c96fa0fe9ce89448650903d8d|verified:true|block:23732633|tx:0xec2eceb6b0ef3ad77abdda9c4b3b0d4be1765f90fff1ec8b3bf0cb16361372db|first_check:1762349536

Submitted on: 2025-11-05 14:32:18

Comments

Log in to comment.

No comments yet.