CollectorV4

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/collector/CollectorV4.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import "../../src/interfaces/queues/IEigenLayerWithdrawalQueue.sol";
import "../../src/interfaces/queues/ISymbioticWithdrawalQueue.sol";
import "../../src/interfaces/tokens/IWSTETH.sol";

import "../../src/strategies/RatiosStrategy.sol";
import "../../src/vaults/MultiVault.sol";
import "./Oracle.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import "./modules/SymbioticModule.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";

contract CollectorV4 is Ownable {
    struct Withdrawal {
        uint256 subvaultIndex;
        uint256 assets;
        bool isTimestamp; // if false -> block.number
        uint256 claimingTime; // if 0 - assets == claimable assets, otherwise = pending assets
        uint256 withdrawalIndex;
        uint256 withdrawalRequestType; // 0 - withdrawals, 1 - transferedWithdrawals
    }

    struct Response {
        address vault;
        address asset;
        uint8 assetDecimals;
        uint256 assetPriceX96;
        uint256 totalLP;
        uint256 totalUSD;
        uint256 totalETH;
        uint256 totalUnderlying;
        uint256 limitLP;
        uint256 limitUSD;
        uint256 limitETH;
        uint256 limitUnderlying;
        uint256 userLP;
        uint256 userETH;
        uint256 userUSD;
        uint256 userUnderlying;
        uint256 lpPriceUSD;
        uint256 lpPriceETH;
        uint256 lpPriceUnderlying;
        Withdrawal[] withdrawals;
        uint256 blockNumber;
        uint256 timestamp;
    }

    struct FetchDepositAmountsResponse {
        bool isDepositPossible;
        bool isDepositorWhitelisted;
        uint256[] ratiosD18; // multiplied by 1e18 for weis of underlying tokens
        address[] tokens;
        uint256 expectedLpAmount; // in lp weis 1e18
        uint256 expectedLpAmountUSDC; // in USDC weis 1e8 (due to chainlink decimals)
        uint256[] expectedAmounts; // in underlying tokens weis
        uint256[] expectedAmountsUSDC; // in USDC weis 1e8 (due to chainlink decimals)
    }

    struct CollectEigenLayerWithdrawalsStack {
        uint256[] withdrawals;
        uint256[] transferredWithdrawals;
        uint256 withdrawalDelay;
        IStrategy strategy;
    }

    struct GetVaultAssetsELStack {
        uint256[] withdrawals;
        uint256 withdrawalDelay;
        IStrategy strategy;
        uint256 length;
        IDelegationManager.Withdrawal data;
        bool isClaimed;
        uint256 shares;
        uint256 vaultShares;
    }

    address public constant eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address public immutable usd = address(bytes20(keccak256("usd-token-address")));
    address public immutable wsteth;
    address public immutable weth;
    address public immutable steth;
    Oracle public oracle;

    constructor(address wsteth_, address weth_, address owner_) Ownable(owner_) {
        wsteth = wsteth_;
        steth = address(IWSTETH(wsteth_).stETH());
        weth = weth_;
    }

    // Mutable functions

    function setOracle(address oracle_) external onlyOwner {
        oracle = Oracle(oracle_);
    }

    // View functions

    function collect(address user, IERC4626 vault_) public view returns (Response memory r) {
        MultiVault vault = MultiVault(address(vault_));

        r.vault = address(vault_);
        r.asset = vault_.asset();
        r.assetDecimals = IERC20Metadata(r.asset).decimals();
        r.assetPriceX96 = oracle.priceX96(r.asset);

        r.totalLP = vault_.totalSupply();
        r.totalUnderlying = vault_.totalAssets();
        r.totalETH = oracle.getValue(r.asset, r.totalUnderlying);
        r.totalUSD = oracle.getValue(r.asset, usd, r.totalUnderlying);

        r.limitUnderlying = vault.limit();
        r.limitLP = vault.limit() > type(uint224).max
            ? type(uint256).max
            : vault.previewWithdraw(r.limitUnderlying);
        r.limitETH = oracle.getValue(r.asset, r.limitUnderlying);
        r.limitUSD = oracle.getValue(r.asset, usd, r.limitUnderlying);

        r.userLP = vault.balanceOf(user);
        r.userUnderlying = vault.convertToAssets(r.userLP);
        r.userETH = oracle.getValue(r.asset, r.userUnderlying);
        r.userUSD = oracle.getValue(r.asset, usd, r.userUnderlying);

        r.lpPriceUnderlying = vault.previewRedeem(1 ether);
        r.lpPriceETH = oracle.getValue(r.asset, r.lpPriceUnderlying);
        r.lpPriceUSD = oracle.getValue(r.asset, usd, r.lpPriceUnderlying);
        r.blockNumber = block.number;
        r.timestamp = block.timestamp;

        if (address(vault) == 0x5E362eb2c0706Bd1d134689eC75176018385430B) {
            return r;
        }

        Withdrawal[] memory withdrawals = new Withdrawal[](vault.subvaultsCount() * 50);
        uint256 iterator = 0;
        uint256 subvaultsCount = vault.subvaultsCount();
        IMultiVaultStorage.Subvault memory subvault;
        for (uint256 subvaultIndex = 0; subvaultIndex < subvaultsCount; subvaultIndex++) {
            subvault = vault.subvaultAt(subvaultIndex);
            if (subvault.withdrawalQueue == address(0)) {
                continue;
            }

            IWithdrawalQueue queue = IWithdrawalQueue(subvault.withdrawalQueue);

            uint256 claimable = queue.claimableAssetsOf(user);
            if (claimable != 0) {
                withdrawals[iterator++] = Withdrawal({
                    subvaultIndex: subvaultIndex,
                    assets: claimable,
                    isTimestamp: true,
                    claimingTime: 0,
                    withdrawalIndex: 0,
                    withdrawalRequestType: 0
                });
            }

            if (queue.pendingAssetsOf(user) == 0) {
                continue;
            }

            Withdrawal[] memory w = subvault.protocol == IMultiVaultStorage.Protocol.SYMBIOTIC
                ? collectSymbioticWithdrawals(
                    user, subvaultIndex, ISymbioticWithdrawalQueue(subvault.withdrawalQueue)
                )
                : subvault.protocol == IMultiVaultStorage.Protocol.EIGEN_LAYER
                    ? collectEigenLayerWithdrawals(
                        user, subvaultIndex, IEigenLayerWithdrawalQueue(subvault.withdrawalQueue)
                    )
                    : new Withdrawal[](0);
            for (uint256 i = 0; i < w.length; i++) {
                withdrawals[iterator++] = w[i];
            }
        }

        assembly {
            mstore(withdrawals, iterator)
        }
        r.withdrawals = withdrawals;
    }

    function collectSymbioticWithdrawals(
        address user,
        uint256 subvaultIndex,
        ISymbioticWithdrawalQueue q
    ) public view returns (Withdrawal[] memory withdrawals_) {
        withdrawals_ = new Withdrawal[](2);
        uint256 iterator = 0;
        (uint256 sharesToClaimPrev, uint256 sharesToClaim,, uint256 claimEpoch) =
            q.getAccountData(user);
        ISymbioticVault symbioticVault = q.symbioticVault();
        uint256 currentEpoch = q.getCurrentEpoch();
        if (claimEpoch == currentEpoch + 1) {
            if (sharesToClaimPrev != 0) {
                ISymbioticWithdrawalQueue.EpochData memory epochData = q.getEpochData(currentEpoch);
                uint256 assets = Math.mulDiv(
                    symbioticVault.withdrawalsOf(currentEpoch, address(q)),
                    sharesToClaimPrev,
                    epochData.sharesToClaim
                );
                if (assets != 0) {
                    withdrawals_[iterator++] = Withdrawal({
                        subvaultIndex: subvaultIndex,
                        assets: assets,
                        isTimestamp: true,
                        claimingTime: symbioticVault.currentEpochStart()
                            + symbioticVault.epochDuration(),
                        withdrawalIndex: 0,
                        withdrawalRequestType: 0
                    });
                }
            }
            if (sharesToClaim != 0) {
                ISymbioticWithdrawalQueue.EpochData memory epochData =
                    q.getEpochData(currentEpoch + 1);
                uint256 assets = Math.mulDiv(
                    symbioticVault.withdrawalsOf(currentEpoch + 1, address(q)),
                    sharesToClaim,
                    epochData.sharesToClaim
                );
                if (assets != 0) {
                    withdrawals_[iterator++] = Withdrawal({
                        subvaultIndex: subvaultIndex,
                        assets: assets,
                        isTimestamp: true,
                        claimingTime: symbioticVault.currentEpochStart()
                            + 2 * symbioticVault.epochDuration(),
                        withdrawalIndex: 0,
                        withdrawalRequestType: 0
                    });
                }
            }
        } else if (claimEpoch == currentEpoch) {
            if (sharesToClaim != 0) {
                ISymbioticWithdrawalQueue.EpochData memory epochData = q.getEpochData(currentEpoch);
                uint256 assets = Math.mulDiv(
                    symbioticVault.withdrawalsOf(currentEpoch, address(q)),
                    sharesToClaim,
                    epochData.sharesToClaim
                );
                if (assets != 0) {
                    withdrawals_[iterator++] = Withdrawal({
                        subvaultIndex: subvaultIndex,
                        assets: assets,
                        isTimestamp: true,
                        claimingTime: symbioticVault.currentEpochStart()
                            + symbioticVault.epochDuration(),
                        withdrawalIndex: 0,
                        withdrawalRequestType: 0
                    });
                }
            }
        }
        assembly {
            mstore(withdrawals_, iterator)
        }
    }

    function collectEigenLayerWithdrawals(
        address user,
        uint256 subvaultIndex,
        IEigenLayerWithdrawalQueue queue
    ) public view returns (Withdrawal[] memory withdrawals_) {
        CollectEigenLayerWithdrawalsStack memory s;
        (, s.withdrawals, s.transferredWithdrawals) =
            queue.getAccountData(user, type(uint256).max, 0, type(uint256).max, 0);
        s.withdrawalDelay = IDelegationManager(queue.delegation()).minWithdrawalDelayBlocks() + 1;
        uint256 currentBlock = block.number;
        withdrawals_ = new Withdrawal[](s.withdrawals.length + s.transferredWithdrawals.length);
        uint256 iterator = 0;
        for (uint256 i = 0; i < s.withdrawals.length; i++) {
            (
                IDelegationManager.Withdrawal memory data,
                bool isClaimed,
                ,
                uint256 shares,
                uint256 accountShares
            ) = queue.getWithdrawalRequest(s.withdrawals[i], user);
            if (isClaimed || data.startBlock + s.withdrawalDelay <= currentBlock) {
                continue;
            }
            uint256 pendingShares = queue.convertScaledSharesToShares(data, accountShares, shares);
            uint256 pendingAssets = pendingShares == 0
                ? 0
                : IIsolatedEigenLayerVault(queue.isolatedVault()).sharesToUnderlyingView(
                    queue.strategy(), pendingShares
                );
            withdrawals_[iterator++] = Withdrawal({
                subvaultIndex: subvaultIndex,
                assets: pendingAssets,
                isTimestamp: false,
                claimingTime: data.startBlock + s.withdrawalDelay,
                withdrawalIndex: s.withdrawals[i],
                withdrawalRequestType: 0
            });
        }

        for (uint256 i = 0; i < s.transferredWithdrawals.length; i++) {
            (
                IDelegationManager.Withdrawal memory data,
                bool isClaimed,
                uint256 assets,
                uint256 shares,
                uint256 accountShares
            ) = queue.getWithdrawalRequest(s.withdrawals[i], user);
            withdrawals_[iterator] = Withdrawal({
                subvaultIndex: subvaultIndex,
                assets: 0,
                isTimestamp: false,
                claimingTime: data.startBlock + s.withdrawalDelay,
                withdrawalIndex: s.withdrawals[i],
                withdrawalRequestType: 1
            });
            if (isClaimed) {
                uint256 claimalbleAssets =
                    shares == accountShares ? assets : Math.mulDiv(assets, accountShares, shares);
                withdrawals_[iterator++].assets = claimalbleAssets;
            } else {
                uint256 pendingShares =
                    queue.convertScaledSharesToShares(data, accountShares, shares);
                uint256 pendingAssets = pendingShares == 0
                    ? 0
                    : IIsolatedEigenLayerVault(queue.isolatedVault()).sharesToUnderlyingView(
                        queue.strategy(), pendingShares
                    );
                withdrawals_[iterator++].assets = pendingAssets;
            }
        }
        assembly {
            mstore(withdrawals_, iterator)
        }
    }

    function collect(address user, address[] memory vaults)
        public
        view
        returns (Response[] memory responses)
    {
        responses = new Response[](vaults.length);
        for (uint256 i = 0; i < vaults.length; i++) {
            responses[i] = collect(user, IERC4626(vaults[i]));
        }
    }

    function multiCollect(address[] calldata users, address[] calldata vaults)
        external
        view
        returns (Response[][] memory responses)
    {
        responses = new Response[][](users.length);
        for (uint256 i = 0; i < users.length; i++) {
            responses[i] = collect(users[i], vaults);
        }
    }

    function fetchWithdrawalAmounts(uint256 lpAmount, address vault)
        external
        view
        returns (uint256[] memory expectedAmounts, uint256[] memory expectedAmountsUSDC)
    {
        expectedAmounts = new uint256[](1);
        expectedAmountsUSDC = new uint256[](1);
        expectedAmounts[0] = IERC4626(vault).previewRedeem(lpAmount);
        expectedAmountsUSDC[0] = oracle.getValue(IERC4626(vault).asset(), usd, expectedAmounts[0]);
    }

    function fetchDepositWrapperParams(address vault, address user, address token, uint256 amount)
        external
        view
        returns (
            bool isDepositPossible,
            bool isDepositorWhitelisted,
            bool isWhitelistedToken,
            uint256 lpAmount,
            uint256 depositValueUSDC
        )
    {
        if (MultiVault(vault).depositPause()) {
            return (false, false, false, 0, 0);
        }
        isDepositPossible = true;
        if (MultiVault(vault).depositWhitelist() && !MultiVault(vault).isDepositorWhitelisted(user))
        {
            return (isDepositPossible, false, false, 0, 0);
        }
        isDepositorWhitelisted = true;

        if (MultiVault(vault).asset() != wsteth) {
            return (isDepositPossible, isDepositorWhitelisted, false, 0, 0);
        }
        if (token == weth || token == steth || token == wsteth || token == eth) {
            isWhitelistedToken = true;
        } else {
            return (isDepositPossible, isDepositorWhitelisted, false, 0, 0);
        }
        if (token != wsteth) {
            amount = IWSTETH(wsteth).getWstETHByStETH(amount);
        }
        lpAmount = MultiVault(vault).previewDeposit(amount);
        depositValueUSDC = oracle.getValue(wsteth, usd, amount);
    }

    function fetchDepositAmounts(uint256[] memory amounts, address vault, address user)
        external
        view
        returns (FetchDepositAmountsResponse memory r)
    {
        if (MultiVault(vault).depositPause()) {
            return r;
        }
        r.isDepositPossible = true;
        if (MultiVault(vault).depositWhitelist() && !MultiVault(vault).isDepositorWhitelisted(user))
        {
            return r;
        }
        r.isDepositorWhitelisted = true;
        r.ratiosD18 = new uint256[](1);
        r.ratiosD18[0] = 1 ether;
        r.tokens = new address[](1);
        r.tokens[0] = MultiVault(vault).asset();
        r.expectedLpAmount = MultiVault(vault).previewDeposit(amounts[0]);
        r.expectedLpAmountUSDC = oracle.getValue(MultiVault(vault).asset(), usd, amounts[0]);
        r.expectedAmounts = new uint256[](1);
        r.expectedAmounts[0] = amounts[0];
        r.expectedAmountsUSDC = new uint256[](1);
        r.expectedAmountsUSDC[0] = r.expectedLpAmountUSDC;
    }

    function getVaultAssets(MultiVault v, address user, uint256 shares)
        public
        view
        returns (
            uint256 accountAssets,
            uint256 accountInstantAssets,
            Withdrawal[] memory withdrawals
        )
    {
        if (address(v) == 0x5E362eb2c0706Bd1d134689eC75176018385430B) {
            // DVstETH
            accountAssets = v.previewRedeem(shares == 0 ? v.balanceOf(user) : shares);
            accountInstantAssets = accountAssets;
            return (accountAssets, accountInstantAssets, new Withdrawal[](0));
        }
        IWithdrawalStrategy.WithdrawalData[] memory data;
        {
            IRatiosStrategy s = IRatiosStrategy(address(v.depositStrategy()));
            (, uint256 liquid) = s.calculateState(address(v), false, 0);
            accountAssets = v.previewRedeem(shares == 0 ? v.balanceOf(user) : shares);
            accountInstantAssets = Math.min(liquid, accountAssets);
            data = s.calculateWithdrawalAmounts(address(v), accountAssets);
        }
        withdrawals = new Withdrawal[](data.length * 128);
        uint256 n = 0;
        for (uint256 i = 0; i < data.length; i++) {
            accountInstantAssets += data[i].claimable;
            IMultiVaultStorage.Subvault memory subvault = v.subvaultAt(data[i].subvaultIndex);
            if (subvault.withdrawalQueue == address(0)) {
                continue;
            }

            if (data[i].staked != 0) {
                // regular unstaking
                if (subvault.protocol == IMultiVaultStorage.Protocol.SYMBIOTIC) {
                    ISymbioticVault symbioticVault = ISymbioticVault(subvault.vault);
                    withdrawals[n++] = Withdrawal({
                        subvaultIndex: data[i].subvaultIndex,
                        assets: data[i].staked,
                        isTimestamp: true,
                        claimingTime: symbioticVault.currentEpochStart()
                            + 2 * symbioticVault.epochDuration(),
                        withdrawalIndex: 0,
                        withdrawalRequestType: 0
                    });
                } else if (subvault.protocol == IMultiVaultStorage.Protocol.EIGEN_LAYER) {
                    IEigenLayerWithdrawalQueue queue =
                        IEigenLayerWithdrawalQueue(subvault.withdrawalQueue);
                    withdrawals[n++] = Withdrawal({
                        subvaultIndex: data[i].subvaultIndex,
                        assets: data[i].staked,
                        isTimestamp: false,
                        claimingTime: block.number
                            + IDelegationManager(queue.delegation()).minWithdrawalDelayBlocks() + 1,
                        withdrawalIndex: queue.withdrawalRequests(),
                        withdrawalRequestType: 0
                    });
                } else {
                    revert("Unsupported protocol");
                }
            }

            if (data[i].pending != 0) {
                if (subvault.protocol == IMultiVaultStorage.Protocol.SYMBIOTIC) {
                    ISymbioticWithdrawalQueue queue =
                        ISymbioticWithdrawalQueue(subvault.withdrawalQueue);
                    (, uint256 sharesToClaim,, uint256 claimEpoch) =
                        queue.getAccountData(address(v));
                    ISymbioticVault symbioticVault = queue.symbioticVault();
                    uint256 currentEpoch = symbioticVault.currentEpoch();
                    if (claimEpoch == currentEpoch + 1) {
                        if (sharesToClaim != 0) {
                            ISymbioticWithdrawalQueue.EpochData memory epochData =
                                queue.getEpochData(currentEpoch + 1);
                            uint256 assets = Math.mulDiv(
                                symbioticVault.withdrawalsOf(currentEpoch + 1, address(queue)),
                                sharesToClaim,
                                epochData.sharesToClaim
                            );
                            if (assets != 0) {
                                assets = Math.min(assets, data[i].pending);
                                data[i].pending -= assets;
                                withdrawals[n++] = Withdrawal({
                                    subvaultIndex: data[i].subvaultIndex,
                                    assets: assets,
                                    isTimestamp: true,
                                    claimingTime: symbioticVault.currentEpochStart()
                                        + 2 * symbioticVault.epochDuration(),
                                    withdrawalIndex: 0,
                                    withdrawalRequestType: 0
                                });
                            }
                        }
                        if (data[i].pending != 0) {
                            withdrawals[n++] = Withdrawal({
                                subvaultIndex: data[i].subvaultIndex,
                                assets: data[i].pending,
                                isTimestamp: true,
                                claimingTime: symbioticVault.currentEpochStart()
                                    + symbioticVault.epochDuration(),
                                withdrawalIndex: 0,
                                withdrawalRequestType: 0
                            });
                        }
                    } else if (claimEpoch == currentEpoch) {
                        withdrawals[n++] = Withdrawal({
                            subvaultIndex: data[i].subvaultIndex,
                            assets: data[i].pending,
                            isTimestamp: true,
                            claimingTime: symbioticVault.currentEpochStart()
                                + symbioticVault.epochDuration(),
                            withdrawalIndex: 0,
                            withdrawalRequestType: 0
                        });
                    } else {
                        revert("Invalid state!");
                    }
                } else if (subvault.protocol == IMultiVaultStorage.Protocol.EIGEN_LAYER) {
                    GetVaultAssetsELStack memory s;
                    IEigenLayerWithdrawalQueue queue =
                        IEigenLayerWithdrawalQueue(subvault.withdrawalQueue);
                    (, s.withdrawals,) =
                        queue.getAccountData(address(v), type(uint256).max, 0, 0, 0);
                    s.withdrawalDelay =
                        IDelegationManager(queue.delegation()).minWithdrawalDelayBlocks() + 1;
                    s.strategy = IStrategy(queue.strategy());
                    s.length = s.withdrawals.length;
                    // Filter out claimed and claimable withdrawals
                    for (uint256 index = 0; index < s.length;) {
                        (s.data, s.isClaimed,,,) =
                            queue.getWithdrawalRequest(s.withdrawals[index], address(v));
                        if (s.isClaimed || s.data.startBlock + s.withdrawalDelay <= block.number) {
                            s.withdrawals[index] = s.withdrawals[--s.length];
                        } else {
                            index++;
                        }
                    }
                    for (uint256 index = 0; index < s.length; index++) {
                        (s.data,,, s.shares, s.vaultShares) =
                            queue.getWithdrawalRequest(s.withdrawals[index], address(v));
                        uint256 transferrableAssets = IIsolatedEigenLayerVault(
                            queue.isolatedVault()
                        ).sharesToUnderlyingView(
                            queue.strategy(),
                            queue.convertScaledSharesToShares(s.data, s.vaultShares, s.shares)
                        );
                        if (transferrableAssets == 0) {
                            continue;
                        }
                        if (transferrableAssets >= data[i].pending) {
                            withdrawals[n++] = Withdrawal({
                                subvaultIndex: data[i].subvaultIndex,
                                assets: data[i].pending,
                                isTimestamp: false,
                                claimingTime: s.data.startBlock + s.withdrawalDelay,
                                withdrawalIndex: s.withdrawals[index],
                                withdrawalRequestType: 1
                            });
                            data[i].pending = 0;
                            break;
                        } else {
                            withdrawals[n++] = Withdrawal({
                                subvaultIndex: data[i].subvaultIndex,
                                assets: transferrableAssets,
                                isTimestamp: false,
                                claimingTime: s.data.startBlock + s.withdrawalDelay,
                                withdrawalIndex: s.withdrawals[index],
                                withdrawalRequestType: 1
                            });
                            data[i].pending -= transferrableAssets;
                        }
                    }
                    if (data[i].pending != 0) {
                        withdrawals[n++] = Withdrawal({
                            subvaultIndex: data[i].subvaultIndex,
                            assets: data[i].pending,
                            isTimestamp: false,
                            claimingTime: 0,
                            withdrawalIndex: 0,
                            withdrawalRequestType: 2 /* roundings, et cetera */
                        });
                    }
                } else {
                    revert("Unsupported protocol");
                }
            }
        }

        assembly {
            mstore(withdrawals, n)
        }
    }
}
"
    },
    "src/interfaces/queues/IEigenLayerWithdrawalQueue.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {IIsolatedEigenLayerVault} from "../adapters/IIsolatedEigenLayerVault.sol";
import {IWithdrawalQueue} from "./IWithdrawalQueue.sol";
import {IDelegationManager} from "@eigenlayer-interfaces/IDelegationManager.sol";
import {IStrategy} from "@eigenlayer-interfaces/IStrategy.sol";

import {IDelegationManagerExtended} from "../utils/IDelegationManagerExtended.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

interface IEigenLayerWithdrawalQueue is IWithdrawalQueue {
    struct WithdrawalData {
        IDelegationManager.Withdrawal data;
        bool isClaimed;
        uint256 assets;
        uint256 shares;
        mapping(address account => uint256) sharesOf;
    }

    struct AccountData {
        uint256 claimableAssets;
        EnumerableSet.UintSet withdrawals;
        EnumerableSet.UintSet transferredWithdrawals;
    }

    function MAX_WITHDRAWALS() external view returns (uint256);

    function isolatedVault() external view returns (address);

    function claimer() external view returns (address);

    function delegation() external view returns (address);

    function strategy() external view returns (address);

    function operator() external view returns (address);

    function isShutdown() external view returns (bool);

    function latestWithdrawableBlock() external view returns (uint256);

    function convertScaledSharesToShares(
        IDelegationManager.Withdrawal memory withdrawal,
        uint256 scaledShares,
        uint256 totalScaledShares
    ) external view returns (uint256);

    function getAccountData(
        address account,
        uint256 withdrawalsLimit,
        uint256 withdrawalsOffset,
        uint256 transferredWithdrawalsLimit,
        uint256 transferredWithdrawalsOffset
    )
        external
        view
        returns (
            uint256 claimableAssets,
            uint256[] memory withdrawals,
            uint256[] memory transferredWithdrawals
        );

    function getWithdrawalRequest(uint256 index, address account)
        external
        view
        returns (
            IDelegationManager.Withdrawal memory data,
            bool isClaimed,
            uint256 assets,
            uint256 shares,
            uint256 accountShares
        );

    function withdrawalRequests() external view returns (uint256);

    function initialize(address isolatedVault_, address strategy_, address operator_) external;

    function request(address account, uint256 assets, bool isSelfRequested) external;

    function handleWithdrawals(address account) external;

    function acceptPendingAssets(address account, uint256[] calldata withdrawals_) external;

    // permissionless function
    function shutdown(uint32 blockNumber, uint256 shares) external;

    event Transfer(
        address indexed from, address indexed to, uint256 indexed withdrawalIndex, uint256 assets
    );

    event Pull(uint256 indexed withdrawalIndex, uint256 assets);

    event Handled(address indexed account, uint256 indexed withdrawalIndex, uint256 assets);

    event Request(
        address indexed account,
        uint256 indexed withdrawalIndex,
        uint256 assets,
        bool isSelfRequested
    );

    event Claimed(address indexed account, address indexed to, uint256 assets);

    event Accepted(address indexed account, uint256 indexed withdrawalIndex);

    event Shutdown(address indexed sender, uint32 indexed blockNumber, uint256 indexed shares);
}
"
    },
    "src/interfaces/queues/ISymbioticWithdrawalQueue.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import {IWithdrawalQueue} from "./IWithdrawalQueue.sol";

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IVault as ISymbioticVault} from "@symbiotic/core/interfaces/vault/IVault.sol";

/**
 * @title ISymbioticWithdrawalQueue
 * @notice Interface to handle the withdrawal process from the Symbiotic Vault.
 * @dev This interface is an extension of IWithdrawalQueue for interacting specifically with the Symbiotic Vault.
 */
interface ISymbioticWithdrawalQueue is IWithdrawalQueue {
    /**
     * @notice Struct to hold epoch-related data.
     * @param isClaimed Indicates whether the epoch has been claimed.
     * @param sharesToClaim The amount of shares to be claimed.
     * @param claimableAssets The amount of assets that can be claimed.
     */
    struct EpochData {
        bool isClaimed;
        uint256 sharesToClaim;
        uint256 claimableAssets;
    }

    /**
     * @notice Struct to store account-related data for withdrawals.
     * @param sharesToClaim A mapping of epochs to shares to be claimed for a given epoch.
     * @param claimableAssets The total amount of assets that can be claimed.
     * @param claimEpoch The most recent epoch requested for withdrawal.
     */
    struct AccountData {
        mapping(uint256 => uint256) sharesToClaim;
        uint256 claimableAssets;
        uint256 claimEpoch;
    }

    /**
     * @notice Returns the address of the associated MellowSymbioticVault.
     * @return vault The address of the MellowSymbioticVault.
     */
    function vault() external view returns (address);

    /**
     * @notice Returns the address of the associated Symbiotic Vault.
     * @return symbioticVault The address of the Symbiotic Vault.
     */
    function symbioticVault() external view returns (ISymbioticVault);

    /**
     * @notice Returns the address of the collateral token used by the vault.
     * @dev The collateral token is the same as the vault's asset.
     * @return collateralAddress The address of the collateral token.
     */
    function collateral() external view returns (address);

    /**
     * @notice Returns the current epoch of the Symbiotic Vault.
     * @return currentEpoch The current epoch of the Symbiotic Vault.
     */
    function getCurrentEpoch() external view returns (uint256);

    /**
     * @notice Returns the data for a specific account.
     * @param account The address of the account to retrieve data for.
     * @return sharesToClaimPrev The amount of shares to claim for the epoch before the last requested epoch.
     * @return sharesToClaim The amount of shares to claim for the last requested epoch.
     * @return claimableAssets The total amount of assets that can be claimed.
     * @return claimEpoch The most recent epoch requested for withdrawal.
     */
    function getAccountData(address account)
        external
        view
        returns (
            uint256 sharesToClaimPrev,
            uint256 sharesToClaim,
            uint256 claimableAssets,
            uint256 claimEpoch
        );

    /**
     * @notice Returns the data for a specific epoch.
     * @param epoch The epoch number to retrieve data for.
     * @return epochData The data for the specified epoch.
     */
    function getEpochData(uint256 epoch) external view returns (EpochData memory);

    /**
     * @notice Returns the total amount of pending assets awaiting withdrawal.
     * @dev This amount may decrease due to slashing events in the Symbiotic Vault.
     * @return assets The total amount of assets pending withdrawal.
     */
    function pendingAssets() external view returns (uint256);

    /**
     * @notice Returns the amount of assets in the withdrawal queue for a specific account that cannot be claimed yet.
     * @param account The address of the account.
     * @return assets The amount of pending assets in the withdrawal queue for the account.
     */
    function pendingAssetsOf(address account) external view returns (uint256 assets);

    /**
     * @notice Returns the amount of assets that can be claimed by an account.
     * @param account The address of the account.
     * @return assets The amount of assets claimable by the account.
     */
    function claimableAssetsOf(address account) external view returns (uint256 assets);

    /**
     * @notice Requests the withdrawal of a specified amount of collateral for a given account.
     * @param account The address of the account requesting the withdrawal.
     * @param amount The amount of collateral to withdraw.
     *
     * @custom:requirements
     * - `msg.sender` MUST be the vault.
     * - `amount` MUST be greater than zero.
     *
     * @custom:effects
     * - Emits a `WithdrawalRequested` event.
     */
    function request(address account, uint256 amount) external;

    /**
     * @notice Claims assets from the Symbiotic Vault for a specified epoch to the Withdrawal Queue address.
     * @param epoch The epoch number.
     * @dev Emits an EpochClaimed event.
     */
    function pull(uint256 epoch) external;

    /**
     * @notice Finalizes the withdrawal process for a specific account and transfers assets to the recipient.
     * @param account The address of the account requesting the withdrawal.
     * @param recipient The address of the recipient receiving the withdrawn assets.
     * @param maxAmount The maximum amount of assets to withdraw.
     * @return amount The actual amount of assets withdrawn.
     */
    function claim(address account, address recipient, uint256 maxAmount)
        external
        returns (uint256 amount);

    /**
     * @notice Handles the pending epochs for a specific account and makes assets claimable for recent epochs.
     * @param account The address of the account.
     * @dev Emits an EpochClaimed event.
     */
    function handlePendingEpochs(address account) external;

    /// @notice Emitted when a withdrawal request is created.
    event WithdrawalRequested(address indexed account, uint256 indexed epoch, uint256 amount);

    /// @notice Emitted when assets are successfully claimed for a specific epoch.
    event EpochClaimed(uint256 indexed epoch, uint256 claimedAssets);

    /// @notice Emitted when assets are successfully withdrawn and transferred to a recipient.
    event Claimed(address indexed account, address indexed recipient, uint256 amount);

    /// @notice Emitted when pending assets are successfully transferred from one account to another.
    event Transfer(address indexed from, address indexed to, uint256 indexed epoch, uint256 amount);
}
"
    },
    "src/interfaces/tokens/IWSTETH.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "./ISTETH.sol";

interface IWSTETH is IERC20 {
    function wrap(uint256 _stETHAmount) external returns (uint256 wstETHAmount);
    function unwrap(uint256 _wstETHAmount) external returns (uint256 stETHAmount);

    function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);
    function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256);

    function stETH() external view returns (ISTETH);
}
"
    },
    "src/strategies/RatiosStrategy.sol": {
      "content": "// // SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/strategies/IRatiosStrategy.sol";

contract RatiosStrategy is IRatiosStrategy {
    /// @inheritdoc IRatiosStrategy
    uint256 public constant D18 = 1e18;
    /// @inheritdoc IRatiosStrategy
    bytes32 public constant RATIOS_STRATEGY_SET_RATIOS_ROLE =
        keccak256("RATIOS_STRATEGY_SET_RATIOS_ROLE");

    /// @inheritdoc IRatiosStrategy
    mapping(address vault => mapping(address subvault => Ratio)) public ratios;

    /// @inheritdoc IRatiosStrategy
    function setRatios(address vault, address[] calldata subvaults, Ratio[] calldata ratios_)
        external
    {
        require(
            IAccessControl(vault).hasRole(RATIOS_STRATEGY_SET_RATIOS_ROLE, msg.sender),
            "RatiosStrategy: forbidden"
        );
        require(
            subvaults.length == ratios_.length,
            "RatiosStrategy: subvaults and ratios length mismatch"
        );
        IMultiVault multiVault = IMultiVault(vault);
        uint256 n = subvaults.length;
        for (uint256 i = 0; i < n; i++) {
            if (multiVault.indexOfSubvault(subvaults[i]) != 0) {
                require(
                    ratios_[i].minRatioD18 <= ratios_[i].maxRatioD18
                        && ratios_[i].maxRatioD18 <= D18,
                    "RatiosStrategy: invalid ratios"
                );
            } else {
                require(
                    ratios_[i].minRatioD18 == 0 && ratios_[i].maxRatioD18 == 0,
                    "RatiosStrategy: invalid subvault"
                );
            }
        }
        mapping(address => Ratio) storage vaultRatios_ = ratios[vault];
        for (uint256 i = 0; i < n; i++) {
            vaultRatios_[subvaults[i]] = ratios_[i];
        }

        emit RatiosSet(vault, subvaults, ratios_);
    }

    /// @inheritdoc IRatiosStrategy
    function calculateState(address vault, bool isDeposit, uint256 increment)
        public
        view
        returns (Amounts[] memory state, uint256 liquid)
    {
        IMultiVault multiVault = IMultiVault(vault);
        uint256 n = multiVault.subvaultsCount();
        state = new Amounts[](n);

        liquid = IERC20(IERC4626(vault).asset()).balanceOf(vault);
        IDefaultCollateral collateral = multiVault.defaultCollateral();
        if (address(collateral) != address(0)) {
            liquid += collateral.balanceOf(vault);
        }
        uint256 totalAssets = liquid;
        IMultiVaultStorage.Subvault memory subvault;
        for (uint256 i = 0; i < n; i++) {
            subvault = multiVault.subvaultAt(i);
            IProtocolAdapter adapter = multiVault.adapterOf(subvault.protocol);
            state[i].staked = adapter.stakedAt(subvault.vault);
            if (!isDeposit && adapter.areWithdrawalsPaused(subvault.vault, vault)) {
                revert("RatiosStrategy: withdrawals paused");
            }
            if (subvault.withdrawalQueue != address(0)) {
                state[i].claimable =
                    IWithdrawalQueue(subvault.withdrawalQueue).claimableAssetsOf(vault);
                state[i].pending = IWithdrawalQueue(subvault.withdrawalQueue).pendingAssetsOf(vault);
                totalAssets += state[i].staked + state[i].pending + state[i].claimable;
            } else {
                totalAssets += state[i].staked;
            }
            uint256 maxDeposit = adapter.maxDeposit(subvault.vault);
            if (type(uint256).max - state[i].staked > maxDeposit) {
                state[i].max = maxDeposit + state[i].staked;
            } else {
                state[i].max = type(uint256).max;
            }
        }
        totalAssets = isDeposit ? totalAssets + increment : totalAssets - increment;
        mapping(address => Ratio) storage vaultRatios_ = ratios[vault];
        for (uint256 i = 0; i < n; i++) {
            Ratio memory ratio = vaultRatios_[multiVault.subvaultAt(i).vault];
            if (ratio.maxRatioD18 == 0) {
                state[i].max = 0;
                state[i].min = 0;
            } else {
                state[i].max = Math.min(state[i].max, (totalAssets * ratio.maxRatioD18) / D18);
                state[i].min = Math.min(state[i].max, (totalAssets * ratio.minRatioD18) / D18);
            }
        }
    }

    /// @inheritdoc IDepositStrategy
    function calculateDepositAmounts(address vault, uint256 amount)
        external
        view
        override
        returns (DepositData[] memory data)
    {
        (Amounts[] memory state,) = calculateState(vault, true, amount);
        uint256 n = state.length;
        for (uint256 i = 0; i < n; i++) {
            uint256 assets_ = state[i].staked;
            if (state[i].min > assets_) {
                state[i].min -= assets_;
                state[i].max -= assets_;
            } else if (state[i].max > assets_) {
                state[i].min = 0;
                state[i].max -= assets_;
            } else {
                state[i].min = 0;
                state[i].max = 0;
            }
        }
        data = new DepositData[](n);
        for (uint256 i = 0; i < n && amount != 0; i++) {
            data[i].subvaultIndex = i;
            if (state[i].min == 0) {
                continue;
            }
            uint256 assets_ = Math.min(state[i].min, amount);
            state[i].max -= assets_;
            amount -= assets_;
            data[i].deposit = assets_;
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].max == 0) {
                continue;
            }
            uint256 assets_ = Math.min(state[i].max, amount);
            amount -= assets_;
            data[i].deposit += assets_;
        }
        uint256 count = 0;
        for (uint256 i = 0; i < n; i++) {
            if (data[i].deposit != 0) {
                if (count != i) {
                    data[count] = data[i];
                }
                count++;
            }
        }
        assembly {
            mstore(data, count)
        }
    }

    /// @inheritdoc IWithdrawalStrategy
    function calculateWithdrawalAmounts(address vault, uint256 amount)
        external
        view
        override
        returns (WithdrawalData[] memory data)
    {
        (Amounts[] memory state, uint256 liquid) = calculateState(vault, false, amount);
        if (amount <= liquid) {
            return data;
        }
        amount -= liquid;
        uint256 n = state.length;
        data = new WithdrawalData[](n);
        for (uint256 i = 0; i < n && amount != 0; i++) {
            data[i].subvaultIndex = i;
            if (state[i].staked > state[i].max) {
                uint256 extra = state[i].staked - state[i].max;
                if (extra > amount) {
                    data[i].staked = amount;
                    amount = 0;
                } else {
                    data[i].staked = extra;
                    amount -= extra;
                }
                state[i].staked -= data[i].staked;
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].staked > state[i].min) {
                uint256 allowed = state[i].staked - state[i].min;
                if (allowed > amount) {
                    data[i].staked += amount;
                    amount = 0;
                } else {
                    data[i].staked += allowed;
                    amount -= allowed;
                    state[i].staked -= allowed;
                }
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].pending > 0) {
                if (state[i].pending > amount) {
                    data[i].pending += amount;
                    amount = 0;
                } else {
                    data[i].pending += state[i].pending;
                    amount -= state[i].pending;
                }
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            if (state[i].claimable > 0) {
                if (state[i].claimable > amount) {
                    data[i].claimable += amount;
                    amount = 0;
                } else {
                    data[i].claimable += state[i].claimable;
                    amount -= state[i].claimable;
                }
            }
        }
        for (uint256 i = 0; i < n && amount != 0; i++) {
            uint256 staked = state[i].staked;
            if (staked > 0) {
                if (staked > amount) {
                    data[i].staked += amount;
                    amount = 0;
                } else {
                    data[i].staked += staked;
                    amount -= staked;
                }
            }
        }

        uint256 count = 0;
        for (uint256 i = 0; i < n; i++) {
            if (data[i].staked + data[i].pending + data[i].claimable != 0) {
                if (count != i) {
                    data[count] = data[i];
                }
                count++;
            }
        }
        assembly {
            mstore(data, count)
        }
    }

    /// @inheritdoc IRebalanceStrategy
    function calculateRebalanceAmounts(address vault)
        external
        view
        override
        returns (RebalanceData[] memory data)
    {
        (Amounts[] memory state, uint256 liquid) = calculateState(vault, false, 0);
        uint256 n = state.length;
        data = new RebalanceData[](n);
        uint256 totalRequired = 0;
        uint256 pending = 0;
        for (uint256 i = 0; i < n; i++) {
            data[i].subvaultIndex = i;
            data[i].claimable = state[i].claimable;
            liquid += state[i].claimable;
            pending += state[i].pending;
            if (state[i].staked > state[i].max) {
                data[i].staked = state[i].staked - state[i].max;
                pending += data[i].staked;
                state[i].staked = state[i].max;
            }
            if (state[i].min > state[i].staked) {
                totalRequired += state[i].min - state[i].staked;
            }
        }

        if (totalRequired > liquid + pending) {
            uint256 unstake = totalRequired - liquid - pending;
            for (uint256 i = 0; i < n && unstake > 0; i++) {
                if (state[i].staked > state[i].min) {
                    uint256 allowed = state[i].staked - state[i].min;
                    if (allowed > unstake) {
                        data[i].staked += unstake;
                        unstake = 0;
                    } else {
                        data[i].staked += allowed;
                        unstake -= allowed;
                    }
                }
            }
        }

        for (uint256 i = 0; i < n && liquid > 0; i++) {
            if (state[i].staked < state[i].min) {
                uint256 required = state[i].min - state[i].staked;
                if (required > liquid) {
                    data[i].deposit = liquid;
                    liquid = 0;
                } else {
                    data[i].deposit = required;
                    liquid -= required;
                    state[i].max -= data[i].deposit;
                }
            }
        }

        for (uint256 i = 0; i < n && liquid > 0; i++) {
            if (state[i].staked < state[i].max) {
                uint256 allowed = state[i].max - state[i].staked;
                if (allowed > liquid) {
                    data[i].deposit += liquid;
                    liquid = 0;
                } else {
                    data[i].deposit += allowed;
                    liquid -= allowed;
                }
            }
        }
    }
}
"
    },
    "src/vaults/MultiVault.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/vaults/IMultiVault.sol";
import {ERC4626Vault} from "./ERC4626Vault.sol";
import {MultiVaultStorage} from "./MultiVaultStorage.sol";
import {VaultControlStorage} from "./VaultControlStorage.sol";

contract MultiVault is IMultiVault, ERC4626Vault, MultiVaultStorage {
    using SafeERC20 for IERC20;
    using Math for uint256;

    uint256 private constant D6 = 1e6;
    bytes32 public constant ADD_SUBVAULT_ROLE = keccak256("ADD_SUBVAULT_ROLE");
    bytes32 public constant REMOVE_SUBVAULT_ROLE = keccak256("REMOVE_SUBVAULT_ROLE");
    bytes32 public constant SET_STRATEGY_ROLE = keccak256("SET_STRATEGY_ROLE");
    bytes32 public constant SET_FARM_ROLE = keccak256("SET_FARM_ROLE");
    bytes32 public constant REBALANCE_ROLE = keccak256("REBALANCE_ROLE");
    bytes32 public constant SET_DEFAULT_COLLATERAL_ROLE = keccak256("SET_DEFAULT_COLLATERAL_ROLE");
    bytes32 public constant SET_ADAPTER_ROLE = keccak256("SET_ADAPTER_ROLE");

    constructor(bytes32 name_, uint256 version_)
        VaultControlStorage(name_, version_)
        MultiVaultStorage(name_, version_)
    {
        _disableInitializers();
    }

    // ------------------------------- EXTERNAL VIEW FUNCTIONS -------------------------------

    /// @inheritdoc IERC4626
    function totalAssets()
        public
        view
        virtual
        override(IERC4626, ERC4626Upgradeable)
        returns (uint256 assets_)
    {
        address this_ = address(this);
        assets_ = IERC20(asset()).balanceOf(this_);
        IDefaultCollateral collateral = defaultCollateral();
        if (address(collateral) != address(0)) {
            assets_ += collateral.balanceOf(this_);
        }

        uint256 length = subvaultsCount();
        Subvault memory subvault;
        for (uint256 i = 0; i < length; i++) {
            subvault = subvaultAt(i);
            assets_ += adapterOf(subvault.protocol).stakedAt(subvault.vault);
            if (subvault.withdrawalQueue != address(0)) {
                assets_ += IWithdrawalQueue(subvault.withdrawalQueue).claimableAssetsOf(this_)
                    + IWithdrawalQueue(subvault.withdrawalQueue).pendingAssetsOf(this_);
            }
        }
    }

    // ------------------------------- EXTERNAL MUTATIVE FUNCTIONS -------------------------------

    /// @inheritdoc IMultiVault
    function initialize(InitParams calldata initParams) public virtual reinitializer(2) {
        __initializeERC4626(
            initParams.admin,
            initParams.limit,
            initParams.depositPause,
            initParams.withdrawalPause,
            initParams.depositWhitelist,
            initParams.asset,
            initParams.name,
            initParams.symbol
        );
        __initializeMultiVaultStorage(
            initParams.depositStrategy,
            initParams.withdrawalStrategy,
            initParams.rebalanceStrategy,
            initParams.defaultCollateral,
            initParams.symbioticAdapter,
            initParams.eigenLayerAdapter,
            initParams.erc4626Adapter
        );
        require(
            initParams.defaultCollateral == address(0)
                || IDefaultCollateral(initParams.defaultCollateral).asset() == initParams.asset,
            "MultiVault: default collateral asset does not match the vault asset"
        );
    }

    /// @inheritdoc IMultiVault
    function addSubvault(address vault, Protocol protocol) external onlyRole(ADD_SUBVAULT_ROLE) {
        IProtocolAdapter adapter = adapterOf(protocol);
        require(
            adapter.assetOf(vault) == asset(),
            "MultiVault: subvault asset does not match the vault asset"
        );
        _addSubvault(vault, adapter.handleVault(vault), protocol);
    }

    /// @inheritdoc IMultiVault
    function removeSubvault(address subvault) external onlyRole(REMOVE_SUBVAULT_ROLE) {
        _removeSubvault(subvault);
    }

    /// @inheritdoc IMultiVault
    function setDepositStrategy(address newDepositStrategy) external onlyRole(SET_STRATEGY_ROLE) {
        require(
            newDepositStrategy != address(0), "MultiVault: deposit strategy cannot be zero address"
        );
        _setDepositStrategy(newDepositStrategy);
    }

    /// @inheritdoc IMultiVault
    function setWithdrawalStrategy(address newWithdrawalStrategy)
        external
        onlyRole(SET_STRATEGY_ROLE)
    {
        require(
            newWithdrawalStrategy != address(0),
            "MultiVault: withdrawal strategy cannot be zero address"
        );
        _setWithdrawalStrategy(newWithdrawalStrategy);
    }

    /// @inheritdoc IMultiVault
    function setRebalanceStrategy(address newRebalanceStrategy)
        external
        onlyRole(SET_STRATEGY_ROLE)
    {
        require(
            newRebalanceStrategy != address(0),
            "MultiVault: rebalance strategy cannot be zero address"
        );
        _setRebalanceStrategy(newRebalanceStrategy);
    }

    /// @inheritdoc IMultiVault
    function setDefaultCollateral(address defaultCollateral_)
        external
        onlyRole(SET_DEFAULT_COLLATERAL_ROLE)
    {
        require(
            address(defaultCollateral()) == address(0) && defaultCollateral_ != address(0),
            "MultiVault: default collateral already set or cannot be zero address"
        );
        require(
            IDefaultCollateral(defaultCollateral_).asset() == asset(),
            "MultiVault: default collateral asset does not match the vault asset"
        );
        _setDefaultCollateral(defaultCollateral_);
    }

    /// @inheritdoc IMultiVault
    function setSymbioticAdapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
        require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
        _setSymbioticAdapter(adapter_);
    }

    /// @inheritdoc IMultiVault
    function setEigenLayerAdapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
        require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
        _setEigenLayerAdapter(adapter_);
    }

    /// @inheritdoc IMultiVault
    function setERC4626Adapter(address adapter_) external onlyRole(SET_ADAPTER_ROLE) {
        require(adapter_ != address(0), "MultiVault: adapter cannot be zero address");
        _setERC4626Adapter(adapter_);
    }

    /// @inheritdoc IMultiVault
    function setRewardsData(uint256 farmId, RewardData calldata rewardData)
        external
        onlyRole(SET_FARM_ROLE)
    {
        if (rewardData.token != address(0)) {
            require(
                rewardData.token != asset() && rewardData.token != address(defaultCollateral()),
                "MultiVault: reward token cannot be the same as the asset or default collateral"
            );
            require(rewardData.curatorFeeD6 <= D6, "MultiVault: curator fee exceeds 100%");
            require(
                rewardData.distributionFarm != address(0),
                "MultiVault: distribution farm address cannot be zero"
            );
            if (rewardData.curatorFeeD6 != 0) {
                require(
                    rewardData.curatorTreasury != address(0),
                    "MultiVault: curator treasury address cannot be zero when fee is set"
                );
            }
            adapterOf(rewardData.protocol).validateRewardData(rewardData.data);
        }
        _setRewardData(farmId, rewardData);
    }

    /// @inheritdoc IMultiVault
    function rebalance() external onlyRole(REBALANCE_ROLE) {
        address this_ = address(this);
        IRebalanceStrategy.RebalanceData[] memory data =
            rebalanceStrategy().calculateRebalanceAmounts(this_);
        for (uint256 i = 0; i < data.length; i++) {
            _withdraw(data[i].subvaultIndex, data[i].staked, 0, data[i].claimable, this_, this_);
        }
        IDefaultCollateral collateral = defaultCollateral();
        if (address(collateral) != address(0)) {
            uint256 balance = collateral.balanceOf(this_);
            if (balance != 0) {
                collateral.withdraw(this_, balance);
            }
        }
        for (uint256 i = 0; i < data.length; i++) {
            _deposit(data[i].subvaultIndex, data[i].deposit);
        }
        _depositIntoCollateral();
        emit Rebalance(data, block.timestamp);
    }

    /// @inheritdoc IMultiVault
    function pushRewards(uint256 farmId, bytes calldata farmData) external nonReentrant {
        require(farmIdsContains(farmId), "MultiVault: farm not found");
        IMultiVaultStorage.RewardData memory data = rewardData(farmId);
        IERC20 rewardToken = IERC20(data.token);

        address this_ = address(this);
        uint256 rewardAmount = rewardToken.balanceOf(this_);

        Address.functionDelegateCall(
            address(adapterOf(data.protocol)),
            abi.encodeCall(
                IProtocolAdapter.pushRewards, (address(rewardToken), farmData, data.data)
            )
        );

        rewardAmount = rewardToken.balanceOf(this_) - rewardAmount;
        if (rewardAmount == 0) {
            return;
        }

        uint256 curatorFee = rewardAmount.mulDiv(data.curatorFeeD6, D6);
        if (curatorFee != 0) {
            rewardToken.safeTransfer(data.curatorTreasury, curatorFee);
        }
        rewardAmount = rewardAmount - curatorFee;
        if (rewardAmount != 0) {
            rewardToken.safeTransfer(data.distributionFarm, rewardAmount);
        }
        emit RewardsPushed(farmId, rewardAmount, curatorFee, block.timestamp);
    }

    // ------------------------------- INTERNAL MUTATIVE FUNCTIONS -------------------------------

    /// @dev Deposits assets into the specified subvault
    function _deposit(uint256 subvaultIndex, uint256 assets) private {
        if (assets == 0) {
            return;
        }
        Subvault memory subvault = subvaultAt(subvaultIndex);
        Address.functionDelegateCall(
            address(adapterOf(subvault.protocol)),
            abi.encodeCall(IProtocolAdapter.deposit, (subvault.vault, assets))
        );
    }

    /// @dev Withdraws assets from the specified subvault
    function _withdraw(
        uint256 subvaultIndex,
        uint256 request,
        uint256 pending,
        uint256 claimable,
        address owner,
        address receiver
    ) private {
        Subvault memory subvault = subvaultAt(subvaultIndex);
        address this_ = address(this);
        if (claimable != 0) {
            IWithdrawalQueue(subvault.withdrawalQueue).claim(this_, receiver, claimable);
        }
        if (pending != 0) {
            IWithdrawalQueue(subvault.withdrawalQueue).transferPendingAssets(receiver, pending);
        }
        if (request != 0) {
            Address.functionDelegateCall(
                address(adapterOf(subvault.protocol)),
                abi.encodeCall(
                    IProtocolAdapter.withdraw,
                    (subvault.vault, subvault.withdrawalQueue, receiver, request, owner)
                )
            );
        }
    }

    /// @dev Deposits assets into the default collateral
    function _depositIntoCollateral() private {
        IDefaultCollateral collateral = defaultCollateral();
        if (address(collateral) == address(0)) {
            return;
        }
        uint256 limit_ = collateral.limit();
        uint256 supply_ = collateral.totalSupply();
        if (supply_ >= limit_) {
            return;
        }
        address this_ = address(this);
        IERC20 asset_ = IERC20(asset());
        uint256 assets = asset_.balanceOf(this_).min(limit_ - supply_);
        if (assets == 0) {
            return;
        }
        asset_.safeIncreaseAllowance(address(collateral), assets);
        collateral.deposit(this_, assets);
        emit DepositIntoCollateral(assets);
    }

    function _deposit(address caller, address receiver, uint256 assets, uint256 shares)
        internal
        virtual
        override
    {
        address this_ = address(this);
        IDepositStrategy.DepositData[] memory data =
            depositStrategy().calculateDepositAmounts(this_, assets);
        super._deposit(caller, receiver, assets, shares);
        for (uint256 i = 0; i < data.length; i++) {
            if (data[i].deposit != 0) {
                _deposit(data[i].subvaultIndex, data[i].deposit);
                assets -= data[i].deposit;
            }
        }

        _depositIntoCollateral();
    }

    function _withdraw(
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    ) internal virtual override {
        address this_ = address(this);

        IWithdrawalStrategy.WithdrawalData[] memory data =
            withdrawalStrategy().calculateWithdrawalAmounts(this_, assets);

        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

        _burn(owner, shares);

        uint256 liquid = assets;
        IWithdrawalStrategy.WithdrawalData memory d;
        for (uint256 i = 0; i < data.length; i++) {
            d = data[i];
            _withdraw(d.subvaultIndex, d.staked, d.pending, d.claimable, owner, receiver);
            liquid -= d.staked + d.pending + d.claimable;
        }

        if (liquid != 0) {
            IERC20 asset_ = IERC20(asset());
            uint256 balance = asset_.balanceOf(this_);
            if (balance < liquid) {
                if (balance != 0) {
                    asset_.safeTransfer(receiver, balance);
                    liquid -= balance;
                }
                defaultCollateral().withdraw(receiver, liquid);
            } else 

Tags:
ERC20, ERC165, Multisig, Mintable, Pausable, Swap, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x8bf71e2f25b54f86fd10a87755863ac2aaaa7f0f|verified:true|block:23647515|tx:0x48014ac901cd44301eef59a1dbd48a16a4c0659ed116f297631df13464cd328c|first_check:1761329737

Submitted on: 2025-10-24 20:15:38

Comments

Log in to comment.

No comments yet.