StrETHOracle

Description:

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

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/collectors/defi/StrETHOracle.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../../../src/vaults/Vault.sol";
import "./ICustomOracle.sol";
import "./external/IAaveOracleV3.sol";
import "./external/IAavePoolV3.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

contract StrETHOracle is ICustomOracle {
    address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;

    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address public constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
    address public constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F;

    address public constant USDE = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3;
    address public constant SUSDE = 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497;

    IAavePoolV3 public constant AAVE_CORE = IAavePoolV3(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);
    IAavePoolV3 public constant AAVE_PRIME = IAavePoolV3(0x4e033931ad43597d96D6bcc25c280717730B58B1);
    IAaveOracleV3 public constant AAVE_ORACLE = IAaveOracleV3(0x54586bE62E3c3580375aE3723C145253060Ca0C2);

    struct Balance {
        address asset;
        int256 balance;
        string metadata;
    }

    function getAaveBalances(address token, address vault, IAavePoolV3 instance)
        public
        view
        returns (uint256 collateral, uint256 debt)
    {
        if (token == ETH) {
            return (0, 0);
        }

        IAavePoolV3.ReserveDataLegacy memory data;
        data = instance.getReserveData(token);
        if (data.aTokenAddress != address(0)) {
            collateral = IERC20(data.aTokenAddress).balanceOf(vault);
        }
        if (data.variableDebtTokenAddress != address(0)) {
            debt = IERC20(data.variableDebtTokenAddress).balanceOf(vault);
        }
    }

    function allTokens() public pure returns (address[] memory tokens) {
        address[8] memory tokens_ = [ETH, WETH, WSTETH, USDC, USDT, USDS, USDE, SUSDE];
        tokens = new address[](tokens_.length);
        for (uint256 i = 0; i < tokens_.length; i++) {
            tokens[i] = tokens_[i];
        }
    }

    function evaluate(address asset, address denominator, uint256 amount) public view returns (uint256) {
        if (asset == denominator) {
            return amount;
        }
        uint256 assetPriceD8 = AAVE_ORACLE.getAssetPrice(asset);
        uint8 assetDecimals = IERC20Metadata(asset).decimals();
        uint256 denominatorPriceD8 = AAVE_ORACLE.getAssetPrice(denominator);
        uint8 denominatorDecimals = IERC20Metadata(asset).decimals();
        return Math.mulDiv(amount, assetPriceD8 * 10 ** denominatorDecimals, denominatorPriceD8 * 10 ** assetDecimals);
    }

    function evaluateSigned(address asset, address denominator, int256 amount) public view returns (int256) {
        if (amount == 0) {
            return 0;
        }
        if (asset == ETH) {
            return evaluateSigned(WETH, denominator, amount);
        }
        if (amount > 0) {
            return int256(evaluate(asset, denominator, uint256(amount)));
        }
        return -int256(evaluate(asset, denominator, uint256(-amount)));
    }

    function tvl(address vault, Data calldata data) public view returns (uint256 value) {
        Balance[] memory response = getDistributions(Vault(payable(vault)));
        address[] memory tokens = allTokens();
        int256[] memory balances = new int256[](tokens.length);
        for (uint256 i = 0; i < response.length; i++) {
            uint256 index;
            for (uint256 j = 0; j < tokens.length; j++) {
                if (tokens[j] == response[i].asset) {
                    index = j;
                    break;
                }
            }
            balances[index] += response[i].balance;
        }
        int256 signedValue = 0;
        for (uint256 i = 0; i < tokens.length; i++) {
            signedValue += evaluateSigned(tokens[i], data.denominator, balances[i]);
        }
        if (signedValue < 0) {
            return 0;
        }
        value = uint256(signedValue);
    }

    function getDistributions(Vault vault) public view returns (Balance[] memory response) {
        uint256 subvaults = vault.subvaults();
        address[] memory vaults = new address[](subvaults + 1);
        for (uint256 i = 0; i < subvaults; i++) {
            vaults[i] = vault.subvaultAt(i);
        }
        vaults[subvaults] = address(vault);

        address[] memory tokens = allTokens();

        response = new Balance[](tokens.length * 5);
        uint256 iterator = 0;

        for (uint256 i = 0; i < tokens.length; i++) {
            Balance memory token = Balance({asset: tokens[i], balance: 0, metadata: "ERC20"});

            Balance memory coreDebt = Balance({asset: tokens[i], balance: 0, metadata: "AaveCoreDebt"});
            Balance memory coreCollateral = Balance({asset: tokens[i], balance: 0, metadata: "AaveCoreCollateral"});

            Balance memory primeDebt = Balance({asset: tokens[i], balance: 0, metadata: "AavePrimeDebt"});
            Balance memory primeCollateral = Balance({asset: tokens[i], balance: 0, metadata: "AavePrimeCollateral"});
            for (uint256 j = 0; j < vaults.length; j++) {
                token.balance += int256(TransferLibrary.balanceOf(tokens[i], vaults[j]));
                {
                    (uint256 collateral, uint256 debt) = getAaveBalances(tokens[i], vaults[j], AAVE_CORE);
                    coreCollateral.balance += int256(collateral);
                    coreDebt.balance -= int256(debt);
                }
                {
                    (uint256 collateral, uint256 debt) = getAaveBalances(tokens[i], vaults[j], AAVE_PRIME);
                    primeCollateral.balance += int256(collateral);
                    primeDebt.balance -= int256(debt);
                }
            }

            if (token.balance != 0) {
                response[iterator++] = token;
            }
            if (coreCollateral.balance != 0) {
                response[iterator++] = coreCollateral;
            }
            if (coreDebt.balance != 0) {
                response[iterator++] = coreDebt;
            }
            if (primeCollateral.balance != 0) {
                response[iterator++] = primeCollateral;
            }
            if (primeDebt.balance != 0) {
                response[iterator++] = primeDebt;
            }
        }

        assembly {
            mstore(response, iterator)
        }
    }
}
"
    },
    "src/vaults/Vault.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/factories/IFactoryEntity.sol";

import "../modules/ACLModule.sol";
import "../modules/ShareModule.sol";
import "../modules/VaultModule.sol";

contract Vault is IFactoryEntity, VaultModule, ShareModule {
    struct RoleHolder {
        bytes32 role;
        address holder;
    }

    constructor(
        string memory name_,
        uint256 version_,
        address depositQueueFactory_,
        address redeemQueueFactory_,
        address subvaultFactory_,
        address verifierFactory_
    )
        ACLModule(name_, version_)
        ShareModule(name_, version_, depositQueueFactory_, redeemQueueFactory_)
        VaultModule(name_, version_, subvaultFactory_, verifierFactory_)
    {}

    function initialize(bytes calldata initParams) external initializer {
        {
            (
                address admin_,
                address shareManager_,
                address feeManager_,
                address riskManager_,
                address oracle_,
                address defaultDepositHook_,
                address defaultRedeemHook_,
                uint256 queueLimit_,
                RoleHolder[] memory roleHolders
            ) = abi.decode(
                initParams, (address, address, address, address, address, address, address, uint256, RoleHolder[])
            );
            __BaseModule_init();
            __ACLModule_init(admin_);
            __ShareModule_init(
                shareManager_, feeManager_, oracle_, defaultDepositHook_, defaultRedeemHook_, queueLimit_
            );
            __VaultModule_init(riskManager_);
            for (uint256 i = 0; i < roleHolders.length; i++) {
                _grantRole(roleHolders[i].role, roleHolders[i].holder);
            }
        }
        emit Initialized(initParams);
    }
}
"
    },
    "src/collectors/defi/ICustomOracle.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface ICustomOracle {
    struct Data {
        address oracle;
        uint256 timestamp;
        address denominator;
        string metadata;
    }

    function tvl(address vault, Data calldata data) external view returns (uint256);
}
"
    },
    "src/collectors/defi/external/IAaveOracleV3.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IAaveOracleV3 {
    function getAssetPrice(address) external view returns (uint256);
}
"
    },
    "src/collectors/defi/external/IAavePoolV3.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

interface IAavePoolV3 {
    struct ReserveConfigurationMap {
        uint256 data;
    }

    struct ReserveDataLegacy {
        ReserveConfigurationMap configuration;
        uint128 liquidityIndex;
        uint128 currentLiquidityRate;
        uint128 variableBorrowIndex;
        uint128 currentVariableBorrowRate;
        uint128 currentStableBorrowRate;
        uint40 lastUpdateTimestamp;
        uint16 id;
        // aToken address
        address aTokenAddress;
        address stableDebtTokenAddress;
        // variableDebtToken address
        address variableDebtTokenAddress;
        address interestRateStrategyAddress;
        uint128 accruedToTreasury;
        uint128 unbacked;
        uint128 isolationModeTotalDebt;
    }

    function getReserveData(address asset) external view returns (ReserveDataLegacy memory res);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.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 ERC-20 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);
}
"
    },
    "src/interfaces/factories/IFactoryEntity.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

/// @title IFactoryEntity
interface IFactoryEntity {
    /// @notice Initializes the factory-created entity with arbitrary initialization data.
    /// @param initParams The initialization parameters.
    function initialize(bytes calldata initParams) external;

    /// @notice Emitted once the entity has been initialized.
    /// @param initParams The initialization parameters.
    event Initialized(bytes initParams);
}
"
    },
    "src/modules/ACLModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/modules/IACLModule.sol";

import "../libraries/SlotLibrary.sol";

import "../permissions/MellowACL.sol";
import "./BaseModule.sol";

abstract contract ACLModule is IACLModule, BaseModule, MellowACL {
    constructor(string memory name_, uint256 version_) MellowACL(name_, version_) {}

    // Internal functions

    function __ACLModule_init(address admin_) internal onlyInitializing {
        if (admin_ == address(0)) {
            revert ZeroAddress();
        }
        _grantRole(DEFAULT_ADMIN_ROLE, admin_);
    }
}
"
    },
    "src/modules/ShareModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/modules/IShareModule.sol";

import "../libraries/SlotLibrary.sol";
import "../libraries/TransferLibrary.sol";

import "./ACLModule.sol";

abstract contract ShareModule is IShareModule, ACLModule {
    using EnumerableSet for EnumerableSet.AddressSet;

    /// @inheritdoc IShareModule
    bytes32 public constant SET_HOOK_ROLE = keccak256("modules.ShareModule.SET_HOOK_ROLE");
    /// @inheritdoc IShareModule
    bytes32 public constant CREATE_QUEUE_ROLE = keccak256("modules.ShareModule.CREATE_QUEUE_ROLE");
    /// @inheritdoc IShareModule
    bytes32 public constant SET_QUEUE_STATUS_ROLE = keccak256("modules.ShareModule.SET_QUEUE_STATUS_ROLE");
    /// @inheritdoc IShareModule
    bytes32 public constant SET_QUEUE_LIMIT_ROLE = keccak256("modules.ShareModule.SET_QUEUE_LIMIT_ROLE");
    /// @inheritdoc IShareModule
    bytes32 public constant REMOVE_QUEUE_ROLE = keccak256("modules.ShareModule.REMOVE_QUEUE_ROLE");

    /// @inheritdoc IShareModule
    IFactory public immutable depositQueueFactory;
    /// @inheritdoc IShareModule
    IFactory public immutable redeemQueueFactory;

    bytes32 private immutable _shareModuleStorageSlot;

    constructor(string memory name_, uint256 version_, address depositQueueFactory_, address redeemQueueFactory_) {
        _shareModuleStorageSlot = SlotLibrary.getSlot("ShareModule", name_, version_);
        depositQueueFactory = IFactory(depositQueueFactory_);
        redeemQueueFactory = IFactory(redeemQueueFactory_);
    }

    // View functions

    /// @inheritdoc IShareModule
    function shareManager() public view returns (IShareManager) {
        return IShareManager(_shareModuleStorage().shareManager);
    }

    /// @inheritdoc IShareModule
    function feeManager() public view returns (IFeeManager) {
        return IFeeManager(_shareModuleStorage().feeManager);
    }

    /// @inheritdoc IShareModule
    function oracle() public view returns (IOracle) {
        return IOracle(_shareModuleStorage().oracle);
    }

    /// @inheritdoc IShareModule
    function hasQueue(address queue) public view returns (bool) {
        return _shareModuleStorage().queues[IQueue(queue).asset()].contains(queue);
    }

    /// @inheritdoc IShareModule
    function getAssetCount() public view returns (uint256) {
        return _shareModuleStorage().assets.length();
    }

    /// @inheritdoc IShareModule
    function assetAt(uint256 index) public view returns (address) {
        return _shareModuleStorage().assets.at(index);
    }

    /// @inheritdoc IShareModule
    function hasAsset(address asset) public view returns (bool) {
        return _shareModuleStorage().assets.contains(asset);
    }

    /// @inheritdoc IShareModule
    function queueAt(address asset, uint256 index) public view returns (address) {
        return _shareModuleStorage().queues[asset].at(index);
    }

    /// @inheritdoc IShareModule
    function getQueueCount() public view returns (uint256) {
        return _shareModuleStorage().queueCount;
    }

    /// @inheritdoc IShareModule
    function getQueueCount(address asset) public view returns (uint256) {
        return _shareModuleStorage().queues[asset].length();
    }

    /// @inheritdoc IShareModule
    function queueLimit() public view returns (uint256) {
        return _shareModuleStorage().queueLimit;
    }

    /// @inheritdoc IShareModule
    function isDepositQueue(address queue) public view returns (bool) {
        return _shareModuleStorage().isDepositQueue[queue];
    }

    /// @inheritdoc IShareModule
    function isPausedQueue(address queue) public view returns (bool) {
        return _shareModuleStorage().isPausedQueue[queue];
    }

    /// @inheritdoc IShareModule
    function defaultDepositHook() public view returns (address) {
        return _shareModuleStorage().defaultDepositHook;
    }

    /// @inheritdoc IShareModule
    function defaultRedeemHook() public view returns (address) {
        return _shareModuleStorage().defaultRedeemHook;
    }

    /// @inheritdoc IShareModule
    function claimableSharesOf(address account) public view returns (uint256 shares) {
        ShareModuleStorage storage $ = _shareModuleStorage();
        EnumerableSet.AddressSet storage assets = $.assets;
        uint256 assetsCount = assets.length();
        for (uint256 i = 0; i < assetsCount; i++) {
            address asset = assets.at(i);
            EnumerableSet.AddressSet storage queues = $.queues[asset];
            uint256 queuesCount = queues.length();
            for (uint256 j = 0; j < queuesCount; j++) {
                address queue = queues.at(j);
                if ($.isDepositQueue[queue]) {
                    shares += IDepositQueue(queue).claimableOf(account);
                }
            }
        }
        return shares;
    }

    /// @inheritdoc IShareModule
    function getHook(address queue) public view returns (address) {
        ShareModuleStorage storage $ = _shareModuleStorage();
        address hook = $.customHooks[queue];
        return hook != address(0) ? hook : $.isDepositQueue[queue] ? $.defaultDepositHook : $.defaultRedeemHook;
    }

    /// @inheritdoc IShareModule
    function getLiquidAssets() public view returns (uint256) {
        address queue = _msgSender();
        address asset = IQueue(queue).asset();
        ShareModuleStorage storage $ = _shareModuleStorage();
        if (!$.queues[asset].contains(queue) || $.isDepositQueue[queue]) {
            revert Forbidden();
        }
        address hook = getHook(queue);
        if (hook == address(0)) {
            return TransferLibrary.balanceOf(asset, address(this));
        }
        return IRedeemHook(hook).getLiquidAssets(asset);
    }

    // Mutable functions

    /// @inheritdoc IShareModule
    function setCustomHook(address queue, address hook) external onlyRole(SET_HOOK_ROLE) {
        if (queue == address(0)) {
            revert ZeroAddress();
        }
        _shareModuleStorage().customHooks[queue] = hook;
        emit CustomHookSet(queue, hook);
    }

    /// @inheritdoc IShareModule
    function setDefaultDepositHook(address hook) external onlyRole(SET_HOOK_ROLE) {
        _shareModuleStorage().defaultDepositHook = hook;
        emit DefaultHookSet(hook, true);
    }

    /// @inheritdoc IShareModule
    function setDefaultRedeemHook(address hook) external onlyRole(SET_HOOK_ROLE) {
        _shareModuleStorage().defaultRedeemHook = hook;
        emit DefaultHookSet(hook, false);
    }

    /// @inheritdoc IShareModule
    function setQueueLimit(uint256 limit) external onlyRole(SET_QUEUE_LIMIT_ROLE) {
        _shareModuleStorage().queueLimit = limit;
        emit QueueLimitSet(limit);
    }

    /// @inheritdoc IShareModule
    function setQueueStatus(address queue, bool isPaused) external onlyRole(SET_QUEUE_STATUS_ROLE) {
        if (!hasQueue(queue)) {
            revert Forbidden();
        }
        _shareModuleStorage().isPausedQueue[queue] = isPaused;
        emit SetQueueStatus(queue, isPaused);
    }

    /// @inheritdoc IShareModule
    function createQueue(uint256 version, bool isDeposit, address owner, address asset, bytes calldata data)
        external
        nonReentrant
        onlyRole(CREATE_QUEUE_ROLE)
    {
        ShareModuleStorage storage $ = _shareModuleStorage();
        if (!IOracle($.oracle).isSupportedAsset(asset)) {
            revert UnsupportedAsset(asset);
        }
        uint256 count = $.queueCount + 1;
        if (count > $.queueLimit) {
            revert QueueLimitReached();
        }
        address queue = (isDeposit ? depositQueueFactory : redeemQueueFactory).create(
            version, owner, abi.encode(asset, address(this), data)
        );
        $.queueCount = count;
        $.queues[asset].add(queue);
        $.assets.add(asset);
        $.isDepositQueue[queue] = isDeposit;
        emit QueueCreated(queue, asset, isDeposit);
    }

    /// @inheritdoc IShareModule
    function removeQueue(address queue) external onlyRole(REMOVE_QUEUE_ROLE) {
        if (!IQueue(queue).canBeRemoved()) {
            revert Forbidden();
        }
        address asset = IQueue(queue).asset();
        ShareModuleStorage storage $ = _shareModuleStorage();
        if (!$.queues[asset].remove(queue)) {
            revert Forbidden();
        }
        delete $.isDepositQueue[queue];
        if ($.queues[asset].length() == 0) {
            $.assets.remove(asset);
        }
        delete $.customHooks[queue];
        --$.queueCount;
        emit QueueRemoved(queue, asset);
    }

    /// @inheritdoc IShareModule
    function claimShares(address account) external {
        ShareModuleStorage storage $ = _shareModuleStorage();
        EnumerableSet.AddressSet storage assets = $.assets;
        uint256 assetsCount = assets.length();
        for (uint256 i = 0; i < assetsCount; i++) {
            address asset = assets.at(i);
            EnumerableSet.AddressSet storage queues = $.queues[asset];
            uint256 queuesCount = queues.length();
            for (uint256 j = 0; j < queuesCount; j++) {
                address queue = queues.at(j);
                if ($.isDepositQueue[queue]) {
                    IDepositQueue(queue).claim(account);
                }
            }
        }
        emit SharesClaimed(account);
    }

    /// @inheritdoc IShareModule
    function callHook(uint256 assets) external {
        address queue = _msgSender();
        address asset = IQueue(queue).asset();
        ShareModuleStorage storage $ = _shareModuleStorage();
        if (!_shareModuleStorage().queues[asset].contains(queue)) {
            revert Forbidden();
        }
        address hook = getHook(queue);
        if (hook != address(0)) {
            Address.functionDelegateCall(hook, abi.encodeCall(IHook.callHook, (asset, assets)));
        }
        if (!$.isDepositQueue[queue]) {
            TransferLibrary.sendAssets(asset, queue, assets);
        }
        emit HookCalled(queue, asset, assets, hook);
    }

    /// @inheritdoc IShareModule
    function handleReport(address asset, uint224 priceD18, uint32 depositTimestamp, uint32 redeemTimestamp)
        external
        nonReentrant
    {
        ShareModuleStorage storage $ = _shareModuleStorage();
        if (_msgSender() != $.oracle) {
            revert Forbidden();
        }
        IShareManager shareManager_ = IShareManager($.shareManager);
        IFeeManager feeManager_ = IFeeManager($.feeManager);
        uint256 fees;
        if (asset == feeManager_.baseAsset(address(this))) {
            address feeRecipient_ = feeManager_.feeRecipient();
            fees = feeManager_.calculateFee(
                address(this),
                asset,
                priceD18,
                shareManager_.totalShares() - shareManager_.activeSharesOf(feeRecipient_)
            );
            if (fees != 0) {
                shareManager_.mint(feeRecipient_, fees);
            }
            feeManager_.updateState(asset, priceD18);
        }
        EnumerableSet.AddressSet storage queues = _shareModuleStorage().queues[asset];
        uint256 length = queues.length();
        for (uint256 i = 0; i < length; i++) {
            address queue = queues.at(i);
            IQueue(queue).handleReport(priceD18, $.isDepositQueue[queue] ? depositTimestamp : redeemTimestamp);
        }
        emit ReportHandled(asset, priceD18, depositTimestamp, redeemTimestamp, fees);
    }

    // Internal functions

    function __ShareModule_init(
        address shareManager_,
        address feeManager_,
        address oracle_,
        address defaultDepositHook_,
        address defaultRedeemHook_,
        uint256 queueLimit_
    ) internal onlyInitializing {
        if (shareManager_ == address(0) || feeManager_ == address(0) || oracle_ == address(0)) {
            revert ZeroAddress();
        }
        ShareModuleStorage storage $ = _shareModuleStorage();
        $.shareManager = shareManager_;
        $.feeManager = feeManager_;
        $.oracle = oracle_;
        $.defaultDepositHook = defaultDepositHook_;
        $.defaultRedeemHook = defaultRedeemHook_;
        $.queueLimit = queueLimit_;
    }

    function _shareModuleStorage() internal view returns (ShareModuleStorage storage $) {
        bytes32 slot = _shareModuleStorageSlot;
        assembly {
            $.slot := slot
        }
    }
}
"
    },
    "src/modules/VaultModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/modules/IVaultModule.sol";

import "../libraries/SlotLibrary.sol";
import "../libraries/TransferLibrary.sol";

import "./ACLModule.sol";

abstract contract VaultModule is IVaultModule, ACLModule {
    using EnumerableSet for EnumerableSet.AddressSet;

    /// @inheritdoc IVaultModule
    bytes32 public constant CREATE_SUBVAULT_ROLE = keccak256("modules.VaultModule.CREATE_SUBVAULT_ROLE");
    /// @inheritdoc IVaultModule
    bytes32 public constant DISCONNECT_SUBVAULT_ROLE = keccak256("modules.VaultModule.DISCONNECT_SUBVAULT_ROLE");
    /// @inheritdoc IVaultModule
    bytes32 public constant RECONNECT_SUBVAULT_ROLE = keccak256("modules.VaultModule.RECONNECT_SUBVAULT_ROLE");
    /// @inheritdoc IVaultModule
    bytes32 public constant PULL_LIQUIDITY_ROLE = keccak256("modules.VaultModule.PULL_LIQUIDITY_ROLE");
    /// @inheritdoc IVaultModule
    bytes32 public constant PUSH_LIQUIDITY_ROLE = keccak256("modules.VaultModule.PUSH_LIQUIDITY_ROLE");

    /// @inheritdoc IVaultModule
    IFactory public immutable subvaultFactory;
    /// @inheritdoc IVaultModule
    IFactory public immutable verifierFactory;

    bytes32 private immutable _subvaultModuleStorageSlot;

    constructor(string memory name_, uint256 version_, address subvaultFactory_, address verifierFactory_) {
        _subvaultModuleStorageSlot = SlotLibrary.getSlot("VaultModule", name_, version_);
        subvaultFactory = IFactory(subvaultFactory_);
        verifierFactory = IFactory(verifierFactory_);
    }

    // View functionss

    /// @inheritdoc IVaultModule
    function subvaults() public view returns (uint256) {
        return _vaultStorage().subvaults.length();
    }

    /// @inheritdoc IVaultModule
    function subvaultAt(uint256 index) public view returns (address) {
        return _vaultStorage().subvaults.at(index);
    }

    /// @inheritdoc IVaultModule
    function hasSubvault(address subvault) public view returns (bool) {
        return _vaultStorage().subvaults.contains(subvault);
    }

    /// @inheritdoc IVaultModule
    function riskManager() public view returns (IRiskManager) {
        return IRiskManager(_vaultStorage().riskManager);
    }

    // Mutable functions

    /// @inheritdoc IVaultModule
    function createSubvault(uint256 version, address owner, address verifier)
        external
        onlyRole(CREATE_SUBVAULT_ROLE)
        nonReentrant
        returns (address subvault)
    {
        if (!verifierFactory.isEntity(verifier)) {
            revert NotEntity(verifier);
        }
        if (address(IVerifier(verifier).vault()) != address(this)) {
            revert Forbidden();
        }
        subvault = subvaultFactory.create(version, owner, abi.encode(verifier, address(this)));
        _vaultStorage().subvaults.add(subvault);
        emit SubvaultCreated(subvault, version, owner, verifier);
    }

    /// @inheritdoc IVaultModule
    function disconnectSubvault(address subvault) external onlyRole(DISCONNECT_SUBVAULT_ROLE) {
        VaultModuleStorage storage $ = _vaultStorage();
        if (!$.subvaults.remove(subvault)) {
            revert NotConnected(subvault);
        }
        emit SubvaultDisconnected(subvault);
    }

    /// @inheritdoc IVaultModule
    function reconnectSubvault(address subvault) external onlyRole(RECONNECT_SUBVAULT_ROLE) {
        VaultModuleStorage storage $ = _vaultStorage();
        if (!subvaultFactory.isEntity(subvault)) {
            revert NotEntity(subvault);
        }
        if (ISubvaultModule(subvault).vault() != address(this)) {
            revert InvalidSubvault(subvault);
        }
        IVerifier verifier = IVerifierModule(subvault).verifier();
        if (!verifierFactory.isEntity(address(verifier))) {
            revert NotEntity(address(verifier));
        }
        if (address(verifier.vault()) != address(this)) {
            revert Forbidden();
        }
        if (!$.subvaults.add(subvault)) {
            revert AlreadyConnected(subvault);
        }
        emit SubvaultReconnected(subvault, address(verifier));
    }

    /// @inheritdoc IVaultModule
    function pullAssets(address subvault, address asset, uint256 value)
        external
        onlyRole(PULL_LIQUIDITY_ROLE)
        nonReentrant
    {
        _pullAssets(subvault, asset, value);
    }

    /// @inheritdoc IVaultModule
    function pushAssets(address subvault, address asset, uint256 value)
        external
        onlyRole(PUSH_LIQUIDITY_ROLE)
        nonReentrant
    {
        _pushAssets(subvault, asset, value);
    }

    /// @inheritdoc IVaultModule
    function hookPullAssets(address subvault, address asset, uint256 value) external {
        if (_msgSender() != address(this)) {
            revert Forbidden();
        }
        _pullAssets(subvault, asset, value);
    }

    /// @inheritdoc IVaultModule
    function hookPushAssets(address subvault, address asset, uint256 value) external {
        if (_msgSender() != address(this)) {
            revert Forbidden();
        }
        _pushAssets(subvault, asset, value);
    }

    // Internal functions

    function _pullAssets(address subvault, address asset, uint256 value) internal {
        riskManager().modifySubvaultBalance(subvault, asset, -int256(value));
        ISubvaultModule(subvault).pullAssets(asset, value);
        emit AssetsPulled(asset, subvault, value);
    }

    function _pushAssets(address subvault, address asset, uint256 value) internal {
        riskManager().modifySubvaultBalance(subvault, asset, int256(value));
        TransferLibrary.sendAssets(asset, subvault, value);
        emit AssetsPushed(asset, subvault, value);
    }

    function __VaultModule_init(address riskManager_) internal onlyInitializing {
        if (riskManager_ == address(0)) {
            revert ZeroAddress();
        }
        _vaultStorage().riskManager = riskManager_;
    }

    function _vaultStorage() private view returns (VaultModuleStorage storage $) {
        bytes32 slot = _subvaultModuleStorageSlot;
        assembly {
            $.slot := slot
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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);
}
"
    },
    "src/interfaces/modules/IACLModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../permissions/IMellowACL.sol";
import "./IBaseModule.sol";

/// @notice Interface for the ACLModule, implements IMellowACL
interface IACLModule is IMellowACL {
    /// @notice Thrown when a zero address is provided
    error ZeroAddress();

    /// @notice Thrown when an unauthorized caller attempts a restricted operation
    error Forbidden();
}
"
    },
    "src/libraries/SlotLibrary.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

/// @title SlotLibrary
/// @notice Library for computing deterministic and collision-resistant storage slots
/// @dev Used to generate unique storage slots for upgradeable modules using string identifiers
library SlotLibrary {
    /// @notice Computes a unique storage slot based on the module's identifiers
    /// @param contractName Logical contract/module name (e.g., "ShareModule")
    /// @param name Human-readable instance name (e.g., "Mellow")
    /// @param version Version number for the module configuration
    /// @return A bytes32 value representing the derived storage slot
    function getSlot(string memory contractName, string memory name, uint256 version) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                uint256(keccak256(abi.encodePacked("mellow.flexible-vaults.storage.", contractName, name, version))) - 1
            )
        ) & ~bytes32(uint256(0xff));
    }
}
"
    },
    "src/permissions/MellowACL.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/permissions/IMellowACL.sol";

import "../libraries/SlotLibrary.sol";

abstract contract MellowACL is IMellowACL, AccessControlEnumerableUpgradeable {
    using EnumerableSet for EnumerableSet.Bytes32Set;

    bytes32 private immutable _mellowACLStorageSlot;

    constructor(string memory name_, uint256 version_) {
        _mellowACLStorageSlot = SlotLibrary.getSlot("MellowACL", name_, version_);
        _disableInitializers();
    }

    // View functions

    /// @inheritdoc IMellowACL
    function supportedRoles() external view returns (uint256) {
        return _mellowACLStorage().supportedRoles.length();
    }

    /// @inheritdoc IMellowACL
    function supportedRoleAt(uint256 index) external view returns (bytes32) {
        return _mellowACLStorage().supportedRoles.at(index);
    }

    /// @inheritdoc IMellowACL
    function hasSupportedRole(bytes32 role) external view returns (bool) {
        return _mellowACLStorage().supportedRoles.contains(role);
    }

    // Internal functions

    function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
        if (super._grantRole(role, account)) {
            if (_mellowACLStorage().supportedRoles.add(role)) {
                emit RoleAdded(role);
            }
            return true;
        }
        return false;
    }

    function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
        if (super._revokeRole(role, account)) {
            if (getRoleMemberCount(role) == 0) {
                _mellowACLStorage().supportedRoles.remove(role);
                emit RoleRemoved(role);
            }
            return true;
        }
        return false;
    }

    function _mellowACLStorage() private view returns (MellowACLStorage storage $) {
        bytes32 slot = _mellowACLStorageSlot;
        assembly {
            $.slot := slot
        }
    }
}
"
    },
    "src/modules/BaseModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../interfaces/modules/IBaseModule.sol";

abstract contract BaseModule is IBaseModule, ContextUpgradeable, ReentrancyGuardUpgradeable {
    constructor() {
        _disableInitializers();
    }

    // View functions

    /// @inheritdoc IBaseModule
    function getStorageAt(bytes32 slot) external pure returns (StorageSlot.Bytes32Slot memory) {
        return StorageSlot.getBytes32Slot(slot);
    }

    /// @inheritdoc IERC721Receiver
    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    // Mutable functions

    receive() external payable {}

    // Internal functions

    function __BaseModule_init() internal onlyInitializing {
        __ReentrancyGuard_init();
    }
}
"
    },
    "src/interfaces/modules/IShareModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../factories/IFactory.sol";
import "../hooks/IRedeemHook.sol";
import "../managers/IFeeManager.sol";
import "../managers/IShareManager.sol";
import "../oracles/IOracle.sol";
import "../queues/IDepositQueue.sol";
import "../queues/IQueue.sol";
import "../queues/IRedeemQueue.sol";
import "./IBaseModule.sol";

/// @title IShareModule
/// @notice Manages user-facing interactions with the vault via deposit/redeem queues, hooks, and share accounting.
/// @dev Coordinates oracle report handling, hook invocation, fee calculation, and queue lifecycle.
interface IShareModule is IBaseModule {
    /// @notice Thrown when an unsupported asset is used for queue creation.
    error UnsupportedAsset(address asset);

    /// @notice Thrown when the number of queues exceeds the allowed system-wide maximum.
    error QueueLimitReached();

    /// @notice Thrown when an operation is attempted with a zero-value parameter.
    error ZeroValue();

    /// @dev Storage structure for the ShareModule.
    struct ShareModuleStorage {
        address shareManager; // Address of the ShareManager responsible for minting/burning shares
        address feeManager; // Address of the FeeManager that calculates and collects protocol fees
        address oracle; // Address of the Oracle
        address defaultDepositHook; // Optional hook that is called by default after DepositQueue requests are processed
        address defaultRedeemHook; // Optional hook that is called by default before RedeemQueue requests are processed
        uint256 queueCount; // Total number of queues across all assets
        uint256 queueLimit; // Maximum number of queues allowed in the system
        mapping(address => address) customHooks; // Optional queue-specific hooks
        mapping(address => bool) isDepositQueue; // Whether the queue is a deposit queue
        mapping(address => bool) isPausedQueue; // Whether queue operations are currently paused
        mapping(address => EnumerableSet.AddressSet) queues; // Mapping of asset to its associated queues
        EnumerableSet.AddressSet assets; // Set of all supported assets with queues
    }

    /// @notice Role identifier for managing per-queue and default hooks
    function SET_HOOK_ROLE() external view returns (bytes32);

    /// @notice Role identifier for creating new queues
    function CREATE_QUEUE_ROLE() external view returns (bytes32);

    /// @notice Role identifier for changing the active/paused status of queues
    function SET_QUEUE_STATUS_ROLE() external view returns (bytes32);

    /// @notice Role identifier for modifying the global queue limit
    function SET_QUEUE_LIMIT_ROLE() external view returns (bytes32);

    /// @notice Role identifier for removing existing queues
    function REMOVE_QUEUE_ROLE() external view returns (bytes32);

    /// @notice Returns the ShareManager used for minting and burning shares
    function shareManager() external view returns (IShareManager);

    /// @notice Returns the FeeManager contract used for fee calculations
    function feeManager() external view returns (IFeeManager);

    /// @notice Returns the Oracle contract used for handling reports and managing supported assets.
    function oracle() external view returns (IOracle);

    /// @notice Returns the factory used for deploying deposit queues
    function depositQueueFactory() external view returns (IFactory);

    /// @notice Returns the factory used for deploying redeem queues
    function redeemQueueFactory() external view returns (IFactory);

    /// @notice Returns total number of distinct assets with queues
    function getAssetCount() external view returns (uint256);

    /// @notice Returns the address of the asset at the given index
    function assetAt(uint256 index) external view returns (address);

    /// @notice Returns whether the given asset is associated with any queues
    function hasAsset(address asset) external view returns (bool);

    /// @notice Returns whether the given queue is registered
    function hasQueue(address queue) external view returns (bool);

    /// @notice Returns whether the given queue is a deposit queue
    function isDepositQueue(address queue) external view returns (bool);

    /// @notice Returns whether the given queue is currently paused
    function isPausedQueue(address queue) external view returns (bool);

    /// @notice Returns number of queues associated with a given asset
    function getQueueCount(address asset) external view returns (uint256);

    /// @notice Returns the total number of queues across all assets
    function getQueueCount() external view returns (uint256);

    /// @notice Returns the queue at the given index for the specified asset
    function queueAt(address asset, uint256 index) external view returns (address);

    /// @notice Returns the hook assigned to a queue (customHook or defaultHook as a fallback)
    function getHook(address queue) external view returns (address hook);

    /// @notice Returns the default hook for deposit queues
    function defaultDepositHook() external view returns (address);

    /// @notice Returns the default hook for redeem queues
    function defaultRedeemHook() external view returns (address);

    /// @notice Returns the current global queue limit
    function queueLimit() external view returns (uint256);

    /// @notice Returns the total number of claimable shares for a given user
    function claimableSharesOf(address account) external view returns (uint256 shares);

    /// @notice Called by redeem queues to check the amount of assets available for instant withdrawal
    function getLiquidAssets() external view returns (uint256);

    /// @notice Claims all claimable shares from deposit queues for the specified account
    function claimShares(address account) external;

    /// @notice Assigns a custom hook contract to a specific queue
    function setCustomHook(address queue, address hook) external;

    /// @notice Sets the global default deposit hook
    function setDefaultDepositHook(address hook) external;

    /// @notice Sets the global default redeem hook
    function setDefaultRedeemHook(address hook) external;

    /// @notice Creates a new deposit or redeem queue for a given asset
    function createQueue(uint256 version, bool isDepositQueue, address owner, address asset, bytes calldata data)
        external;

    /// @notice Removes a queue from the system if its `canBeRemoved()` function returns true
    function removeQueue(address queue) external;

    /// @notice Sets the maximum number of allowed queues across the module
    function setQueueLimit(uint256 limit) external;

    /// @notice Pauses or resumes a queue's operation
    function setQueueStatus(address queue, bool isPaused) external;

    /// @notice Invokes a queue's hook (also transfers assets to the queue for redeem queues)
    function callHook(uint256 assets) external;

    /// @notice Handles an oracle price report, distributes fees and calls internal hooks
    function handleReport(address asset, uint224 priceD18, uint32 depositTimestamp, uint32 redeemTimestamp) external;

    /// @notice Emitted when a user successfully claims shares from deposit queues
    event SharesClaimed(address indexed account);

    /// @notice Emitted when a queue-specific custom hook is updated
    event CustomHookSet(address indexed queue, address indexed hook);

    /// @notice Emitted when a new queue is created
    event QueueCreated(address indexed queue, address indexed asset, bool isDepositQueue);

    /// @notice Emitted when a queue is removed
    event QueueRemoved(address indexed queue, address indexed asset);

    /// @notice Emitted after a queue hook is successfully called
    event HookCalled(address indexed queue, address indexed asset, uint256 assets, address hook);

    /// @notice Emitted when the global queue limit is updated
    event QueueLimitSet(uint256 limit);

    /// @notice Emitted when a queue's paused status changes
    event SetQueueStatus(address indexed queue, bool indexed isPaused);

    /// @notice Emitted when a new default hook is configured
    event DefaultHookSet(address indexed hook, bool isDepositHook);

    /// @notice Emitted after processing a price report and fee distribution
    event ReportHandled(
        address indexed asset, uint224 indexed priceD18, uint32 depositTimestamp, uint32 redeemTimestamp, uint256 fees
    );
}
"
    },
    "src/libraries/TransferLibrary.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/// @title TransferLibrary
/// @notice Library for unified handling of native ETH and ERC20 asset transfers.
/// @dev Provides safe and abstracted methods for sending and receiving both ETH and ERC20 tokens.
///
/// # ETH Convention
/// Uses the constant `ETH = 0xEeee...EeE` to distinguish native ETH from ERC20 tokens.
library TransferLibrary {
    using SafeERC20 for IERC20;

    /// @notice Error thrown when `msg.value` does not match expected ETH amount
    error InvalidValue();

    /// @dev Placeholder address used to represent native ETH transfers
    address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /// @notice Safely sends assets (ETH or ERC20) to a recipient
    /// @param asset Address of the asset to send (use `ETH` constant for native ETH)
    /// @param to Recipient address
    /// @param assets Amount of assets to send
    /// @dev Uses `Address.sendValue` for ETH and `safeTransfer` for ERC20
    function sendAssets(address asset, address to, uint256 assets) internal {
        if (asset == ETH) {
            Address.sendValue(payable(to), assets);
        } else {
            IERC20(asset).safeTransfer(to, assets);
        }
    }

    /// @notice Safely receives assets (ETH or ERC20) from a sender
    /// @param asset Address of the asset to receive (use `ETH` constant for native ETH)
    /// @param from Sender address (only used for ERC20)
    /// @param assets Amount of assets expected to receive
    /// @dev Reverts if `msg.value` is incorrect for ETH or uses `safeTransferFrom` for ERC20
    function receiveAssets(address asset, address from, uint256 assets) internal {
        if (asset == ETH) {
            if (msg.value != assets) {
                revert InvalidValue();
            }
        } else {
            IERC20(asset).safeTransferFrom(from, address(this), assets);
        }
    }

    /// @notice Returns the balance of an account for a given asset
    /// @param asset Address of the asset to check the balance of (use `ETH` constant for native ETH)
    /// @param account Address of the account to check the balance of
    /// @return Balance of the account for the given asset
    function balanceOf(address asset, address account) internal view returns (uint256) {
        if (asset == ETH) {
            return account.balance;
        } else {
            return IERC20(asset).balanceOf(account);
        }
    }
}
"
    },
    "src/interfaces/modules/IVaultModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "../factories/IFactory.sol";
import "../managers/IRiskManager.sol";
import "./IACLModule.sol";
import "./IShareModule.sol";
import "./ISubvaultModule.sol";
import "./IVerifierModule.sol";

/// @title IVaultModule
/// @notice Interface for a VaultModule that manages and coordinates asset flows
/// and sub-vault connections within a modular vault architecture.
interface IVaultModule is IACLModule {
    /// @dev Thrown when trying to reconnect a subvault that is already connected.
    error AlreadyConnected(address subvault);

    /// @dev Thrown when trying to disconnect a subvault that is not currently connected.
    error NotConnected(address subvault);

    /// @dev Thrown when the provided address is not a valid factory-deployed entity.
    error NotEntity(address subvault);

    /// @dev Thrown when a given subvault is not correctly configured.
    error InvalidSubvault(address subvault);

    /// @notice Storage structure used to track vault state and subvaults.
    struct VaultModuleStorage {
        address riskManager;
        EnumerableSet.AddressSet subvaults;
    }

    /// @notice Role that allows the creation of new subvaults.
    function CREATE_SUBVAULT_ROLE() external view returns (bytes32);

    /// @notice Role that allows disconnecting existing subvaults.
    function DISCONNECT_SUBVAULT_ROLE() external view returns (bytes32);

    /// @notice Role identifier for reconnecting subvaults.
    /// @dev Grants permission to reattach a subvault to the vault system.
    /// This includes both re-connecting a previously disconnected subvault
    /// and connecting a new, properly configured subvault for the first time.
    /// Used to maintain modularity and support hot-swapping of subvaults.
    function RECONNECT_SUBVAULT_ROLE() external view returns (bytes32);

    /// @notice Role that allows pulling assets from subvaults.
    function PULL_LIQUIDITY_ROLE() external view returns (bytes32);

    /// @notice Role that allows pushing assets into subvaults.
    function PUSH_LIQUIDITY_ROLE() external view returns (bytes32);

    /// @notice Returns the factory used to deploy new subvaults.
    function subvaultFactory() external view returns (IFactory);

    /// @notice Returns the factory used to deploy verifiers.
    function verifierFactory() external view returns (IFactory);

    /// @notice Returns the total number of connected subvaults.
    function subvaults() external view returns (uint256);

    /// @notice Returns the address of the subvault at a specific index.
    /// @param index Index in the set of subvaults.
    function subvaultAt(uint256 index) external view returns (address);

    /// @notice Checks whether a given address is currently an active subvault.
    /// @param subvault Address to check.
    function hasSubvault(address subvault) external view returns (bool);

    /// @notice Returns the address of the risk manager module.
    function riskManager() external view returns (IRiskManager);

    /// @notice Creates and connects a new subvault.
    /// @param version Version of the subvault contract to deploy.
    /// @param owner Owner of the newly created subvault.
    /// @param verifier Verifier contract used for permissions within the subvault.
    /// @return subvault Address of the newly created subvault.
    function createSubvault(uint256 version, address owner, address verifier) external returns (address subvault);

    /// @notice Disconnects a subvault from the vault.
    /// @param subvault Address of the subvault to disconnect.
    function disconnectSubvault(address subvault) external;

    /// @notice Reconnects a subvault to the main vault system.
    /// @dev Can be used to reattach either:
    /// - A previously disconnected subvault, or
    /// - A newly created and properly configured subvault.
    /// Requires the caller to have the `RECONNECT_SUBVAULT_ROLE`.
    /// @param subvault The address of the subvault to reconnect.
    function reconnectSubvault(address subvault) external;

    /// @notice Sends a specified amount of assets from the vault to a connected subvault.
    /// @param subvault Address of the destination subvault.
    /// @param asset Address of the asset to transfer.
    /// @param value Amount of the asset to send.
    function pushAssets(address subvault, address asset, uint256 value) external;

    /// @notice Pulls a specified amount of assets from a connected subvault into the vault.
    /// @param subvault Address of the source subvault.
    /// @param asset Address of the asset to transfer.
    /// @param value Amount of the asset to receive.
    function pullAssets(address subvault, address asset, uint256 value) external;

    /// @notice Internally used function that transfers assets from the vault to a connected subvault.
    /// @dev Must be invoked by the vault itself via hook execution logic.
    /// @param subvault Address of the destination subvault.
    /// @param asset Address of the asset being transferred.
    /// @param value Amount of the asset being transferred.
    function hookPushAssets(address subvault, address asset, uint256 value) external;

    /// @notice Internally used function that pulls assets from a connected subvault into the vault.
    /// @dev Must be invoked by the vault itself via hook execution logic.
    /// @param subvault Address of the source subvault.
    /// @param asset Address of the asset being pulled.
    /// @param value Amount of the asset being pulled.
    function hookPullAssets(address subvault, address asset, uint256 value) external;

    /// @notice Emitted when a new subvault is created.
    event SubvaultCreated(address indexed subvault, uint256 version, address indexed owner, address indexed verifier);

    /// @notice Emitted when a subvault is disconnected.
    event SubvaultDisconnected(address indexed subvault);

    /// @notice Emitted when a subvault is reconnected.
    event SubvaultReconnected(address indexed subvault, address indexed verifier);

    /// @notice Emitted when assets are pulled from a subvault into the vault.
    event AssetsPulled(address indexed asset, address indexed subvault, uint256 value);

    /// @notice Emitted when assets are pushed from the vault into a subvault.
    event AssetsPushed(address indexed asset, address indexed subvault, uint256 value);
}
"
    },
    "src/interfaces/permissions/IMellowACL.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";

/// @notice Interface for the MellowACL contract, which extends OpenZeppelin's AccessControlEnumerable
/// @dev Adds tracking of which roles are actively in use (i.e., assigned to at least one address)
interface IMellowACL is IAccessControlEnumerable {
    /// @notice Storage layout used to track actively assigned roles
    struct MellowACLStorage {
        EnumerableSet.Bytes32Set supportedRoles; // Set of roles that have at least one assigned member
    }

    /// @notice Returns the total number of unique roles that are currently assigned
    function supportedRoles() external view returns (uint256);

    /// @notice Returns the role at the specified index in the set of active roles
    /// @param index Index within the supported role set
    /// @return role The bytes32 identifier of the role
    function supportedRoleAt(uint256 index) external view returns (bytes32);

    /// @notice Checks whether a given role is currently active (i.e., has at least one member)
    /// @param role The bytes32 identifier of the role to check
    /// @return isActive True if the role has any members assigned
    function hasSupportedRole(bytes32 role) external view returns (bool);

    /// @notice Emitted when a new role is granted for the first time
    event RoleAdded(bytes32 indexed role);

    /// @notice Emitted when a role loses its last member
    event RoleRemoved(bytes32 indexed role);
}
"
    },
    "src/interfaces/modules/IBaseModule.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/utils/StorageSlot.sol";

/// @notice Interface for base module functionality shared across all modules
/// @dev Provides basic utilities such as raw storage access, ERC721 receiver support and `receive()` callback
interface IBaseModule is IERC721Receiver {
    /// @notice Returns a reference to a storage slot as a `StorageSlot.Bytes32Slot` struct
    /// @param slot The keccak256-derived storage slot identifier
    /// @return A struct exposing the `.value` field stored at the given slot
    function getStorageAt(bytes32 slot) external pure returns (StorageSlot.Bytes32Slot memory);
}
"
    },
    "src/interfaces/factories/IFactory.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;

import "./IFactoryEntity.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/// @title IFactory
/// @notice Interface for a factory that manages deployable upgradeable proxies and implementation governance.
interface IFactory is IFactoryEntity {
    /// @notice Thrown when attempting to access an index outside the valid range.
    error OutOfBounds(uint256 index);

    /// @notice Thrown when trying to use an implementation version that is blacklisted.
    error BlacklistedVersion(uint256 version);

    /// @notice Thrown when an implementation is already in the accepted list.
    error ImplementationAlreadyAccepted(address implementation);

    /// @notice Thrown when an implementation has already been proposed.
    error ImplementationAlreadyProposed(address implementation);

    /// @notice Thrown when attempting to accept an implementation that was never proposed.
    error ImplementationNotProposed(address implementation);

    /// @dev Internal storage structure for tracking factory state.
    struct FactoryStorage {
        EnumerableSet.AddressSet entities; // Set of deployed upgradeable proxies
        EnumerableSet.AddressSet implementations; // Set of accepted implementation addresses
        EnumerableSet.AddressSet proposals; // Set of currently proposed (but not yet accepted) implementations
        mapping(uint256 version => bool) isBlacklisted; // Tracks whether a given version is blacklisted
    }

    /// @notice Returns the total number of deployed entities (proxies).
    function entities() external view returns (uint256);

    /// @notice Returns the address of the deployed entity at a given index.
    function entityAt(uint256 index) external view returns (address);

    /// @notice Returns whether the given address is a deployed entity.
    function isEntity(address entity) external view returns (bool);

    /// @notice Returns the total number of accepted implementation contracts.
    function implementations() external view returns (uint256);

    /// @notice Returns the implementation address at the given index.
    function implementationAt(uint256 index) external view returns (address);

    /// @notice Returns the number of currently proposed (pending) implementations.
    function proposals() external view returns (uint256);

    /// @notice Returns the address of a proposed implementation at a given index.
    function proposalAt(uint256 index) external view returns (address);

    /// @notice Returns whether the given implementation version is blacklisted.
    function isBlacklisted(uint256 version) external view returns (bool);

    /// @notice Updates the blacklist status for a specific implementation version.
    /// @param version The version index to update.
    /// @param flag True to blacklist, false to unblacklist.
    function setBlacklistStatus(uint256 version, bool flag) external;

    /// @notice Proposes a new implementation for future deployment.
    /// @param implementation The address of the proposed implementation contract.
    function proposeImplementation(address implementation) external;

    /// @notice Approves a previously proposed implementation, allowing it to be used for deployments.
    /// @param implementation The address of the proposed implementation to approve.
    function acceptProposedImplementation(address implementation) external;

    /// @notice Deploys a new TransparentUpgradeableProxy using an accepted implementation.
    /// @param version The version index of the implementation to use.
    /// @param owner The address that will become the owner of the proxy.
    /// @param initParams Calldata to be passed for initialization of the new proxy instance.
    /// @return instance The address of the newly deployed proxy contract.
    function create(uint256 version, address owner, bytes calldata initParams) external returns (address instance);

    /// @notice Emitted when the blacklist status of a version is updated.
    event SetBlacklistStatus(uint256 version, bool flag);

    /// @notice Emitted when a new implementation is proposed.
    event ProposeImplementation(address implementation);

    /// @notice Emitted when a proposed implementation is accepted.
    event AcceptProposedImplementation(address implementation);

    /// @notice Emitted when a new proxy instance is successfully deployed.
    event Created(address indexed instance, uint256 indexed version, address indexed owner, bytes initParams);
}
"
    },
    "src/interfaces/hooks/IRedeemHook.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Swap, Liquidity, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xba166e23adbbe8d43ab21eb4d9cd7e290c202176|verified:true|block:23419146|tx:0xa18afd41014c820ad3c62129fededa814a87891e248ba11fe69a4d409e1cf611|first_check:1758554716

Submitted on: 2025-09-22 17:25:17

Comments

Log in to comment.

No comments yet.