Contract Diff Checker

Contract Name:
FactoryLib

Contract Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "./CommonLib.sol";
import "./VaultTypeLib.sol";
import "../../interfaces/IPlatform.sol";
import "../../interfaces/IStrategy.sol";
import "../../interfaces/ISwapper.sol";
import "../../interfaces/IFactory.sol";
import "../../interfaces/IPriceReader.sol";
import "../../interfaces/IVault.sol";
import "../../interfaces/IRVault.sol";
import "../../interfaces/IVaultProxy.sol";
import "../../interfaces/IStrategyProxy.sol";

library FactoryLib {
    using SafeERC20 for IERC20;
    using EnumerableSet for EnumerableSet.Bytes32Set;

    uint public constant BOOST_REWARD_DURATION = 86400 * 30;

    struct WhatToBuildVars {
        bytes32[] strategyIdHashes;
        uint strategyIdHashesLen;
        IFactory.Farm[] farms;
        uint farmsLen;
        string[] vaultTypes;
        uint vaultTypesLen;
        // getVaultInitParamsVariants returns
        string[] vaultType;
        uint[] usedAddresses;
        uint[] usedNums;
        address[] allVaultInitAddresses;
        uint[] allVaultInitNums;
        uint allVaultInitAddressesIndex;
        uint allVaultInitNumsIndex;
        // strategy initVariants returns
        string[] strategyVariantDesc;
        uint strategyVariantDescLen;
        address[] allStrategyInitAddresses;
        uint[] allStrategyInitNums;
        int24[] allStrategyInitTicks;
        uint allStrategyInitAddressesIndex;
        uint allStrategyInitNumsIndex;
        uint allStrategyInitTicksIndex;
        // target vault init params
        address[] vaultInitAddresses;
        uint[] vaultInitNums;
        // target strategy init params
        address[] strategyInitAddresses;
        uint[] strategyInitNums;
        int24[] strategyInitTicks;
        uint usedStrategyInitAddresses;
        uint usedStrategyInitNums;
        uint usedStrategyInitTicks;
        // total results, used for counters too
        uint total;
        uint totalVaultInitAddresses;
        uint totalVaultInitNums;
        uint totalStrategyInitAddresses;
        uint totalStrategyInitNums;
        uint totalStrategyInitTicks;
        // counters and length
        uint i;
        uint j;
        uint c;
    }

    struct VaultPostDeployVars {
        bool isRewardingVaultType;
        uint minInitialBoostDuration;
        uint minInitialBoostPerDay;
    }

    struct GetVaultInitParamsVariantsVars {
        string[] vaultTypes;
        uint total;
        uint totalVaultInitAddresses;
        uint totalVaultInitNums;
        uint len;
    }

    function whatToBuild(address platform)
        external
        view
        returns (
            string[] memory desc,
            string[] memory vaultType,
            string[] memory strategyId,
            uint[10][] memory initIndexes,
            address[] memory vaultInitAddresses,
            uint[] memory vaultInitNums,
            address[] memory strategyInitAddresses,
            uint[] memory strategyInitNums,
            int24[] memory strategyInitTicks
        )
    {
        WhatToBuildVars memory vars;

        IFactory factory = IFactory(IPlatform(platform).factory());
        vars.strategyIdHashes = factory.strategyLogicIdHashes();
        vars.strategyIdHashesLen = vars.strategyIdHashes.length;
        vars.farms = factory.farms();
        vars.farmsLen = vars.farms.length;

        for (; vars.i < vars.strategyIdHashesLen; ++vars.i) {
            IFactory.StrategyLogicConfig memory strategyConfig;
            //slither-disable-next-line unused-return
            strategyConfig = factory.strategyLogicConfig(vars.strategyIdHashes[vars.i]);

            if (strategyConfig.deployAllowed) {
                (vars.vaultType, vars.usedAddresses, vars.usedNums, vars.allVaultInitAddresses, vars.allVaultInitNums) =
                    _getVaultInitParamsVariants(platform, strategyConfig.implementation);
                // nosemgrep
                vars.vaultTypesLen = vars.vaultType.length;

                vars.allVaultInitAddressesIndex = 0;
                vars.allVaultInitNumsIndex = 0;
                // nosemgrep
                for (uint k; k < vars.vaultTypesLen; ++k) {
                    vars.vaultInitAddresses = new address[](vars.usedAddresses[k]);
                    vars.vaultInitNums = new uint[](vars.usedNums[k]);
                    // nosemgrep
                    for (uint j; j < vars.usedAddresses[k]; ++j) {
                        vars.vaultInitAddresses[j] = vars.allVaultInitAddresses[vars.allVaultInitAddressesIndex];
                        ++vars.allVaultInitAddressesIndex;
                    }
                    // nosemgrep
                    for (uint j; j < vars.usedNums[k]; ++j) {
                        vars.vaultInitNums[j] = vars.allVaultInitNums[vars.allVaultInitNumsIndex];
                        ++vars.allVaultInitNumsIndex;
                    }

                    (
                        vars.strategyVariantDesc,
                        vars.allStrategyInitAddresses,
                        vars.allStrategyInitNums,
                        vars.allStrategyInitTicks
                    ) = IStrategy(strategyConfig.implementation).initVariants(platform);
                    vars.allStrategyInitAddressesIndex = 0;
                    vars.allStrategyInitNumsIndex = 0;
                    vars.allStrategyInitTicksIndex = 0;
                    // nosemgrep
                    uint len = vars.strategyVariantDesc.length;
                    for (vars.j = 0; vars.j < len; ++vars.j) {
                        // nosemgrep
                        uint size = vars.allStrategyInitAddresses.length / len;
                        vars.usedStrategyInitAddresses = 0;
                        vars.usedStrategyInitNums = 0;
                        vars.usedStrategyInitTicks = 0;
                        vars.strategyInitAddresses = new address[](size);
                        // nosemgrep
                        for (uint c; c < size; ++c) {
                            vars.strategyInitAddresses[c] =
                                vars.allStrategyInitAddresses[vars.allStrategyInitAddressesIndex];
                            ++vars.allStrategyInitAddressesIndex;
                            ++vars.usedStrategyInitAddresses;
                        }
                        // nosemgrep
                        size = vars.allStrategyInitNums.length / len;
                        vars.strategyInitNums = new uint[](size);
                        // nosemgrep
                        for (uint c; c < size; ++c) {
                            vars.strategyInitNums[c] = vars.allStrategyInitNums[vars.allStrategyInitNumsIndex];
                            ++vars.allStrategyInitNumsIndex;
                            ++vars.usedStrategyInitNums;
                        }
                        // nosemgrep
                        size = vars.allStrategyInitTicks.length / len;
                        vars.strategyInitTicks = new int24[](size);
                        // nosemgrep
                        for (uint c; c < size; ++c) {
                            vars.strategyInitTicks[c] = vars.allStrategyInitTicks[vars.allStrategyInitTicksIndex];
                            ++vars.allStrategyInitTicksIndex;
                            ++vars.usedStrategyInitTicks;
                        }

                        bytes32 _deploymentKey = getDeploymentKey(
                            vars.vaultType[k],
                            strategyConfig.id,
                            vars.vaultInitAddresses,
                            vars.vaultInitNums,
                            vars.strategyInitAddresses,
                            vars.strategyInitNums,
                            vars.strategyInitTicks,
                            [1, 0, 1, 1, 0]
                        );

                        if (factory.deploymentKey(_deploymentKey) == address(0)) {
                            ++vars.total;
                            vars.totalVaultInitAddresses += vars.usedAddresses[k];
                            vars.totalVaultInitNums += vars.usedNums[k];
                            vars.totalStrategyInitAddresses += vars.usedStrategyInitAddresses;
                            vars.totalStrategyInitNums += vars.usedStrategyInitNums;
                            vars.totalStrategyInitTicks += vars.usedStrategyInitTicks;
                        }
                    }
                }
            }
        }

        desc = new string[](vars.total);
        vaultType = new string[](vars.total);
        strategyId = new string[](vars.total);
        initIndexes = new uint[10][](vars.total);
        vaultInitAddresses = new address[](vars.totalVaultInitAddresses);
        vaultInitNums = new uint[](vars.totalVaultInitNums);
        strategyInitAddresses = new address[](vars.totalStrategyInitAddresses);
        strategyInitNums = new uint[](vars.totalStrategyInitNums);
        strategyInitTicks = new int24[](vars.totalStrategyInitTicks);

        vars.total = 0;
        vars.totalVaultInitAddresses = 0;
        vars.totalVaultInitNums = 0;
        vars.totalStrategyInitAddresses = 0;
        vars.totalStrategyInitNums = 0;
        vars.totalStrategyInitTicks = 0;
        for (vars.i = 0; vars.i < vars.strategyIdHashesLen; ++vars.i) {
            IFactory.StrategyLogicConfig memory strategyConfig;
            //slither-disable-next-line unused-return
            strategyConfig = factory.strategyLogicConfig(vars.strategyIdHashes[vars.i]);
            if (strategyConfig.deployAllowed) {
                (vars.vaultType, vars.usedAddresses, vars.usedNums, vars.allVaultInitAddresses, vars.allVaultInitNums) =
                    _getVaultInitParamsVariants(platform, strategyConfig.implementation);
                // nosemgrep
                vars.vaultTypesLen = vars.vaultType.length;

                vars.allVaultInitAddressesIndex = 0;
                vars.allVaultInitNumsIndex = 0;
                // nosemgrep
                for (uint k; k < vars.vaultTypesLen; ++k) {
                    vars.vaultInitAddresses = new address[](vars.usedAddresses[k]);
                    vars.vaultInitNums = new uint[](vars.usedNums[k]);
                    // nosemgrep
                    for (uint j; j < vars.usedAddresses[k]; ++j) {
                        vars.vaultInitAddresses[j] = vars.allVaultInitAddresses[vars.allVaultInitAddressesIndex];
                        ++vars.allVaultInitAddressesIndex;
                    }
                    // nosemgrep
                    for (uint j; j < vars.usedNums[k]; ++j) {
                        vars.vaultInitNums[j] = vars.allVaultInitNums[vars.allVaultInitNumsIndex];
                        ++vars.allVaultInitNumsIndex;
                    }

                    (
                        vars.strategyVariantDesc,
                        vars.allStrategyInitAddresses,
                        vars.allStrategyInitNums,
                        vars.allStrategyInitTicks
                    ) = IStrategy(strategyConfig.implementation).initVariants(platform);
                    vars.allStrategyInitAddressesIndex = 0;
                    vars.allStrategyInitNumsIndex = 0;
                    vars.allStrategyInitTicksIndex = 0;
                    // nosemgrep
                    vars.strategyVariantDescLen = vars.strategyVariantDesc.length;
                    for (vars.j = 0; vars.j < vars.strategyVariantDescLen; ++vars.j) {
                        // nosemgrep
                        uint size = vars.allStrategyInitAddresses.length / vars.strategyVariantDescLen;
                        vars.usedStrategyInitAddresses = 0;
                        vars.usedStrategyInitNums = 0;
                        vars.usedStrategyInitTicks = 0;
                        vars.strategyInitAddresses = new address[](size);
                        // nosemgrep
                        for (uint c; c < size; ++c) {
                            vars.strategyInitAddresses[c] =
                                vars.allStrategyInitAddresses[vars.allStrategyInitAddressesIndex];
                            ++vars.allStrategyInitAddressesIndex;
                            ++vars.usedStrategyInitAddresses;
                        }
                        // nosemgrep
                        size = vars.allStrategyInitNums.length / vars.strategyVariantDescLen;
                        vars.strategyInitNums = new uint[](size);
                        // nosemgrep
                        for (uint c; c < size; ++c) {
                            vars.strategyInitNums[c] = vars.allStrategyInitNums[vars.allStrategyInitNumsIndex];
                            ++vars.allStrategyInitNumsIndex;
                            ++vars.usedStrategyInitNums;
                        }
                        // nosemgrep
                        size = vars.allStrategyInitTicks.length / vars.strategyVariantDescLen;
                        vars.strategyInitTicks = new int24[](size);
                        // nosemgrep
                        for (uint c; c < size; ++c) {
                            vars.strategyInitTicks[c] = vars.allStrategyInitTicks[vars.allStrategyInitTicksIndex];
                            ++vars.allStrategyInitTicksIndex;
                            ++vars.usedStrategyInitTicks;
                        }

                        bytes32 _deploymentKey = getDeploymentKey(
                            vars.vaultType[k],
                            strategyConfig.id,
                            vars.vaultInitAddresses,
                            vars.vaultInitNums,
                            vars.strategyInitAddresses,
                            vars.strategyInitNums,
                            vars.strategyInitTicks,
                            [1, 0, 1, 1, 0]
                        );

                        if (factory.deploymentKey(_deploymentKey) == address(0)) {
                            desc[vars.total] = vars.strategyVariantDesc[vars.j];
                            vaultType[vars.total] = vars.vaultType[k];
                            strategyId[vars.total] = strategyConfig.id;

                            initIndexes[vars.total][0] = vars.totalVaultInitAddresses;
                            initIndexes[vars.total][1] = vars.totalVaultInitAddresses + vars.usedAddresses[k];
                            initIndexes[vars.total][2] = vars.totalVaultInitNums;
                            initIndexes[vars.total][3] = vars.totalVaultInitNums + vars.usedNums[k];
                            initIndexes[vars.total][4] = vars.totalStrategyInitAddresses;
                            initIndexes[vars.total][5] =
                                vars.totalStrategyInitAddresses + vars.usedStrategyInitAddresses;
                            initIndexes[vars.total][6] = vars.totalStrategyInitNums;
                            initIndexes[vars.total][7] = vars.totalStrategyInitNums + vars.usedStrategyInitNums;
                            initIndexes[vars.total][8] = vars.totalStrategyInitTicks;
                            initIndexes[vars.total][9] = vars.totalStrategyInitTicks + vars.usedStrategyInitTicks;
                            // nosemgrep
                            for (uint c; c < vars.usedAddresses[k]; ++c) {
                                vaultInitAddresses[vars.totalVaultInitAddresses + c] = vars.vaultInitAddresses[c];
                            }
                            // nosemgrep
                            for (uint c; c < vars.usedNums[k]; ++c) {
                                vaultInitNums[vars.totalVaultInitNums + c] = vars.vaultInitNums[c];
                            }
                            // nosemgrep
                            for (uint c; c < vars.usedStrategyInitAddresses; ++c) {
                                strategyInitAddresses[vars.totalStrategyInitAddresses + c] =
                                    vars.strategyInitAddresses[c];
                            }
                            // nosemgrep
                            for (uint c; c < vars.usedStrategyInitNums; ++c) {
                                strategyInitNums[vars.totalStrategyInitNums + c] = vars.strategyInitNums[c];
                            }
                            // nosemgrep
                            for (uint c; c < vars.usedStrategyInitTicks; ++c) {
                                strategyInitTicks[vars.totalStrategyInitTicks + c] = vars.strategyInitTicks[c];
                            }

                            ++vars.total;
                            vars.totalVaultInitAddresses += vars.usedAddresses[k];
                            vars.totalVaultInitNums += vars.usedNums[k];
                            vars.totalStrategyInitAddresses += vars.usedStrategyInitAddresses;
                            vars.totalStrategyInitNums += vars.usedStrategyInitNums;
                            vars.totalStrategyInitTicks += vars.usedStrategyInitTicks;
                        }
                    }
                }
            }
        }
    }

    function _getVaultInitParamsVariants(
        address platform,
        address strategyImplementation
    )
        internal
        view
        returns (
            string[] memory vaultType,
            uint[] memory usedAddresses,
            uint[] memory usedNums,
            address[] memory allVaultInitAddresses,
            uint[] memory allVaultInitNums
        )
    {
        GetVaultInitParamsVariantsVars memory vars;
        vars.vaultTypes = IStrategy(strategyImplementation).supportedVaultTypes();
        vars.len = vars.vaultTypes.length;
        //slither-disable-next-line unused-return
        (address[] memory allowedBBTokens,) = IPlatform(platform).allowedBBTokenVaultsFiltered();
        uint allowedBBTokensLen = allowedBBTokens.length;
        // nosemgrep
        for (uint i; i < vars.len; ++i) {
            if (CommonLib.eq(vars.vaultTypes[i], VaultTypeLib.COMPOUNDING)) {
                ++vars.total;
            } else if (
                CommonLib.eq(vars.vaultTypes[i], VaultTypeLib.REWARDING)
                    || CommonLib.eq(vars.vaultTypes[i], VaultTypeLib.REWARDING_MANAGED)
            ) {
                vars.total += allowedBBTokensLen;
                vars.totalVaultInitAddresses += allowedBBTokensLen;
            }
        }
        vaultType = new string[](vars.total);
        usedAddresses = new uint[](vars.total);
        usedNums = new uint[](vars.total);
        allVaultInitAddresses = new address[](vars.totalVaultInitAddresses);
        allVaultInitNums = new uint[](vars.totalVaultInitNums); // now its always 0, but function can be upgraded without changing interface

        // vaultType index, allVaultInitAddresses index, allVaultInitNums index
        uint[3] memory indexes;
        // nosemgrep
        for (uint i; i < vars.len; ++i) {
            if (CommonLib.eq(vars.vaultTypes[i], VaultTypeLib.COMPOUNDING)) {
                vaultType[indexes[0]] = vars.vaultTypes[i];
                ++indexes[0];
            } else if (
                CommonLib.eq(vars.vaultTypes[i], VaultTypeLib.REWARDING)
                    || CommonLib.eq(vars.vaultTypes[i], VaultTypeLib.REWARDING_MANAGED)
            ) {
                // nosemgrep
                for (uint k; k < allowedBBTokensLen; ++k) {
                    vaultType[indexes[0]] = vars.vaultTypes[i];
                    allVaultInitAddresses[indexes[1]] = allowedBBTokens[k];
                    usedAddresses[indexes[0]] = 1;
                    ++indexes[0];
                    ++indexes[1];
                }
            }
        }
    }

    function getExchangeAssetIndex(
        address platform,
        address[] memory assets
    ) external view returns (uint exchangeAssetIndex) {
        address targetExchangeAsset = IPlatform(platform).targetExchangeAsset();
        uint len = assets.length;
        // nosemgrep
        for (uint i; i < len; ++i) {
            if (assets[i] == targetExchangeAsset) {
                return i;
            }
        }

        exchangeAssetIndex = type(uint).max;
        uint minRoutes = type(uint).max;
        ISwapper swapper = ISwapper(IPlatform(platform).swapper());
        // nosemgrep
        for (uint i; i < len; ++i) {
            //slither-disable-next-line unused-return
            (ISwapper.PoolData[] memory route,) = swapper.buildRoute(assets[i], targetExchangeAsset);
            // nosemgrep
            uint routeLength = route.length;
            if (routeLength < minRoutes) {
                minRoutes = routeLength;
                exchangeAssetIndex = i;
            }
        }
        if (exchangeAssetIndex == type(uint).max) {
            revert ISwapper.NoRouteFound();
        }
        if (exchangeAssetIndex > type(uint).max) revert ISwapper.NoRoutesForAssets();
    }

    function getName(
        string memory vaultType,
        string memory id,
        string memory symbols,
        string memory specificName,
        address[] memory vaultInitAddresses
    ) public view returns (string memory name) {
        name = string.concat("Stability ", symbols, " ", id);
        if (keccak256(bytes(specificName)) != keccak256(bytes(""))) {
            name = string.concat(name, " ", specificName);
        }
        if (keccak256(bytes(vaultType)) == keccak256(bytes(VaultTypeLib.REWARDING))) {
            name = string.concat(name, " ", IERC20Metadata(vaultInitAddresses[0]).symbol(), " reward");
        }
    }

    function getDeploymentKey(
        string memory vaultType,
        string memory strategyId,
        address[] memory initVaultAddresses,
        uint[] memory initVaultNums,
        address[] memory initStrategyAddresses,
        uint[] memory initStrategyNums,
        int24[] memory initStrategyTicks,
        uint8[5] memory usedValuesForKey
    ) public pure returns (bytes32) {
        uint key = uint(keccak256(abi.encodePacked(vaultType)));
        unchecked {
            key += uint(keccak256(abi.encodePacked(strategyId)));
        }

        uint i;
        uint len;

        // process initVaultAddresses
        len = initVaultAddresses.length;
        if (len > usedValuesForKey[0]) {
            len = usedValuesForKey[0];
        }
        for (; i < len; ++i) {
            unchecked {
                key += uint(uint160(initVaultAddresses[i]));
            }
        }

        // process initVaultNums
        len = initVaultNums.length;
        if (len > usedValuesForKey[1]) {
            len = usedValuesForKey[1];
        }
        for (i = 0; i < len; ++i) {
            unchecked {
                key += initVaultNums[i];
            }
        }

        // process initStrategyAddresses
        len = initStrategyAddresses.length;
        if (len > usedValuesForKey[2]) {
            len = usedValuesForKey[2];
        }
        for (i = 0; i < len; ++i) {
            unchecked {
                key += uint(uint160(initStrategyAddresses[i]));
            }
        }

        // process initStrategyNums
        len = initStrategyNums.length;
        if (len > usedValuesForKey[3]) {
            len = usedValuesForKey[3];
        }
        for (i = 0; i < len; ++i) {
            unchecked {
                key += initStrategyNums[i];
            }
        }

        // process initStrategyTicks
        len = initStrategyTicks.length;
        if (len > usedValuesForKey[4]) {
            len = usedValuesForKey[4];
        }
        for (i = 0; i < len; ++i) {
            unchecked {
                key += initStrategyTicks[i] >= 0 ? uint(int(initStrategyTicks[i])) : uint(-int(initStrategyTicks[i]));
            }
        }

        return bytes32(key);
    }

    function vaultPostDeploy(
        address platform,
        address vault,
        string memory vaultType,
        address[] memory vaultInitAddresses,
        uint[] memory vaultInitNums
    ) external {
        VaultPostDeployVars memory vars;
        vars.isRewardingVaultType = CommonLib.eq(vaultType, VaultTypeLib.REWARDING);
        if (vars.isRewardingVaultType || CommonLib.eq(vaultType, VaultTypeLib.REWARDING_MANAGED)) {
            IPlatform(platform).useAllowedBBTokenVault(vaultInitAddresses[0]);
            IPriceReader priceReader = IPriceReader(IPlatform(platform).priceReader());
            vars.minInitialBoostDuration = IPlatform(platform).minInitialBoostDuration();
            vars.minInitialBoostPerDay = IPlatform(platform).minInitialBoostPerDay();
            vaultInitAddresses = IRVault(vault).rewardTokens();
            uint boostTokensLen = vaultInitAddresses.length - 1;
            uint totalInitialBoostUsdPerDay;
            // nosemgrep
            for (uint i; i < boostTokensLen; ++i) {
                address token = vaultInitAddresses[1 + i];
                uint durationSeconds = vars.isRewardingVaultType ? BOOST_REWARD_DURATION : vaultInitNums[1 + i];
                if (durationSeconds < vars.minInitialBoostDuration) {
                    revert IFactory.BoostDurationTooLow();
                }
                uint initialNotifyAmount =
                    vars.isRewardingVaultType ? vaultInitNums[i] : vaultInitNums[1 + boostTokensLen + i];
                //slither-disable-next-line unused-return
                (uint price,) = priceReader.getPrice(token);
                totalInitialBoostUsdPerDay += (
                    ((((initialNotifyAmount * 1e18) / 10 ** IERC20Metadata(token).decimals()) * price) / 1e18) * 86400
                ) / durationSeconds;
                if (initialNotifyAmount > 0) {
                    IERC20(token).safeTransferFrom(msg.sender, address(this), initialNotifyAmount);
                    IERC20(token).forceApprove(vault, initialNotifyAmount);
                    IRVault(vault).notifyTargetRewardAmount(1 + i, initialNotifyAmount);
                }
            }
            if (totalInitialBoostUsdPerDay == 0) {
                revert IFactory.BoostAmountIsZero();
            }
            if (totalInitialBoostUsdPerDay < vars.minInitialBoostPerDay) {
                revert IFactory.BoostAmountTooLow();
            }
        }
    }

    function setVaultConfig(
        IFactory.FactoryStorage storage $,
        IFactory.VaultConfig memory vaultConfig_
    ) external returns (bool needGovOrMultisigAccess) {
        string memory type_ = vaultConfig_.vaultType;
        bytes32 typeHash = keccak256(abi.encodePacked(type_));
        $.vaultConfig[typeHash] = vaultConfig_;
        bool newVaultType = $.vaultTypeHashes.add(typeHash);
        if (!newVaultType) {
            needGovOrMultisigAccess = true;
        }
        emit IFactory.VaultConfigChanged(
            type_, vaultConfig_.implementation, vaultConfig_.deployAllowed, vaultConfig_.upgradeAllowed, newVaultType
        );
    }

    function upgradeVaultProxy(IFactory.FactoryStorage storage $, address vault) external {
        IVaultProxy proxy = IVaultProxy(vault);
        bytes32 vaultTypeHash = proxy.vaultTypeHash();
        address oldImplementation = proxy.implementation();
        IFactory.VaultConfig memory tempVaultConfig = $.vaultConfig[vaultTypeHash];
        address newImplementation = tempVaultConfig.implementation;
        if (!tempVaultConfig.upgradeAllowed) {
            revert IFactory.UpgradeDenied(vaultTypeHash);
        }
        if (oldImplementation == newImplementation) {
            revert IFactory.AlreadyLastVersion(vaultTypeHash);
        }
        proxy.upgrade();
        emit IFactory.VaultProxyUpgraded(vault, oldImplementation, newImplementation);
    }

    function upgradeStrategyProxy(IFactory.FactoryStorage storage $, address strategyProxy) external {
        IStrategyProxy proxy = IStrategyProxy(strategyProxy);
        bytes32 idHash = proxy.strategyImplementationLogicIdHash();
        IFactory.StrategyLogicConfig storage config = $.strategyLogicConfig[idHash];
        address oldImplementation = proxy.implementation();
        address newImplementation = config.implementation;
        if (!config.upgradeAllowed) {
            revert IFactory.UpgradeDenied(idHash);
        }
        if (oldImplementation == newImplementation) {
            revert IFactory.AlreadyLastVersion(idHash);
        }
        proxy.upgrade();
        emit IFactory.StrategyProxyUpgraded(strategyProxy, oldImplementation, newImplementation);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./ConstantsLib.sol";

library CommonLib {
    function filterAddresses(
        address[] memory addresses,
        address addressToRemove
    ) external pure returns (address[] memory filteredAddresses) {
        uint len = addresses.length;
        uint newLen;
        // nosemgrep
        for (uint i; i < len; ++i) {
            if (addresses[i] != addressToRemove) {
                ++newLen;
            }
        }
        filteredAddresses = new address[](newLen);
        uint k;
        // nosemgrep
        for (uint i; i < len; ++i) {
            if (addresses[i] != addressToRemove) {
                filteredAddresses[k] = addresses[i];
                ++k;
            }
        }
    }

    function formatUsdAmount(uint amount) external pure returns (string memory formattedPrice) {
        uint dollars = amount / 10 ** 18;
        string memory priceStr;
        if (dollars >= 1000) {
            uint kDollars = dollars / 1000;
            uint kDollarsFraction = (dollars - kDollars * 1000) / 10;
            string memory delimiter = ".";
            if (kDollarsFraction < 10) {
                delimiter = ".0";
            }
            priceStr = string.concat(Strings.toString(kDollars), delimiter, Strings.toString(kDollarsFraction), "k");
        } else if (dollars >= 100) {
            priceStr = Strings.toString(dollars);
        } else {
            uint dollarsFraction = (amount - dollars * 10 ** 18) / 10 ** 14;
            if (dollarsFraction > 0) {
                string memory dollarsFractionDelimiter = ".";
                if (dollarsFraction < 10) {
                    dollarsFractionDelimiter = ".000";
                } else if (dollarsFraction < 100) {
                    dollarsFractionDelimiter = ".00";
                } else if (dollarsFraction < 1000) {
                    dollarsFractionDelimiter = ".0";
                }
                priceStr = string.concat(
                    Strings.toString(dollars), dollarsFractionDelimiter, Strings.toString(dollarsFraction)
                );
            } else {
                priceStr = Strings.toString(dollars);
            }
        }

        formattedPrice = string.concat("$", priceStr);
    }

    function formatApr(uint apr) external pure returns (string memory formattedApr) {
        uint aprInt = apr * 100 / ConstantsLib.DENOMINATOR;
        uint aprFraction = (apr - aprInt * ConstantsLib.DENOMINATOR / 100) / 10;
        string memory delimiter = ".";
        if (aprFraction < 10) {
            delimiter = ".0";
        }
        formattedApr = string.concat(Strings.toString(aprInt), delimiter, Strings.toString(aprFraction), "%");
    }

    function implodeSymbols(
        address[] memory assets,
        string memory delimiter
    ) external view returns (string memory outString) {
        return implode(getSymbols(assets), delimiter);
    }

    function implode(string[] memory strings, string memory delimiter) public pure returns (string memory outString) {
        uint len = strings.length;
        if (len == 0) {
            return "";
        }
        outString = strings[0];
        // nosemgrep
        for (uint i = 1; i < len; ++i) {
            outString = string.concat(outString, delimiter, strings[i]);
        }
        return outString;
    }

    function getSymbols(address[] memory assets) public view returns (string[] memory symbols) {
        uint len = assets.length;
        symbols = new string[](len);
        // nosemgrep
        for (uint i; i < len; ++i) {
            symbols[i] = IERC20Metadata(assets[i]).symbol();
        }
    }

    function bytesToBytes32(bytes memory b) external pure returns (bytes32 out) {
        // nosemgrep
        for (uint i; i < b.length; ++i) {
            out |= bytes32(b[i] & 0xFF) >> (i * 8);
        }
        // return out;
    }

    function bToHex(bytes memory buffer) external pure returns (string memory) {
        // Fixed buffer size for hexadecimal convertion
        bytes memory converted = new bytes(buffer.length * 2);
        bytes memory _base = "0123456789abcdef";
        uint baseLength = _base.length;
        // nosemgrep
        for (uint i; i < buffer.length; ++i) {
            converted[i * 2] = _base[uint8(buffer[i]) / baseLength];
            converted[i * 2 + 1] = _base[uint8(buffer[i]) % baseLength];
        }
        return string(abi.encodePacked(converted));
    }

    function shortId(string memory id) external pure returns (string memory) {
        uint words = 1;
        bytes memory idBytes = bytes(id);
        uint idBytesLength = idBytes.length;
        // nosemgrep
        for (uint i; i < idBytesLength; ++i) {
            if (keccak256(bytes(abi.encodePacked(idBytes[i]))) == keccak256(bytes(" "))) {
                ++words;
            }
        }
        bytes memory _shortId = new bytes(words);
        uint k = 1;
        _shortId[0] = idBytes[0];
        // nosemgrep
        for (uint i = 1; i < idBytesLength; ++i) {
            if (keccak256(bytes(abi.encodePacked(idBytes[i]))) == keccak256(bytes(" "))) {
                if (keccak256(bytes(abi.encodePacked(idBytes[i + 1]))) == keccak256(bytes("0"))) {
                    _shortId[k] = idBytes[i + 3];
                } else {
                    _shortId[k] = idBytes[i + 1];
                }
                ++k;
            }
        }
        return string(abi.encodePacked(_shortId));
    }

    function eq(string memory a, string memory b) external pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }

    function u2s(uint num) external pure returns (string memory) {
        return Strings.toString(num);
    }

    function i2s(int num) external pure returns (string memory) {
        return Strings.toString(num > 0 ? uint(num) : uint(-num));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

library VaultTypeLib {
    string internal constant COMPOUNDING = "Compounding";
    string internal constant REWARDING = "Rewarding";
    string internal constant REWARDING_MANAGED = "Rewarding Managed";
    string internal constant SPLITTER_MANAGED = "Splitter Managed";
    string internal constant SPLITTER_AUTO = "Splitter Automatic";
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @notice Interface of the main contract and entry point to the platform.
/// @author Alien Deployer (https://github.com/a17)
/// @author Jude (https://github.com/iammrjude)
/// @author JodsMigel (https://github.com/JodsMigel)
interface IPlatform {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    error AlreadyAnnounced();
    error SameVersion();
    error NoNewVersion();
    error UpgradeTimerIsNotOver(uint TimerTimestamp);
    error IncorrectFee(uint minFee, uint maxFee);
    error NotEnoughAllowedBBToken();
    error TokenAlreadyExistsInSet(address token);
    error AggregatorNotExists(address dexAggRouter);

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    event PlatformVersion(string version);
    event UpgradeAnnounce(
        string oldVersion, string newVersion, address[] proxies, address[] newImplementations, uint timelock
    );
    event CancelUpgrade(string oldVersion, string newVersion);
    event ProxyUpgraded(
        address indexed proxy, address implementation, string oldContractVersion, string newContractVersion
    );
    event Addresses(
        address multisig_,
        address factory_,
        address priceReader_,
        address swapper_,
        address buildingPermitToken_,
        address vaultManager_,
        address strategyLogic_,
        address aprOracle_,
        address hardWorker,
        address rebalancer,
        address zap,
        address bridge
    );
    event OperatorAdded(address operator);
    event OperatorRemoved(address operator);
    event FeesChanged(uint fee, uint feeShareVaultManager, uint feeShareStrategyLogic, uint feeShareEcosystem);
    event MinInitialBoostChanged(uint minInitialBoostPerDay, uint minInitialBoostDuration);
    event NewAmmAdapter(string id, address proxy);
    event EcosystemRevenueReceiver(address receiver);
    event SetAllowedBBTokenVaults(address bbToken, uint vaultsToBuild, bool firstSet);
    event RemoveAllowedBBToken(address bbToken);
    event AddAllowedBoostRewardToken(address token);
    event RemoveAllowedBoostRewardToken(address token);
    event AddDefaultBoostRewardToken(address token);
    event RemoveDefaultBoostRewardToken(address token);
    event AddBoostTokens(address[] allowedBoostRewardToken, address[] defaultBoostRewardToken);
    event AllowedBBTokenVaultUsed(address bbToken, uint vaultToUse);
    event AddDexAggregator(address router);
    event RemoveDexAggregator(address router);
    event MinTvlForFreeHardWorkChanged(uint oldValue, uint newValue);
    event CustomVaultFee(address vault, uint platformFee);
    event Rebalancer(address rebalancer_);
    event Bridge(address bridge_);

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         DATA TYPES                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    struct PlatformUpgrade {
        string newVersion;
        address[] proxies;
        address[] newImplementations;
    }

    struct PlatformSettings {
        string networkName;
        bytes32 networkExtra;
        uint fee;
        uint feeShareVaultManager;
        uint feeShareStrategyLogic;
        uint feeShareEcosystem;
        uint minInitialBoostPerDay;
        uint minInitialBoostDuration;
    }

    struct AmmAdapter {
        string id;
        address proxy;
    }

    struct SetupAddresses {
        address factory;
        address priceReader;
        address swapper;
        address buildingPermitToken;
        address buildingPayPerVaultToken;
        address vaultManager;
        address strategyLogic;
        address aprOracle;
        address targetExchangeAsset;
        address hardWorker;
        address zap;
        address bridge;
        address rebalancer;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      VIEW FUNCTIONS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @notice Platform version in CalVer scheme: YY.MM.MINOR-tag. Updates on core contract upgrades.
    function platformVersion() external view returns (string memory);

    /// @notice Time delay for proxy upgrades of core contracts and changing important platform settings by multisig
    //slither-disable-next-line naming-convention
    function TIME_LOCK() external view returns (uint);

    /// @notice DAO governance
    function governance() external view returns (address);

    /// @notice Core team multi signature wallet. Development and operations fund
    function multisig() external view returns (address);

    /// @notice This NFT allow user to build limited number of vaults per week
    function buildingPermitToken() external view returns (address);

    /// @notice This ERC20 token is used as payment token for vault building
    function buildingPayPerVaultToken() external view returns (address);

    /// @notice Receiver of ecosystem revenue
    function ecosystemRevenueReceiver() external view returns (address);

    /// @dev The best asset in a network for swaps between strategy assets and farms rewards assets
    ///      The target exchange asset is used for finding the best strategy's exchange asset.
    ///      Rhe fewer routes needed to swap to the target exchange asset, the better.
    function targetExchangeAsset() external view returns (address);

    /// @notice Platform factory assembling vaults. Stores settings, strategy logic, farms.
    /// Provides the opportunity to upgrade vaults and strategies.
    /// @return Address of Factory proxy
    function factory() external view returns (address);

    /// @notice The holders of these NFT receive a share of the vault revenue
    /// @return Address of VaultManager proxy
    function vaultManager() external view returns (address);

    /// @notice The holders of these tokens receive a share of the revenue received in all vaults using this strategy logic.
    function strategyLogic() external view returns (address);

    /// @notice Combining oracle and DeX spot prices
    /// @return Address of PriceReader proxy
    function priceReader() external view returns (address);

    /// @notice Providing underlying assets APRs on-chain
    /// @return Address of AprOracle proxy
    function aprOracle() external view returns (address);

    /// @notice On-chain price quoter and swapper
    /// @return Address of Swapper proxy
    function swapper() external view returns (address);

    /// @notice HardWork resolver and caller
    /// @return Address of HardWorker proxy
    function hardWorker() external view returns (address);

    /// @notice Rebalance resolver
    /// @return Address of Rebalancer proxy
    function rebalancer() external view returns (address);

    /// @notice ZAP feature
    /// @return Address of Zap proxy
    function zap() external view returns (address);

    /// @notice Stability Bridge
    /// @return Address of Bridge proxy
    function bridge() external view returns (address);

    /// @notice Name of current EVM network
    function networkName() external view returns (string memory);

    /// @notice Minimal initial boost rewards per day USD amount which needs to create rewarding vault
    function minInitialBoostPerDay() external view returns (uint);

    /// @notice Minimal boost rewards vesting duration for initial boost
    function minInitialBoostDuration() external view returns (uint);

    /// @notice This function provides the timestamp of the platform upgrade timelock.
    /// @dev This function is an external view function, meaning it doesn't modify the state.
    /// @return uint representing the timestamp of the platform upgrade timelock.
    function platformUpgradeTimelock() external view returns (uint);

    /// @dev Extra network data
    /// @return 0-2 bytes - color
    ///         3-5 bytes - background color
    ///         6-31 bytes - free
    function networkExtra() external view returns (bytes32);

    /// @notice Pending platform upgrade data
    function pendingPlatformUpgrade() external view returns (PlatformUpgrade memory);

    /// @notice Get platform revenue fee settings
    /// @return fee Revenue fee % (between MIN_FEE - MAX_FEE) with DENOMINATOR precision.
    /// @return feeShareVaultManager Revenue fee share % of VaultManager tokenId owner
    /// @return feeShareStrategyLogic Revenue fee share % of StrategyLogic tokenId owner
    /// @return feeShareEcosystem Revenue fee share % of ecosystemFeeReceiver
    function getFees()
        external
        view
        returns (uint fee, uint feeShareVaultManager, uint feeShareStrategyLogic, uint feeShareEcosystem);

    /// @notice Get custom vault platform fee
    /// @return fee revenue fee % with DENOMINATOR precision
    function getCustomVaultFee(address vault) external view returns (uint fee);

    /// @notice Platform settings
    function getPlatformSettings() external view returns (PlatformSettings memory);

    /// @notice AMM adapters of the platform
    function getAmmAdapters() external view returns (string[] memory id, address[] memory proxy);

    /// @notice Get AMM adapter data by hash
    /// @param ammAdapterIdHash Keccak256 hash of adapter ID string
    /// @return ID string and proxy address of AMM adapter
    function ammAdapter(bytes32 ammAdapterIdHash) external view returns (AmmAdapter memory);

    /// @notice Allowed buy-back tokens for rewarding vaults
    function allowedBBTokens() external view returns (address[] memory);

    /// @notice Vaults building limit for buy-back token.
    /// This limit decrements when a vault for BB-token is built.
    /// @param token Allowed buy-back token
    /// @return vaultsLimit Number of vaults that can be built for BB-token
    function allowedBBTokenVaults(address token) external view returns (uint vaultsLimit);

    /// @notice Vaults building limits for allowed buy-back tokens.
    /// @return bbToken Allowed buy-back tokens
    /// @return vaultsLimit Number of vaults that can be built for BB-tokens
    function allowedBBTokenVaults() external view returns (address[] memory bbToken, uint[] memory vaultsLimit);

    /// @notice Non-zero vaults building limits for allowed buy-back tokens.
    /// @return bbToken Allowed buy-back tokens
    /// @return vaultsLimit Number of vaults that can be built for BB-tokens
    function allowedBBTokenVaultsFiltered()
        external
        view
        returns (address[] memory bbToken, uint[] memory vaultsLimit);

    /// @notice Check address for existance in operators list
    /// @param operator Address
    /// @return True if this address is Stability Operator
    function isOperator(address operator) external view returns (bool);

    /// @notice Tokens that can be used for boost rewards of rewarding vaults
    /// @return Addresses of tokens
    function allowedBoostRewardTokens() external view returns (address[] memory);

    /// @notice Allowed boost reward tokens that used for unmanaged rewarding vaults creation
    /// @return Addresses of tokens
    function defaultBoostRewardTokens() external view returns (address[] memory);

    /// @notice Allowed boost reward tokens that used for unmanaged rewarding vaults creation
    /// @param addressToRemove This address will be removed from default boost reward tokens
    /// @return Addresses of tokens
    function defaultBoostRewardTokensFiltered(address addressToRemove) external view returns (address[] memory);

    /// @notice Allowed DeX aggregators
    /// @return Addresses of DeX aggregator rounters
    function dexAggregators() external view returns (address[] memory);

    /// @notice DeX aggregator router address is allowed to be used in the platform
    /// @param dexAggRouter Address of DeX aggreagator router
    /// @return Can be used
    function isAllowedDexAggregatorRouter(address dexAggRouter) external view returns (bool);

    /// @notice Show minimum TVL for compensate if vault has not enough ETH
    /// @return Minimum TVL for compensate.
    function minTvlForFreeHardWork() external view returns (uint);

    /// @notice Front-end platform viewer
    /// @return platformAddresses Platform core addresses
    ///        platformAddresses[0] factory
    ///        platformAddresses[1] vaultManager
    ///        platformAddresses[2] strategyLogic
    ///        platformAddresses[3] buildingPermitToken
    ///        platformAddresses[4] buildingPayPerVaultToken
    ///        platformAddresses[5] governance
    ///        platformAddresses[6] multisig
    ///        platformAddresses[7] zap
    ///        platformAddresses[8] bridge
    /// @return bcAssets Blue chip token addresses
    /// @return dexAggregators_ DeX aggregators allowed to be used entire the platform
    /// @return vaultType Vault type ID strings
    /// @return vaultExtra Vault color, background color and other extra data. Index of vault same as in previous array.
    /// @return vaultBulldingPrice Price of creating new vault in buildingPayPerVaultToken. Index of vault same as in previous array.
    /// @return strategyId Strategy logic ID strings
    /// @return isFarmingStrategy True if strategy is farming strategy. Index of strategy same as in previous array.
    /// @return strategyTokenURI StrategyLogic NFT tokenId metadata and on-chain image. Index of strategy same as in previous array.
    /// @return strategyExtra Strategy color, background color and other extra data. Index of strategy same as in previous array.
    function getData()
        external
        view
        returns (
            address[] memory platformAddresses,
            address[] memory bcAssets,
            address[] memory dexAggregators_,
            string[] memory vaultType,
            bytes32[] memory vaultExtra,
            uint[] memory vaultBulldingPrice,
            string[] memory strategyId,
            bool[] memory isFarmingStrategy,
            string[] memory strategyTokenURI,
            bytes32[] memory strategyExtra
        );

    // todo add vaultSymbol, vaultName
    /// @notice Front-end balances, prices and vault list viewer
    /// @param yourAccount Address of account to query balances
    /// @return token Tokens supported by the platform
    /// @return tokenPrice USD price of token. Index of token same as in previous array.
    /// @return tokenUserBalance User balance of token. Index of token same as in previous array.
    /// @return vault Deployed vaults
    /// @return vaultSharePrice Price 1.0 vault share. Index of vault same as in previous array.
    /// @return vaultUserBalance User balance of vault. Index of vault same as in previous array.
    /// @return nft Ecosystem NFTs
    ///         nft[0] BuildingPermitToken
    ///         nft[1] VaultManager
    ///         nft[2] StrategyLogic
    /// @return nftUserBalance User balance of NFT. Index of NFT same as in previous array.
    /// @return buildingPayPerVaultTokenBalance User balance of vault creation paying token
    function getBalance(address yourAccount)
        external
        view
        returns (
            address[] memory token,
            uint[] memory tokenPrice,
            uint[] memory tokenUserBalance,
            address[] memory vault,
            uint[] memory vaultSharePrice,
            uint[] memory vaultUserBalance,
            address[] memory nft,
            uint[] memory nftUserBalance,
            uint buildingPayPerVaultTokenBalance
        );

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      WRITE FUNCTIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @notice Add platform operator.
    /// Only governance and multisig can add operator.
    /// @param operator Address of new operator
    function addOperator(address operator) external;

    /// @notice Remove platform operator.
    /// Only governance and multisig can remove operator.
    /// @param operator Address of operator to remove
    function removeOperator(address operator) external;

    /// @notice Announce upgrade of platform proxies implementations
    /// Only governance and multisig can announce platform upgrades.
    /// @param newVersion New platform version. Version must be changed when upgrading.
    /// @param proxies Addresses of core contract proxies
    /// @param newImplementations New implementation for proxy. Index of proxy same as in previous array.
    function announcePlatformUpgrade(
        string memory newVersion,
        address[] memory proxies,
        address[] memory newImplementations
    ) external;

    /// @notice Upgrade platform
    /// Only operator (multisig is operator too) can ececute pending platform upgrade
    function upgrade() external;

    /// @notice Cancel pending platform upgrade
    /// Only operator (multisig is operator too) can ececute pending platform upgrade
    function cancelUpgrade() external;

    /// @notice Register AMM adapter in platform
    /// @param id AMM adapter ID string from AmmAdapterIdLib
    /// @param proxy Address of AMM adapter proxy
    function addAmmAdapter(string memory id, address proxy) external;

    // todo Only governance and multisig can set allowed bb-token vaults building limit
    /// @notice Set new vaults building limit for buy-back token
    /// @param bbToken Address of allowed buy-back token
    /// @param vaultsToBuild Number of vaults that can be built for BB-token
    function setAllowedBBTokenVaults(address bbToken, uint vaultsToBuild) external;

    // todo Only governance and multisig can add allowed boost reward token
    /// @notice Add new allowed boost reward token
    /// @param token Address of token
    function addAllowedBoostRewardToken(address token) external;

    // todo Only governance and multisig can remove allowed boost reward token
    /// @notice Remove allowed boost reward token
    /// @param token Address of allowed boost reward token
    function removeAllowedBoostRewardToken(address token) external;

    // todo Only governance and multisig can add default boost reward token
    /// @notice Add default boost reward token
    /// @param token Address of default boost reward token
    function addDefaultBoostRewardToken(address token) external;

    // todo Only governance and multisig can remove default boost reward token
    /// @notice Remove default boost reward token
    /// @param token Address of allowed boost reward token
    function removeDefaultBoostRewardToken(address token) external;

    // todo Only governance and multisig can add allowed boost reward token
    // todo Only governance and multisig can add default boost reward token
    /// @notice Add new allowed boost reward token
    /// @notice Add default boost reward token
    /// @param allowedBoostRewardToken Address of allowed boost reward token
    /// @param defaultBoostRewardToken Address of default boost reward token
    function addBoostTokens(
        address[] memory allowedBoostRewardToken,
        address[] memory defaultBoostRewardToken
    ) external;

    /// @notice Decrease allowed BB-token vault building limit when vault is built
    /// Only Factory can do it.
    /// @param bbToken Address of allowed buy-back token
    function useAllowedBBTokenVault(address bbToken) external;

    /// @notice Allow DeX aggregator routers to be used in the platform
    /// @param dexAggRouter Addresses of DeX aggreagator routers
    function addDexAggregators(address[] memory dexAggRouter) external;

    /// @notice Remove allowed DeX aggregator router from the platform
    /// @param dexAggRouter Address of DeX aggreagator router
    function removeDexAggregator(address dexAggRouter) external;

    /// @notice Change initial boost rewards settings
    /// @param minInitialBoostPerDay_ Minimal initial boost rewards per day USD amount which needs to create rewarding vault
    /// @param minInitialBoostDuration_ Minimal boost rewards vesting duration for initial boost
    function setInitialBoost(uint minInitialBoostPerDay_, uint minInitialBoostDuration_) external;

    /// @notice Update new minimum TVL for compensate.
    /// @param value New minimum TVL for compensate.
    function setMinTvlForFreeHardWork(uint value) external;

    /// @notice Set custom platform fee for vault
    /// @param vault Vault address
    /// @param platformFee Custom platform fee
    function setCustomVaultFee(address vault, uint platformFee) external;

    /// @notice Setup Rebalancer.
    /// Only Goverannce or Multisig can do this when Rebalancer is not set.
    /// @param rebalancer_ Proxy address of Bridge
    function setupRebalancer(address rebalancer_) external;

    /// @notice Setup Bridge.
    /// Only Goverannce or Multisig can do this when Bridge is not set.
    /// @param bridge_ Proxy address of Bridge
    function setupBridge(address bridge_) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/// @dev Core interface of strategy logic
interface IStrategy is IERC165 {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    event HardWork(
        uint apr, uint compoundApr, uint earned, uint tvl, uint duration, uint sharePrice, uint[] assetPrices
    );
    event ExtractFees(
        uint vaultManagerReceiverFee,
        uint strategyLogicReceiverFee,
        uint ecosystemRevenueReceiverFee,
        uint multisigReceiverFee
    );

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    error NotReadyForHardWork();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         DATA TYPES                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @custom:storage-location erc7201:stability.StrategyBase
    struct StrategyBaseStorage {
        /// @inheritdoc IStrategy
        address vault;
        /// @inheritdoc IStrategy
        uint total;
        /// @inheritdoc IStrategy
        uint lastHardWork;
        /// @inheritdoc IStrategy
        uint lastApr;
        /// @inheritdoc IStrategy
        uint lastAprCompound;
        /// @inheritdoc IStrategy
        address[] _assets;
        /// @inheritdoc IStrategy
        address _underlying;
        string _id;
        uint _exchangeAssetIndex;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       VIEW FUNCTIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Strategy logic string ID
    function strategyLogicId() external view returns (string memory);

    /// @dev Extra data
    /// @return 0-2 bytes - strategy color
    ///         3-5 bytes - strategy background color
    ///         6-31 bytes - free
    function extra() external view returns (bytes32);

    /// @dev Types of vault that supported by strategy implementation
    /// @return types Vault type ID strings
    function supportedVaultTypes() external view returns (string[] memory types);

    /// @dev Linked vault address
    function vault() external view returns (address);

    /// @dev Final assets that strategy invests
    function assets() external view returns (address[] memory);

    /// @notice Final assets and amounts that strategy manages
    function assetsAmounts() external view returns (address[] memory assets_, uint[] memory amounts_);

    /// @notice Priced invested assets proportions
    /// @return proportions Proportions of assets with 18 decimals. Min is 0, max is 1e18.
    function getAssetsProportions() external view returns (uint[] memory proportions);

    /// @notice Underlying token address
    /// @dev Can be used for liquidity farming strategies where AMM has fungible liquidity token (Solidly forks, etc),
    ///      for concentrated liquidity tokenized vaults (Gamma, G-UNI etc) and for other needs.
    /// @return Address of underlying token or zero address if no underlying in strategy
    function underlying() external view returns (address);

    /// @dev Balance of liquidity token or liquidity value
    function total() external view returns (uint);

    /// @dev Last HardWork time
    /// @return Timestamp
    function lastHardWork() external view returns (uint);

    /// @dev Last APR of earned USD amount registered by HardWork
    ///      ONLY FOR OFF-CHAIN USE.
    ///      Not trusted asset price can be manipulated.
    /// @return APR with 18 decimals. 1e18 - 100%.
    function lastApr() external view returns (uint);

    /// @dev Last APR of compounded assets registered by HardWork.
    ///      Can be used on-chain.
    /// @return APR with 18 decimals. 1e18 - 100%.
    function lastAprCompound() external view returns (uint);

    /// @notice Calculation of consumed amounts and liquidity/underlying value for provided strategy assets and amounts.
    /// @param assets_ Strategy assets or part of them, if necessary
    /// @param amountsMax Amounts of specified assets available for investing
    /// @return amountsConsumed Cosumed amounts of assets when investing
    /// @return value Liquidity value or underlying token amount minted when investing
    function previewDepositAssets(
        address[] memory assets_,
        uint[] memory amountsMax
    ) external view returns (uint[] memory amountsConsumed, uint value);

    /// @notice Write version of previewDepositAssets
    /// @param assets_ Strategy assets or part of them, if necessary
    /// @param amountsMax Amounts of specified assets available for investing
    /// @return amountsConsumed Cosumed amounts of assets when investing
    /// @return value Liquidity value or underlying token amount minted when investing
    function previewDepositAssetsWrite(
        address[] memory assets_,
        uint[] memory amountsMax
    ) external returns (uint[] memory amountsConsumed, uint value);

    /// @notice All strategy revenue (pool fees, farm rewards etc) that not claimed by strategy yet
    /// @return assets_ Revenue assets
    /// @return amounts Amounts. Index of asset same as in previous array.
    function getRevenue() external view returns (address[] memory assets_, uint[] memory amounts);

    /// @notice Optional specific name of investing strategy, underyling type, setup variation etc
    /// @return name Empty string or specific name
    /// @return showInVaultSymbol Show specific in linked vault symbol
    function getSpecificName() external view returns (string memory name, bool showInVaultSymbol);

    /// @notice Variants pf strategy initializations with description of money making mechanic.
    /// As example, if strategy need farm, then number of variations is number of available farms.
    /// If CAMM strategy have set of available widths (tick ranges), then number of variations is number of available farms.
    /// If both example conditions are met then total number or variations = total farms * total widths.
    /// @param platform_ Need this param because method called when strategy implementation is not initialized
    /// @return variants Descriptions of the strategy for making money
    /// @return addresses Init strategy addresses. Indexes for each variants depends of copmpared arrays lengths.
    /// @return nums Init strategy numbers. Indexes for each variants depends of copmpared arrays lengths.
    /// @return ticks Init strategy ticks. Indexes for each variants depends of copmpared arrays lengths.
    function initVariants(address platform_)
        external
        view
        returns (string[] memory variants, address[] memory addresses, uint[] memory nums, int24[] memory ticks);

    /// @notice How does the strategy make money?
    /// @return Description in free form
    function description() external view returns (string memory);

    /// @notice Is HardWork on vault deposits can be enabled
    function isHardWorkOnDepositAllowed() external view returns (bool);

    /// @notice Is HardWork can be executed
    function isReadyForHardWork() external view returns (bool);

    /// @notice Strategy not need to process revenue on HardWorks
    function autoCompoundingByUnderlyingProtocol() external view returns (bool);

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      WRITE FUNCTIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev A single universal initializer for all strategy implementations.
    /// @param addresses All addresses that strategy requires for initialization. Min array length is 2.
    ///        addresses[0]: platform (required)
    ///        addresses[1]: vault (required)
    ///        addresses[2]: initStrategyAddresses[0] (optional)
    ///        addresses[3]: initStrategyAddresses[1] (optional)
    ///        addresses[n]: initStrategyAddresses[n - 2] (optional)
    /// @param nums All uint values that strategy requires for initialization. Min array length is 0.
    /// @param ticks All int24 values that strategy requires for initialization. Min array length is 0.
    function initialize(address[] memory addresses, uint[] memory nums, int24[] memory ticks) external;

    /// @notice Invest strategy assets. Amounts of assets must be already on strategy contract balance.
    /// Only vault can call this.
    /// @param amounts Anounts of strategy assets
    /// @return value Liquidity value or underlying token amount
    function depositAssets(uint[] memory amounts) external returns (uint value);

    /// @notice Invest underlying asset. Asset must be already on strategy contract balance.
    /// Only vault can call this.
    /// @param amount Amount of underlying asset to invest
    /// @return amountsConsumed Cosumed amounts of invested assets
    function depositUnderlying(uint amount) external returns (uint[] memory amountsConsumed);

    /// @dev For specified amount of shares and assets_, withdraw strategy assets from farm/pool/staking and send to receiver if possible
    /// Only vault can call this.
    /// @param assets_ Here we give the user a choice of assets to withdraw if strategy support it
    /// @param value Part of strategy total value to withdraw
    /// @param receiver User address
    /// @return amountsOut Amounts of assets sent to user
    function withdrawAssets(
        address[] memory assets_,
        uint value,
        address receiver
    ) external returns (uint[] memory amountsOut);

    /// @notice Wothdraw underlying invested and send to receiver
    /// Only vault can call this.
    /// @param amount Ampunt of underlying asset to withdraw
    /// @param receiver User of vault which withdraw underlying from the vault
    function withdrawUnderlying(uint amount, address receiver) external;

    /// @dev For specified amount of shares, transfer strategy assets from contract balance and send to receiver if possible
    /// This method is called by vault w/o underlying on triggered fuse mode.
    /// Only vault can call this.
    /// @param amount Ampunt of liquidity value that user withdraw
    /// @param totalAmount Total amount of strategy liquidity
    /// @param receiver User of vault which withdraw assets
    /// @return amountsOut Amounts of strategy assets sent to user
    function transferAssets(
        uint amount,
        uint totalAmount,
        address receiver
    ) external returns (uint[] memory amountsOut);

    /// @notice Execute HardWork
    /// During HardWork strategy claiming revenue and processing it.
    /// Only vault can call this.
    function doHardWork() external;

    /// @notice Emergency stop investing by strategy, withdraw liquidity without rewards.
    /// This action triggers FUSE mode.
    /// Only governance or multisig can call this.
    function emergencyStopInvesting() external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @notice On-chain price quoter and swapper by predefined routes
/// @author Alien Deployer (https://github.com/a17)
/// @author Jude (https://github.com/iammrjude)
/// @author JodsMigel (https://github.com/JodsMigel)
/// @author 0xhokugava (https://github.com/0xhokugava)
interface ISwapper {
    event Swap(address indexed tokenIn, address indexed tokenOut, uint amount);
    event PoolAdded(PoolData poolData, bool assetAdded);
    event PoolRemoved(address token);
    event BlueChipAdded(PoolData poolData);
    event ThresholdChanged(address[] tokenIn, uint[] thresholdAmount);
    event BlueChipPoolRemoved(address tokenIn, address tokenOut);

    //region ----- Custom Errors -----
    error UnknownAMMAdapter();
    error LessThenThreshold(uint minimumAmount);
    error NoRouteFound();
    error NoRoutesForAssets();
    //endregion -- Custom Errors -----

    struct PoolData {
        address pool;
        address ammAdapter;
        address tokenIn;
        address tokenOut;
    }

    struct AddPoolData {
        address pool;
        string ammAdapterId;
        address tokenIn;
        address tokenOut;
    }

    /// @notice All assets in pools added to Swapper
    /// @return Addresses of assets
    function assets() external view returns (address[] memory);

    /// @notice All blue chip assets in blue chip pools added to Swapper
    /// @return Addresses of blue chip assets
    function bcAssets() external view returns (address[] memory);

    /// @notice All assets in Swapper
    /// @return Addresses of assets and blue chip assets
    function allAssets() external view returns (address[] memory);

    /// @notice Add pools with largest TVL
    /// @param pools Largest pools with AMM adapter addresses
    /// @param rewrite Rewrite pool for tokenIn
    function addPools(PoolData[] memory pools, bool rewrite) external;

    /// @notice Add pools with largest TVL
    /// @param pools Largest pools with AMM adapter ID string
    /// @param rewrite Rewrite pool for tokenIn
    function addPools(AddPoolData[] memory pools, bool rewrite) external;

    /// @notice Add largest pools with the most popular tokens on the current network
    /// @param pools_ PoolData array with pool, tokens and AMM adapter address
    /// @param rewrite Change exist pool records
    function addBlueChipsPools(PoolData[] memory pools_, bool rewrite) external;

    /// @notice Add largest pools with the most popular tokens on the current network
    /// @param pools_ AddPoolData array with pool, tokens and AMM adapter string ID
    /// @param rewrite Change exist pool records
    function addBlueChipsPools(AddPoolData[] memory pools_, bool rewrite) external;

    /// @notice Retrieves pool data for a specified token swap in Blue Chip Pools.
    /// @dev This function provides information about the pool associated with the specified input and output tokens.
    /// @param tokenIn The input token address.
    /// @param tokenOut The output token address.
    /// @return poolData The data structure containing information about the Blue Chip Pool.
    /// @custom:opcodes view
    function blueChipsPools(address tokenIn, address tokenOut) external view returns (PoolData memory poolData);

    /// @notice Set swap threshold for token
    /// @dev Prevents dust swap.
    /// @param tokenIn Swap input token
    /// @param thresholdAmount Minimum amount of token for executing swap
    function setThresholds(address[] memory tokenIn, uint[] memory thresholdAmount) external;

    /// @notice Swap threshold for token
    /// @param token Swap input token
    /// @return threshold_ Minimum amount of token for executing swap
    function threshold(address token) external view returns (uint threshold_);

    /// @notice Price of given tokenIn against tokenOut
    /// @param tokenIn Swap input token
    /// @param tokenOut Swap output token
    /// @param amount Amount of tokenIn. If provide zero then amount is 1.0.
    /// @return Amount of tokenOut with decimals of tokenOut
    function getPrice(address tokenIn, address tokenOut, uint amount) external view returns (uint);

    /// @notice Return price the first poolData.tokenIn against the last poolData.tokenOut in decimals of tokenOut.
    /// @param route Array of pool address, swapper address tokenIn, tokenOut
    /// @param amount Amount of tokenIn. If provide zero then amount is 1.0.
    function getPriceForRoute(PoolData[] memory route, uint amount) external view returns (uint);

    /// @notice Check possibility of swap tokenIn for tokenOut
    /// @param tokenIn Swap input token
    /// @param tokenOut Swap output token
    /// @return Swap route exists
    function isRouteExist(address tokenIn, address tokenOut) external view returns (bool);

    /// @notice Build route for swap. No reverts inside.
    /// @param tokenIn Swap input token
    /// @param tokenOut Swap output token
    /// @return route Array of pools for swap tokenIn to tokenOut. Zero length indicate an error.
    /// @return errorMessage Possible reason why the route was not found. Empty for success routes.
    function buildRoute(
        address tokenIn,
        address tokenOut
    ) external view returns (PoolData[] memory route, string memory errorMessage);

    /// @notice Sell tokenIn for tokenOut
    /// @dev Assume approve on this contract exist
    /// @param tokenIn Swap input token
    /// @param tokenOut Swap output token
    /// @param amount Amount of tokenIn for swap.
    /// @param priceImpactTolerance Price impact tolerance. Must include fees at least. Denominator is 100_000.
    function swap(address tokenIn, address tokenOut, uint amount, uint priceImpactTolerance) external;

    /// @notice Swap by predefined route
    /// @param route Array of pool address, swapper address tokenIn, tokenOut.
    /// TokenIn from first item will be swaped to tokenOut of last .
    /// @param amount Amount of first item tokenIn.
    /// @param priceImpactTolerance Price impact tolerance. Must include fees at least. Denominator is 100_000.
    function swapWithRoute(PoolData[] memory route, uint amount, uint priceImpactTolerance) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/// @notice Creating vaults, upgrading vaults and strategies, vault list, farms and strategy logics management
/// @author Alien Deployer (https://github.com/a17)
/// @author Jude (https://github.com/iammrjude)
/// @author JodsMigel (https://github.com/JodsMigel)
/// @author HCrypto7 (https://github.com/hcrypto7)
interface IFactory {
    //region ----- Custom Errors -----

    error VaultImplementationIsNotAvailable();
    error VaultNotAllowedToDeploy();
    error StrategyImplementationIsNotAvailable();
    error StrategyLogicNotAllowedToDeploy();
    error YouDontHaveEnoughTokens(uint userBalance, uint requireBalance, address payToken);
    error SuchVaultAlreadyDeployed(bytes32 key);
    error NotActiveVault();
    error UpgradeDenied(bytes32 _hash);
    error AlreadyLastVersion(bytes32 _hash);
    error NotStrategy();
    error BoostDurationTooLow();
    error BoostAmountTooLow();
    error BoostAmountIsZero();

    //endregion ----- Custom Errors -----

    //region ----- Events -----

    event VaultAndStrategy(
        address indexed deployer,
        string vaultType,
        string strategyId,
        address vault,
        address strategy,
        string name,
        string symbol,
        address[] assets,
        bytes32 deploymentKey,
        uint vaultManagerTokenId
    );
    event StrategyProxyUpgraded(address proxy, address oldImplementation, address newImplementation);
    event VaultProxyUpgraded(address proxy, address oldImplementation, address newImplementation);
    event VaultConfigChanged(
        string type_, address implementation, bool deployAllowed, bool upgradeAllowed, bool newVaultType
    );
    event StrategyLogicConfigChanged(
        string id, address implementation, bool deployAllowed, bool upgradeAllowed, bool newStrategy
    );
    event VaultStatus(address indexed vault, uint newStatus);
    event NewFarm(Farm[] farms);
    event UpdateFarm(uint id, Farm farm);
    event SetStrategyAvailableInitParams(string id, address[] initAddresses, uint[] initNums, int24[] initTicks);
    event AliasNameChanged(address indexed operator, address indexed tokenAddress, string newAliasName);

    //endregion -- Events -----

    //region ----- Data types -----

    /// @custom:storage-location erc7201:stability.Factory
    struct FactoryStorage {
        /// @inheritdoc IFactory
        mapping(bytes32 typeHash => VaultConfig) vaultConfig;
        /// @inheritdoc IFactory
        mapping(bytes32 idHash => StrategyLogicConfig) strategyLogicConfig;
        /// @inheritdoc IFactory
        mapping(bytes32 deploymentKey => address vaultProxy) deploymentKey;
        /// @inheritdoc IFactory
        mapping(address vault => uint status) vaultStatus;
        /// @inheritdoc IFactory
        mapping(address address_ => bool isStrategy_) isStrategy;
        EnumerableSet.Bytes32Set vaultTypeHashes;
        EnumerableSet.Bytes32Set strategyLogicIdHashes;
        mapping(uint week => mapping(uint builderPermitTokenId => uint vaultsBuilt)) vaultsBuiltByPermitTokenId;
        address[] deployedVaults;
        Farm[] farms;
        /// @inheritdoc IFactory
        mapping(bytes32 idHash => StrategyAvailableInitParams) strategyAvailableInitParams;
        mapping(address tokenAddress => string aliasName) aliasNames;
    }

    struct VaultConfig {
        string vaultType;
        address implementation;
        bool deployAllowed;
        bool upgradeAllowed;
        uint buildingPrice;
    }

    struct StrategyLogicConfig {
        string id;
        address implementation;
        bool deployAllowed;
        bool upgradeAllowed;
        bool farming;
        uint tokenId;
    }

    struct Farm {
        uint status;
        address pool;
        string strategyLogicId;
        address[] rewardAssets;
        address[] addresses;
        uint[] nums;
        int24[] ticks;
    }

    struct StrategyAvailableInitParams {
        address[] initAddresses;
        uint[] initNums;
        int24[] initTicks;
    }

    //endregion -- Data types -----

    //region ----- View functions -----

    /// @notice All vaults deployed by the factory
    /// @return Vault proxy addresses
    function deployedVaults() external view returns (address[] memory);

    /// @notice Total vaults deployed
    function deployedVaultsLength() external view returns (uint);

    /// @notice Get vault by VaultManager tokenId
    /// @param id Vault array index. Same as tokenId of VaultManager NFT
    /// @return Address of VaultProxy
    function deployedVault(uint id) external view returns (address);

    /// @notice All farms known by the factory in current network
    function farms() external view returns (Farm[] memory);

    /// @notice Total farms known by the factory in current network
    function farmsLength() external view returns (uint);

    /// @notice Farm data by farm index
    /// @param id Index of farm
    function farm(uint id) external view returns (Farm memory);

    /// @notice Strategy logic settings
    /// @param idHash keccak256 hash of strategy logic string ID
    /// @return config Strategy logic settings
    function strategyLogicConfig(bytes32 idHash) external view returns (StrategyLogicConfig memory config);

    /// @notice All known strategies
    /// @return Array of keccak256 hashes of strategy logic string ID
    function strategyLogicIdHashes() external view returns (bytes32[] memory);

    // todo remove, use new function without calculating vault symbol on the fly for not initialized vaults
    // factory required that special functionally only internally, not for interface
    function getStrategyData(
        string memory vaultType,
        address strategyAddress,
        address bbAsset
    )
        external
        view
        returns (
            string memory strategyId,
            address[] memory assets,
            string[] memory assetsSymbols,
            string memory specificName,
            string memory vaultSymbol
        );

    /// @dev Get best asset of assets to be strategy exchange asset
    function getExchangeAssetIndex(address[] memory assets) external view returns (uint);

    /// @notice Deployment key of created vault
    /// @param deploymentKey_ Hash of concatenated unique vault and strategy initialization parameters
    /// @return Address of deployed vault
    function deploymentKey(bytes32 deploymentKey_) external view returns (address);

    /// @notice Calculating deployment key based on unique vault and strategy initialization parameters
    /// @param vaultType Vault type string
    /// @param strategyId Strategy logic Id string
    /// @param vaultInitAddresses Vault initizlization addresses for deployVaultAndStrategy method
    /// @param vaultInitNums Vault initizlization uint numbers for deployVaultAndStrategy method
    /// @param strategyInitAddresses Strategy initizlization addresses for deployVaultAndStrategy method
    /// @param strategyInitNums Strategy initizlization uint numbers for deployVaultAndStrategy method
    /// @param strategyInitTicks Strategy initizlization int24 ticks for deployVaultAndStrategy method
    function getDeploymentKey(
        string memory vaultType,
        string memory strategyId,
        address[] memory vaultInitAddresses,
        uint[] memory vaultInitNums,
        address[] memory strategyInitAddresses,
        uint[] memory strategyInitNums,
        int24[] memory strategyInitTicks
    ) external returns (bytes32);

    /// @notice Available variants of new vault for creating.
    /// The structure of the function's output values is complex,
    /// but after parsing them, the front end has all the data to generate a list of vaults to create.
    /// @return desc Descriptions of the strategy for making money
    /// @return vaultType Vault type strings. Output values are matched by index with previous array.
    /// @return strategyId Strategy logic ID strings. Output values are matched by index with previous array.
    /// @return initIndexes Map of start and end indexes in next 5 arrays. Output values are matched by index with previous array.
    ///                 [0] Start index in vaultInitAddresses
    ///                 [1] End index in vaultInitAddresses
    ///                 [2] Start index in vaultInitNums
    ///                 [3] End index in vaultInitNums
    ///                 [4] Start index in strategyInitAddresses
    ///                 [5] End index in strategyInitAddresses
    ///                 [6] Start index in strategyInitNums
    ///                 [7] End index in strategyInitNums
    ///                 [8] Start index in strategyInitTicks
    ///                 [9] End index in strategyInitTicks
    /// @return vaultInitAddresses Vault initizlization addresses for deployVaultAndStrategy method for all building variants.
    /// @return vaultInitNums Vault initizlization uint numbers for deployVaultAndStrategy method for all building variants.
    /// @return strategyInitAddresses Strategy initizlization addresses for deployVaultAndStrategy method for all building variants.
    /// @return strategyInitNums Strategy initizlization uint numbers for deployVaultAndStrategy method for all building variants.
    /// @return strategyInitTicks Strategy initizlization int24 ticks for deployVaultAndStrategy method for all building variants.
    function whatToBuild()
        external
        view
        returns (
            string[] memory desc,
            string[] memory vaultType,
            string[] memory strategyId,
            uint[10][] memory initIndexes,
            address[] memory vaultInitAddresses,
            uint[] memory vaultInitNums,
            address[] memory strategyInitAddresses,
            uint[] memory strategyInitNums,
            int24[] memory strategyInitTicks
        );

    /// @notice Governance and multisig can set a vault status other than Active - the default status.
    /// HardWorker only works with active vaults.
    /// @return status Constant from VaultStatusLib
    function vaultStatus(address vault) external view returns (uint status);

    /// @notice Check that strategy proxy deployed by the Factory
    /// @param address_ Address of contract
    /// @return This address is our strategy proxy
    function isStrategy(address address_) external view returns (bool);

    /// @notice How much vaults was built by builderPermitToken NFT tokenId in week
    /// @param week Week index (timestamp / (86400 * 7))
    /// @param builderPermitTokenId Token ID of buildingPermitToken NFT
    /// @return vaultsBuilt Vaults built
    function vaultsBuiltByPermitTokenId(
        uint week,
        uint builderPermitTokenId
    ) external view returns (uint vaultsBuilt);

    /// @notice Data on all factory strategies.
    /// The output values are matched by index in the arrays.
    /// @return id Strategy logic ID strings
    /// @return deployAllowed New vaults can be deployed
    /// @return upgradeAllowed Strategy can be upgraded
    /// @return farming It is farming strategy (earns farming/gauge rewards)
    /// @return tokenId Token ID of StrategyLogic NFT
    /// @return tokenURI StrategyLogic NFT tokenId metadata and on-chain image
    /// @return extra Strategy color, background color and other extra data
    function strategies()
        external
        view
        returns (
            string[] memory id,
            bool[] memory deployAllowed,
            bool[] memory upgradeAllowed,
            bool[] memory farming,
            uint[] memory tokenId,
            string[] memory tokenURI,
            bytes32[] memory extra
        );

    /// @notice Get config of vault type
    /// @param typeHash Keccak256 hash of vault type string
    /// @return vaultType Vault type string
    /// @return implementation Vault implementation address
    /// @return deployAllowed New vaults can be deployed
    /// @return upgradeAllowed Vaults can be upgraded
    /// @return buildingPrice Price of building new vault
    function vaultConfig(bytes32 typeHash)
        external
        view
        returns (
            string memory vaultType,
            address implementation,
            bool deployAllowed,
            bool upgradeAllowed,
            uint buildingPrice
        );

    /// @notice Data on all factory vault types
    /// The output values are matched by index in the arrays.
    /// @return vaultType Vault type string
    /// @return implementation Address of vault implemented logic
    /// @return deployAllowed New vaults can be deployed
    /// @return upgradeAllowed Vaults can be upgraded
    /// @return buildingPrice  Price of building new vault
    /// @return extra Vault type color, background color and other extra data
    function vaultTypes()
        external
        view
        returns (
            string[] memory vaultType,
            address[] memory implementation,
            bool[] memory deployAllowed,
            bool[] memory upgradeAllowed,
            uint[] memory buildingPrice,
            bytes32[] memory extra
        );

    /// @notice Initialization strategy params store
    function strategyAvailableInitParams(bytes32 idHash) external view returns (StrategyAvailableInitParams memory);

    /// @notice Retrieves the alias name associated with a given address
    /// @param tokenAddress_ The address to query for its alias name
    /// @return The alias name associated with the provided address
    function getAliasName(address tokenAddress_) external view returns (string memory);

    //endregion -- View functions -----

    //region ----- Write functions -----

    /// @notice Main method of the Factory - new vault creation by user.
    /// @param vaultType Vault type ID string
    /// @param strategyId Strategy logic ID string
    /// Different types of vaults and strategies have different lengths of input arrays.
    /// @param vaultInitAddresses Addresses for vault initialization
    /// @param vaultInitNums Numbers for vault initialization
    /// @param strategyInitAddresses Addresses for strategy initialization
    /// @param strategyInitNums Numbers for strategy initialization
    /// @param strategyInitTicks Ticks for strategy initialization
    /// @return vault Deployed VaultProxy address
    /// @return strategy Deployed StrategyProxy address
    function deployVaultAndStrategy(
        string memory vaultType,
        string memory strategyId,
        address[] memory vaultInitAddresses,
        uint[] memory vaultInitNums,
        address[] memory strategyInitAddresses,
        uint[] memory strategyInitNums,
        int24[] memory strategyInitTicks
    ) external returns (address vault, address strategy);

    /// @notice Upgrade vault proxy. Can be called by any address.
    /// @param vault Address of vault proxy for upgrade
    function upgradeVaultProxy(address vault) external;

    /// @notice Upgrade strategy proxy. Can be called by any address.
    /// @param strategy Address of strategy proxy for upgrade
    function upgradeStrategyProxy(address strategy) external;

    /// @notice Add farm to factory
    /// @param farms_ Settings and data required to work with the farm.
    function addFarms(Farm[] memory farms_) external;

    /// @notice Update farm
    /// @param id Farm index
    /// @param farm_ Settings and data required to work with the farm.
    function updateFarm(uint id, Farm memory farm_) external;

    /// @notice Initial addition or change of vault type settings.
    /// Operator can add new vault type. Governance or multisig can change existing vault type config.
    /// @param vaultConfig_ Vault type settings
    function setVaultConfig(VaultConfig memory vaultConfig_) external;

    /// @notice Initial addition or change of strategy logic settings.
    /// Operator can add new strategy logic. Governance or multisig can change existing logic config.
    /// @param config Strategy logic settings
    /// @param developer Strategy developer is receiver of minted StrategyLogic NFT on initial addition
    function setStrategyLogicConfig(StrategyLogicConfig memory config, address developer) external;

    /// @notice Governance and multisig can set a vault status other than Active - the default status.
    /// @param vaults Addresses of vault proxy
    /// @param statuses New vault statuses. Constant from VaultStatusLib
    function setVaultStatus(address[] memory vaults, uint[] memory statuses) external;

    /// @notice Initial addition or change of strategy available init params
    /// @param id Strategy ID string
    /// @param initParams Init params variations that will be parsed by strategy
    function setStrategyAvailableInitParams(string memory id, StrategyAvailableInitParams memory initParams) external;

    /// @notice Assigns a new alias name to a specific address
    /// @dev This function may require certain permissions to be called successfully.
    /// @param tokenAddress_ The address to assign an alias name to
    /// @param aliasName_ The alias name to assign to the given address
    function setAliasName(address tokenAddress_, string memory aliasName_) external;

    //endregion -- Write functions -----
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @dev Combining oracle and DeX spot prices
/// @author Alien Deployer (https://github.com/a17)
/// @author Jude (https://github.com/iammrjude)
/// @author JodsMigel (https://github.com/JodsMigel)
interface IPriceReader {
    //region ----- Events -----
    event AdapterAdded(address adapter);
    event AdapterRemoved(address adapter);
    //endregion -- Events -----

    /// @notice Price of asset
    /// @dev Price of 1.0 amount of asset in USD
    /// @param asset Address of asset
    /// @return price USD price with 18 decimals
    /// @return trusted Price from oracle
    function getPrice(address asset) external view returns (uint price, bool trusted);

    /// @notice Get USD price of specified assets and amounts
    /// @param assets_ Addresses of assets
    /// @param amounts_ Amount of asset. Index of asset same as in previous parameter.
    /// @return total Total USD value with 18 decimals
    /// @return assetAmountPrice USD price of asset amount. Index of assetAmountPrice same as in assets_ parameters.
    /// @return assetPrice USD price of asset. Index of assetAmountPrice same as in assets_ parameters.
    /// @return trusted True if only oracle prices was used for calculation.
    function getAssetsPrice(
        address[] memory assets_,
        uint[] memory amounts_
    ) external view returns (uint total, uint[] memory assetAmountPrice, uint[] memory assetPrice, bool trusted);

    /// @notice Add oracle adapter to PriceReader
    /// Only operator (multisig is operator too) can add adapter
    /// @param adapter_ Address of price oracle proxy
    function addAdapter(address adapter_) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "./IStrategy.sol";

/// @notice Vault core interface.
/// Derived implementations can be effective for building tokenized vaults with single or multiple underlying liquidity mining position.
/// Fungible, static non-fungible and actively re-balancing liquidity is supported, as well as single token liquidity provided to lending protocols.
/// Vaults can be used for active concentrated liquidity management and market making.
/// @author Jude (https://github.com/iammrjude)
/// @author JodsMigel (https://github.com/JodsMigel)
interface IVault is IERC165 {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    error NotEnoughBalanceToPay();
    error FuseTrigger();
    error ExceedSlippage(uint mintToUser, uint minToMint);
    error ExceedSlippageExactAsset(address asset, uint mintToUser, uint minToMint);
    error ExceedMaxSupply(uint maxSupply);
    error NotEnoughAmountToInitSupply(uint mintAmount, uint initialShares);
    error WaitAFewBlocks();
    error StrategyZeroDeposit();
    error NotSupported();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    event DepositAssets(address indexed account, address[] assets, uint[] amounts, uint mintAmount);
    event WithdrawAssets(
        address indexed sender, address indexed owner, address[] assets, uint sharesAmount, uint[] amountsOut
    );
    event HardWorkGas(uint gasUsed, uint gasCost, bool compensated);
    event DoHardWorkOnDepositChanged(bool oldValue, bool newValue);
    event MaxSupply(uint maxShares);
    event VaultName(string newName);
    event VaultSymbol(string newSymbol);
    event MintFees(
        uint vaultManagerReceiverFee,
        uint strategyLogicReceiverFee,
        uint ecosystemRevenueReceiverFee,
        uint multisigReceiverFee
    );

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         DATA TYPES                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @custom:storage-location erc7201:stability.VaultBase
    struct VaultBaseStorage {
        /// @dev Prevents manipulations with deposit and withdraw in short time.
        ///      For simplification we are setup new withdraw request on each deposit/transfer.
        mapping(address msgSender => uint blockNumber) withdrawRequests;
        /// @inheritdoc IVault
        IStrategy strategy;
        /// @inheritdoc IVault
        uint maxSupply;
        /// @inheritdoc IVault
        uint tokenId;
        /// @inheritdoc IVault
        bool doHardWorkOnDeposit;
        /// @dev Immutable vault type ID
        string _type;
        /// @dev Changed ERC20 name
        string changedName;
        /// @dev Changed ERC20 symbol
        string changedSymbol;
    }

    /// @title Vault Initialization Data
    /// @notice Data structure containing parameters for initializing a new vault.
    /// @dev This struct is commonly used as a parameter for the `initialize` function in vault contracts.
    /// @param platform Platform address providing access control, infrastructure addresses, fee settings, and upgrade capability.
    /// @param strategy Immutable strategy proxy used by the vault.
    /// @param name ERC20 name for the vault token.
    /// @param symbol ERC20 symbol for the vault token.
    /// @param tokenId NFT ID associated with the VaultManager.
    /// @param vaultInitAddresses Array of addresses used during vault initialization.
    /// @param vaultInitNums Array of uint values corresponding to initialization parameters.
    struct VaultInitializationData {
        address platform;
        address strategy;
        string name;
        string symbol;
        uint tokenId;
        address[] vaultInitAddresses;
        uint[] vaultInitNums;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       VIEW FUNCTIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @notice Immutable vault type ID
    function vaultType() external view returns (string memory);

    /// @return uniqueInitAddresses Return required unique init addresses
    /// @return uniqueInitNums Return required unique init nums
    function getUniqueInitParamLength() external view returns (uint uniqueInitAddresses, uint uniqueInitNums);

    /// @notice Vault type extra data
    /// @return Vault type color, background color and other extra data
    function extra() external view returns (bytes32);

    /// @notice Immutable strategy proxy used by the vault
    /// @return Linked strategy
    function strategy() external view returns (IStrategy);

    /// @notice Max supply of shares in the vault.
    /// Since the starting share price is $1, this ceiling can be considered as an approximate TVL limit.
    /// @return Max total supply of vault
    function maxSupply() external view returns (uint);

    /// @dev VaultManager token ID. This tokenId earn feeVaultManager provided by Platform.
    function tokenId() external view returns (uint);

    /// @dev Trigger doHardwork on invest action. Enabled by default.
    function doHardWorkOnDeposit() external view returns (bool);

    /// @dev USD price of share with 18 decimals.
    ///      ONLY FOR OFF-CHAIN USE.
    ///      Not trusted vault share price can be manipulated.
    /// @return price_ Price of 1e18 shares with 18 decimals precision
    /// @return trusted True means oracle price, false means AMM spot price
    function price() external view returns (uint price_, bool trusted);

    /// @dev USD price of assets managed by strategy with 18 decimals
    ///      ONLY FOR OFF-CHAIN USE.
    ///      Not trusted TVL can be manipulated.
    /// @return tvl_ Total USD value of final assets in vault
    /// @return trusted True means TVL calculated based only on oracle prices, false means AMM spot price was used.
    function tvl() external view returns (uint tvl_, bool trusted);

    /// @dev Calculation of consumed amounts, shares amount and liquidity/underlying value for provided available amounts of strategy assets
    /// @param assets_ Assets suitable for vault strategy. Can be strategy assets, underlying asset or specific set of assets depending on strategy logic.
    /// @param amountsMax Available amounts of assets_ that user wants to invest in vault
    /// @return amountsConsumed Amounts of strategy assets that can be deposited by providing amountsMax
    /// @return sharesOut Amount of vault shares that will be minted
    /// @return valueOut Liquidity value or underlying token amount that will be received by the strategy
    function previewDepositAssets(
        address[] memory assets_,
        uint[] memory amountsMax
    ) external view returns (uint[] memory amountsConsumed, uint sharesOut, uint valueOut);

    /// @notice All available data on the latest declared APR (annual percentage rate)
    /// @return totalApr Total APR of investing money to vault. 18 decimals: 1e18 - +100% per year.
    /// @return strategyApr Strategy investmnt APR declared on last HardWork.
    /// @return assetsWithApr Assets with underlying APR
    /// @return assetsAprs Underlying APR of asset
    function getApr()
        external
        view
        returns (uint totalApr, uint strategyApr, address[] memory assetsWithApr, uint[] memory assetsAprs);

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      WRITE FUNCTIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Mint fee shares callback
    /// @param revenueAssets Assets returned by _claimRevenue function that was earned during HardWork
    /// @param revenueAmounts Assets amounts returned from _claimRevenue function that was earned during HardWork
    /// Only strategy can call this
    function hardWorkMintFeeCallback(address[] memory revenueAssets, uint[] memory revenueAmounts) external;

    /// @dev Deposit final assets (pool assets) to the strategy and minting of vault shares.
    ///      If the strategy interacts with a pool or farms through an underlying token, then it will be minted.
    ///      Emits a {DepositAssets} event with consumed amounts.
    /// @param assets_ Assets suitable for the strategy. Can be strategy assets, underlying asset or specific set of assets depending on strategy logic.
    /// @param amountsMax Available amounts of assets_ that user wants to invest in vault
    /// @param minSharesOut Slippage tolerance. Minimal shares amount which must be received by user.
    /// @param receiver Receiver of deposit. If receiver is zero address, receiver is msg.sender.
    function depositAssets(
        address[] memory assets_,
        uint[] memory amountsMax,
        uint minSharesOut,
        address receiver
    ) external;

    /// @dev Burning shares of vault and obtaining strategy assets.
    /// @param assets_ Assets suitable for the strategy. Can be strategy assets, underlying asset or specific set of assets depending on strategy logic.
    /// @param amountShares Shares amount for burning
    /// @param minAssetAmountsOut Slippage tolerance. Minimal amounts of strategy assets that user must receive.
    /// @return Amount of assets for withdraw. It's related to assets_ one-by-one.
    function withdrawAssets(
        address[] memory assets_,
        uint amountShares,
        uint[] memory minAssetAmountsOut
    ) external returns (uint[] memory);

    /// @dev Burning shares of vault and obtaining strategy assets.
    /// @param assets_ Assets suitable for the strategy. Can be strategy assets, underlying asset or specific set of assets depending on strategy logic.
    /// @param amountShares Shares amount for burning
    /// @param minAssetAmountsOut Slippage tolerance. Minimal amounts of strategy assets that user must receive.
    /// @param receiver Receiver of assets
    /// @param owner Owner of vault shares
    /// @return Amount of assets for withdraw. It's related to assets_ one-by-one.
    function withdrawAssets(
        address[] memory assets_,
        uint amountShares,
        uint[] memory minAssetAmountsOut,
        address receiver,
        address owner
    ) external returns (uint[] memory);

    /// @dev Setting of vault capacity
    /// @param maxShares If totalSupply() exceeds this value, deposits will not be possible
    function setMaxSupply(uint maxShares) external;

    /// @dev If activated will call doHardWork on strategy on some deposit actions
    /// @param value HardWork on deposit is enabled
    function setDoHardWorkOnDeposit(bool value) external;

    /// @notice Initialization function for the vault.
    /// @dev This function is usually called by the Factory during the creation of a new vault.
    /// @param vaultInitializationData Data structure containing parameters for vault initialization.
    function initialize(VaultInitializationData memory vaultInitializationData) external;

    /// @dev Calling the strategy HardWork by operator with optional compensation for spent gas from the vault balance
    function doHardWork() external;

    /// @dev Changing ERC20 name of vault
    function setName(string calldata newName) external;

    /// @dev Changing ERC20 symbol of vault
    function setSymbol(string calldata newSymbol) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "./IVault.sol";

/// @notice Interface of Rewarding Vault
/// @author Alien Deployer (https://github.com/a17)
/// @author JodsMigel (https://github.com/JodsMigel)
/// @author 0xhokugava (https://github.com/0xhokugava)
interface IRVault is IVault {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    error NotAllowed();
    error Overflow(uint maxAmount);
    error RTNotFound();
    error NoBBToken();
    error NotAllowedBBToken();
    error IncorrectNums();
    error ZeroToken();
    error ZeroVestingDuration();
    error TooHighCompoundRation();
    error RewardIsTooSmall();
    // error RewardIsTooBig();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    event RewardAdded(address rewardToken, uint reward);
    event RewardPaid(address indexed user, address rewardToken, uint reward);
    event SetRewardsRedirect(address owner, address receiver);
    event AddedRewardToken(address indexed token, uint indexed tokenIndex);
    event CompoundRatio(uint compoundRatio_);

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         DATA TYPES                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @custom:storage-location erc7201:stability.RVaultBase
    struct RVaultBaseStorage {
        /// @inheritdoc IRVault
        mapping(uint tokenIndex => address rewardToken) rewardToken;
        /// @inheritdoc IRVault
        mapping(uint tokenIndex => uint durationSeconds) duration;
        /// @inheritdoc IRVault
        mapping(address owner => address receiver) rewardsRedirect;
        /// @dev Timestamp value when current period of rewards will be ended
        mapping(uint tokenIndex => uint finishTimestamp) periodFinishForToken;
        /// @dev Reward rate in normal circumstances is distributed rewards divided on duration
        mapping(uint tokenIndex => uint rewardRate) rewardRateForToken;
        /// @dev Last rewards snapshot time. Updated on each share movements
        mapping(uint tokenIndex => uint lastUpdateTimestamp) lastUpdateTimeForToken;
        /// @dev Rewards snapshot calculated from rewardPerToken(rt). Updated on each share movements
        mapping(uint tokenIndex => uint rewardPerTokenStored) rewardPerTokenStoredForToken;
        /// @dev User personal reward rate snapshot. Updated on each share movements
        mapping(uint tokenIndex => mapping(address user => uint rewardPerTokenPaid)) userRewardPerTokenPaidForToken;
        /// @dev User personal earned reward snapshot. Updated on each share movements
        mapping(uint tokenIndex => mapping(address user => uint earned)) rewardsForToken;
        /// @inheritdoc IRVault
        uint rewardTokensTotal;
        /// @inheritdoc IRVault
        uint compoundRatio;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       VIEW FUNCTIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @notice All vault rewarding tokens
    /// @return Reward token addresses
    function rewardTokens() external view returns (address[] memory);

    /// @return Total of bbToken + boost reward tokens
    function rewardTokensTotal() external view returns (uint);

    /// @notice Immutable reward buy-back token with tokenIndex 0
    function bbToken() external view returns (address);

    /// @dev A mapping of reward tokens that able to be distributed to this contract.
    /// Token with index 0 always is bbToken.
    function rewardToken(uint tokenIndex) external view returns (address rewardToken_);

    /// @notice Re-investing ratio
    /// @dev Changeable ratio of revenue part for re-investing. Other part goes to rewarding by bbToken.
    /// @return Ratio of re-investing part of revenue. Denominator is 100_000.
    function compoundRatio() external view returns (uint);

    /// @notice Vesting period for distribution reward
    /// @param tokenIndex Index of rewarding token
    /// @return durationSeconds Duration for distributing of notified reward
    function duration(uint tokenIndex) external view returns (uint durationSeconds);

    /// @notice Return earned rewards for specific token and account
    ///         Accurate value returns only after updateRewards call
    ///         ((balanceOf(account)
    ///           * (rewardPerToken - userRewardPerTokenPaidForToken)) / 10**18) + rewardsForToken
    function earned(uint rewardTokenIndex, address account) external view returns (uint);

    /// @notice Return reward per token ratio by reward token address
    ///                rewardPerTokenStoredForToken + (
    ///                (lastTimeRewardApplicable - lastUpdateTimeForToken)
    ///                 * rewardRateForToken * 10**18 / totalSupply)
    /// @param rewardTokenIndex Index of reward token
    /// @return Return reward per token ratio by reward token address
    function rewardPerToken(uint rewardTokenIndex) external view returns (uint);

    /// @dev Receiver of rewards can be set by multisig when owner cant claim rewards himself
    /// @param owner Token owner address
    /// @return receiver Return reward's receiver
    function rewardsRedirect(address owner) external view returns (address receiver);

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      WRITE FUNCTIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @notice Filling vault with rewards
    /// @dev Update rewardRateForToken
    /// If period ended: reward / duration
    /// else add leftover to the reward amount and refresh the period
    /// (reward + ((periodFinishForToken - block.timestamp) * rewardRateForToken)) / duration
    /// @param tokenIndex Index of rewarding token
    /// @param amount Amount for rewarding
    function notifyTargetRewardAmount(uint tokenIndex, uint amount) external;

    /// @notice Update and Claim all rewards for caller
    function getAllRewards() external;

    /// @notice Update and Claim rewards for specific token
    /// @param rt Index of reward token
    function getReward(uint rt) external;

    /// @dev All rewards for given owner could be claimed for receiver address.
    /// @param owner Token owner address
    /// @param receiver New reward's receiver
    function setRewardsRedirect(address owner, address receiver) external;

    /// @notice Update and Claim all rewards for given owner address. Send them to predefined receiver.
    /// @param owner Token owner address
    function getAllRewardsAndRedirect(address owner) external;

    /// @notice Update and Claim all rewards for the given owner.
    ///         Sender should have allowance for push rewards for the owner.
    /// @param owner Token owner address
    function getAllRewardsFor(address owner) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @dev Interface of proxy contract for a vault implementation
interface IVaultProxy {
    //region ----- Custom Errors -----
    error ProxyForbidden();
    //endregion -- Custom Errors -----

    /// @notice Initialize vault proxy by Factory
    /// @param type_ Vault type ID string
    function initProxy(string memory type_) external;

    /// @notice Upgrade vault implementation if available and allowed
    /// Anyone can execute vault upgrade
    function upgrade() external;

    /// @notice Current vault implementation
    /// @return Address of vault implementation contract
    function implementation() external view returns (address);

    /// @notice Vault type hash
    /// @return keccan256 hash of vault type ID string
    function vaultTypeHash() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @dev Interface of proxy contract for a strategy implementation
interface IStrategyProxy {
    /// @notice Initialize strategy proxy by Factory
    /// @param id Strategy logic ID string
    function initStrategyProxy(string memory id) external;

    /// @notice Upgrade strategy implementation if available and allowed
    /// Anyone can execute strategy upgrade
    function upgrade() external;

    /// @notice Current strategy implementation
    /// @return Address of strategy implementation contract
    function implementation() external view returns (address);

    /// @notice Strategy logic hash
    /// @return keccan256 hash of strategy logic ID string
    function strategyImplementationLogicIdHash() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

library ConstantsLib {
    uint internal constant DENOMINATOR = 100_000;
    address internal constant DEAD_ADDRESS = 0xdEad000000000000000000000000000000000000;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):