AllocatorVaultV3

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/allocator-vaults/AllocatorVaultV3.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.25;

import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { ERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title AllocatorVaultV3
 * @dev Implementation of an ERC4626 compliant Allocator Vault with fee management.
 * The vault delegates asset management to a strategy contract while handling deposits,
 * withdrawals, and fee calculations.
 */
contract AllocatorVaultV3 is ERC4626Upgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
    using SafeERC20 for IERC20;

    /// @notice Emitted when a harvest operation is executed
    /// @param caller The address that initiated the harvest
    /// @param sharesMinted The number of fee shares minted during the harvest
    /// @param previousTotalUnderlying The total underlying assets before harvest
    /// @param currentTotalUnderlying The total underlying assets after harvest
    event Harvested(
        address indexed caller, uint256 sharesMinted, uint256 previousTotalUnderlying, uint256 currentTotalUnderlying
    );

    /// @notice Emitted when the vault configuration is updated
    /// @param config The new configuration applied to the vault
    event VaultConfigured(AllocatorVaultConfig config);

    /// @notice Emitted when the vault is paused or unpaused
    /// @param paused The new paused state (true if paused)
    event PausedSet(bool paused);

    /// @notice Emitted when a target and selector are added to or removed from the whitelist
    /// @param target The address of the contract target
    /// @param selector The 4-byte function selector affected
    /// @param added True if added to whitelist, false if removed
    event Whitelisted(address indexed target, bytes4 selector, bool added);

    /// @notice Emitted when additional reward tokens are claimed and transferred
    /// @param target The address of the contract call used to claim rewards
    /// @param asset The address of the reward token claimed
    /// @param amount The amount of tokens successfully claimed
    event RewardsClaimed(address indexed target, address indexed asset, uint256 amount);

    /// @notice Emitted when miscellaneous ERC20 tokens are swept to the fee recipient
    /// @param asset The address of the token being swept
    /// @param amount The amount of tokens transferred
    event MiscellaneousRewardClaimed(address indexed asset, uint256 amount);

    /**
     * @dev Configuration for the vault's fee structure and behavior
     * @param depositFee Fee charged when depositing assets (in basis points)
     * @param performanceFee Fee charged on performance gains (in basis points)
     * @param managementFee Ongoing fee for management (in basis points)
     * @param feeRecipient Address that receives collected fees
     * @param hasCooldown Whether withdrawals are subject to a cooldown period
     */
    struct AllocatorVaultConfig {
        uint256 depositFee;
        uint256 performanceFee;
        uint256 managementFee;
        address feeRecipient;
        bool hasCooldown;
    }

    // Constants
    uint256 private constant MAX_BPS = 10_000_000_000;
    uint256 private constant MAX_PERF_FEE = 5_000_000_000;
    uint256 private constant MAX_MGMT_FEE = 1_000_000_000;
    uint256 private constant MAX_DEP_FEE = 500_000_000;
    uint256 private constant SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
    uint256 private constant VIRTUAL_ASSETS = 1;
    uint256 private constant VIRTUAL_SHARES = 10 ** 6;
    bytes32 public constant HARVESTER_ROLE = keccak256("HARVESTER_ROLE");

    // Variables
    bool public paused;
    address public underlying;
    AllocatorVaultConfig public config;
    IERC4626 public strategy;
    uint256 public lastStamp;
    uint256 public totalUnderlyingStamp;

    mapping(address target => mapping(bytes4 selector => bool isWhitelisted)) public whitelistedTargets;

    // modifiers
    /**
     * @dev Ensures the vault is not paused when executing function
     */
    modifier notPaused() {
        require(!paused, "Paused");
        _;
    }

    /**
     * @dev Initializes the vault with the specified parameters
     * @param _underlying Address of the underlying token
     * @param _strategy Strategy contract that will manage the assets
     * @param vaultTokenName Name for the vault's share token
     * @param vaultTokenSymbol Symbol for the vault's share token
     */
    function initialize(
        address _underlying,
        IERC4626 _strategy,
        string memory vaultTokenName,
        string memory vaultTokenSymbol
    )
        public
        initializer
    {
        __ERC4626_init(IERC20(address(_strategy)));
        __ERC20_init(vaultTokenName, vaultTokenSymbol);
        __AccessControl_init();
        __ReentrancyGuard_init();

        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(HARVESTER_ROLE, msg.sender);

        require(address(_strategy) != address(0), "Invalid strategy");
        require(_underlying != address(0), "Invalid underlying token");
        require(_underlying == _strategy.asset(), "Underlying token mismatch");
        require(address(_strategy) == asset(), "Strategy token mismatch");
        strategy = _strategy;
        underlying = _underlying;
        lastStamp = block.timestamp;
    }

    /**
     * @dev Sets the configuration for the vault
     * @param _config New configuration to apply
     */
    function configureVault(AllocatorVaultConfig memory _config) public onlyRole(DEFAULT_ADMIN_ROLE) {
        require(_config.depositFee <= MAX_DEP_FEE, "Deposit fee too high");
        require(_config.performanceFee <= MAX_PERF_FEE, "Performance fee too high");
        require(_config.managementFee <= MAX_MGMT_FEE, "Management fee too high");
        require(_config.feeRecipient != address(0), "Invalid fee recipient");
        config = _config;
        emit VaultConfigured(_config);
    }

    /**
     * @dev Pauses or unpauses the vault
     * @param _paused New paused state
     */
    function setPaused(bool _paused) external onlyRole(DEFAULT_ADMIN_ROLE) {
        paused = _paused;
        emit PausedSet(_paused);
    }

    /**
     * @dev Returns the total amount of assets in the vault
     * @notice the contract's assets are actually the strategy's shares. This function returns
     * the amount of strategy shares that this vault holds, not the underlying tokens.
     * @return Total strategy shares held by the vault
     */
    function totalAssets() public view override returns (uint256) {
        return strategy.balanceOf(address(this));
    }

    /**
     * @dev Converts an amount of assets to an equivalent amount of shares, with optional ceiling rounding
     * @param assets Amount of assets to convert
     * @param ceil If true, rounds up to ensure sufficient shares; if false, performs standard rounding
     * @return Amount of shares corresponding to the assets
     */
    function _convertToShares(uint256 assets, bool ceil) internal view returns (uint256) {
        if (ceil) {
            uint256 numerator = assets * (totalSupply() + VIRTUAL_SHARES);
            uint256 denominator = totalAssets() + VIRTUAL_ASSETS;
            uint256 shares = (numerator + denominator - 1) / denominator;
            return shares;
        } else {
            return (assets * (totalSupply() + VIRTUAL_SHARES)) / (totalAssets() + VIRTUAL_ASSETS);
        }
    }

    /**
     * @dev Converts an amount of assets to an equivalent amount of shares
     * @param assets Amount of assets to convert
     * @return Amount of shares
     */
    function convertToShares(uint256 assets) public view override returns (uint256) {
        return _convertToShares(assets, false);
    }

    /**
     * @dev Converts an amount of shares to an equivalent amount of assets, with optional ceiling rounding
     * @param shares Amount of shares to convert
     * @param ceil If true, rounds up to ensure sufficient assets; if false, performs standard rounding
     * @return Amount of assets corresponding to the shares
     */
    function _convertToAssets(uint256 shares, bool ceil) internal view returns (uint256) {
        if (ceil) {
            uint256 numerator = shares * (totalAssets() + VIRTUAL_ASSETS);
            uint256 denominator = totalSupply() + VIRTUAL_SHARES;
            uint256 assets = (numerator + denominator - 1) / denominator;
            return assets;
        } else {
            return (shares * (totalAssets() + VIRTUAL_ASSETS)) / (totalSupply() + VIRTUAL_SHARES);
        }
    }

    /**
     * @dev Converts an amount of shares to an equivalent amount of assets
     * @param shares Amount of shares to convert
     * @return Amount of assets
     */
    function convertToAssets(uint256 shares) public view override returns (uint256) {
        return _convertToAssets(shares, false);
    }

    /**
     * @dev Simulates the effects of a deposit at the current block, returning the expected shares
     * @param _underlying Amount of underlying tokens to deposit
     * @return Expected amount of shares to be received
     */
    function previewDeposit(uint256 _underlying) public view override returns (uint256) {
        uint256 assets = strategy.previewDeposit(_underlying);
        uint256 newTotalShares = previewHarvest();

        uint256 shares = (assets * (newTotalShares + VIRTUAL_SHARES)) / (totalAssets() + VIRTUAL_ASSETS);
        return shares * (MAX_BPS - config.depositFee) / MAX_BPS;
    }

    /**
     * @dev Deposits underlying tokens into the vault
     * @param _underlying Amount of underlying tokens to deposit
     * @param receiver Address that will receive the shares
     * @notice Harvest is called before the deposit to ensure all accrued fees are
     * properly collected before changing the deposit state, maintaining accurate
     * accounting and fee distribution
     * @return Amount of shares minted
     */
    function deposit(uint256 _underlying, address receiver) public override notPaused nonReentrant returns (uint256) {
        uint256 maxUnderlying = maxDeposit(receiver);
        require(_underlying <= maxUnderlying, "Exceeded max deposit");

        uint256 expectedAssets = strategy.previewDeposit(_underlying);
        require(expectedAssets > 0, "Insufficient deposit amount");

        _harvest();

        IERC20(underlying).safeTransferFrom(msg.sender, address(this), _underlying);
        uint256 previousTotalAssets = totalAssets();

        IERC20(underlying).approve(address(strategy), _underlying);

        uint256 receivedAssets = strategy.deposit(_underlying, address(this));

        uint256 newShares = receivedAssets * (totalSupply() + VIRTUAL_SHARES) / (previousTotalAssets + VIRTUAL_ASSETS);
        uint256 userShares = (newShares * (MAX_BPS - config.depositFee)) / MAX_BPS;
        uint256 feeShares = newShares - userShares;

        totalUnderlyingStamp = strategy.convertToAssets(totalAssets());
        lastStamp = block.timestamp;

        _mint(receiver, userShares);
        if (feeShares > 0) {
            _mint(config.feeRecipient, feeShares);
        }

        emit Deposit(msg.sender, receiver, _underlying, userShares);
        return userShares;
    }

    /**
     * @dev Simulates the effects of a mint at the current block, returning the expected amount of underlying tokens
     * @param shares Amount of shares to mint
     * @return Expected amount of underlying tokens needed
     */
    function previewMint(uint256 shares) public view override returns (uint256) {
        uint256 newTotalShares = previewHarvest();
        uint256 actualShares = (shares * MAX_BPS) / (MAX_BPS - config.depositFee);

        // round up
        uint256 numerator = actualShares * (totalAssets() + VIRTUAL_ASSETS);
        uint256 denominator = newTotalShares + VIRTUAL_SHARES;
        uint256 assets = (numerator + denominator - 1) / denominator;
        return strategy.previewMint(assets);
    }

    /**
     * @dev Mints the specified amount of shares to the receiver by depositing underlying tokens
     * @param shares Amount of shares to mint
     * @param receiver Address that will receive the shares
     * @return Amount of underlying tokens deposited
     */
    function mint(uint256 shares, address receiver) public override notPaused nonReentrant returns (uint256) {
        uint256 maxShares = maxMint(receiver);
        require(shares > 0, "Cannot mint 0");
        require(shares <= maxShares, "Exceeded max mint");

        uint256 _underlying = previewMint(shares);
        _harvest();

        IERC20(underlying).safeTransferFrom(msg.sender, address(this), _underlying);

        IERC20(underlying).approve(address(strategy), _underlying);
        strategy.deposit(_underlying, address(this));

        uint256 newShares = (shares * MAX_BPS) / (MAX_BPS - config.depositFee);
        uint256 feeShares = newShares - shares;

        totalUnderlyingStamp = strategy.convertToAssets(totalAssets());
        lastStamp = block.timestamp;

        _mint(receiver, shares);
        if (feeShares > 0) {
            _mint(config.feeRecipient, feeShares);
        }

        emit Deposit(msg.sender, receiver, _underlying, shares);
        return _underlying;
    }

    /**
     * @dev Returns the maximum amount of underlying tokens that can be withdrawn by the receiver
     * @param receiver Address that would receive the underlying tokens
     * @return Maximum amount of underlying tokens
     */
    function maxWithdraw(address receiver) public view override returns (uint256) {
        uint256 shares = balanceOf(receiver);
        uint256 _underlying = previewRedeem(shares);
        return _underlying;
    }

    /**
     * @dev Simulates the effects of a withdrawal at the current block, returning the expected shares needed
     * @param _underlying Amount of underlying tokens to withdraw
     * @return Expected amount of shares to be burned
     */
    function previewWithdraw(uint256 _underlying) public view override returns (uint256) {
        uint256 assets = strategy.previewWithdraw(_underlying);
        uint256 newTotalShares = previewHarvest();

        if (newTotalShares == 0) {
            return 0;
        }

        uint256 numerator = assets * (newTotalShares + VIRTUAL_SHARES);
        uint256 denominator = totalAssets() + VIRTUAL_ASSETS;
        uint256 shares = (numerator + denominator - 1) / denominator;

        return shares;
    }

    /**
     * @dev Withdraws the specified amount of underlying tokens to the receiver
     * @param _underlying Amount of underlying tokens to withdraw
     * @param receiver Address that will receive the underlying tokens
     * @param owner Address that owns the shares to be burned
     * @return Amount of shares burned
     */
    function withdraw(
        uint256 _underlying,
        address receiver,
        address owner
    )
        public
        override
        nonReentrant
        returns (uint256)
    {
        uint256 maxUnderlying = maxWithdraw(owner);
        require(_underlying > 0, "Cannot withdraw 0");
        require(_underlying <= maxUnderlying, "Exceeded max withdraw");

        _harvest();

        uint256 assets = strategy.previewWithdraw(_underlying);
        uint256 shares = _convertToShares(assets, true);

        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        _burn(owner, shares);

        if (config.hasCooldown) {
            IERC20(address(strategy)).safeTransfer(receiver, assets);
        } else {
            strategy.withdraw(_underlying, receiver, address(this));
        }

        totalUnderlyingStamp = strategy.convertToAssets(totalAssets());
        lastStamp = block.timestamp;

        emit Withdraw(msg.sender, receiver, owner, _underlying, shares);

        return shares;
    }

    /**
     * @dev Returns the maximum amount of shares that can be redeemed by the owner
     * @param owner Address that owns the shares
     * @return Maximum amount of shares that can be redeemed
     */
    function maxRedeem(address owner) public view override returns (uint256) {
        return balanceOf(owner);
    }

    /**
     * @dev Simulates the effects of a redemption at the current block, returning the expected amount of underlying
     * tokens
     * @param shares Amount of shares to redeem
     * @return Expected amount of underlying tokens to be received
     */
    function previewRedeem(uint256 shares) public view override returns (uint256) {
        uint256 newTotalShares = previewHarvest();
        if (newTotalShares == 0) {
            return 0;
        }

        uint256 strategyShares = shares * (totalAssets() + VIRTUAL_ASSETS) / (newTotalShares + VIRTUAL_SHARES);
        return strategy.convertToAssets(strategyShares);
    }

    /**
     * @dev Redeems the specified amount of shares for underlying tokens
     * @param shares Amount of shares to redeem
     * @param receiver Address that will receive the underlying tokens
     * @param owner Address that owns the shares to be burned
     * @return Amount of underlying tokens received
     */
    function redeem(uint256 shares, address receiver, address owner) public override nonReentrant returns (uint256) {
        uint256 maxShares = maxRedeem(owner);
        require(shares > 0, "Cannot redeem 0");
        require(shares <= maxShares, "Exceeded max redeem");

        _harvest();

        uint256 assets = _convertToAssets(shares, false);
        uint256 _underlying = strategy.convertToAssets(assets);

        if (msg.sender != owner) {
            _spendAllowance(owner, msg.sender, shares);
        }

        _burn(owner, shares);

        if (config.hasCooldown) {
            IERC20(address(strategy)).safeTransfer(receiver, assets);
        } else {
            strategy.redeem(assets, receiver, address(this));
        }

        totalUnderlyingStamp = strategy.convertToAssets(totalAssets());
        lastStamp = block.timestamp;

        emit Withdraw(msg.sender, receiver, owner, _underlying, shares);
        return _underlying;
    }

    /**
     * @dev Previews the effect of a harvest operation without executing it
     * @return Expected total supply after harvest fees are applied
     */
    function previewHarvest() internal view returns (uint256) {
        uint256 fee = computeHarvestFee();
        uint256 newShares = (totalSupply() + VIRTUAL_SHARES) * fee / (MAX_BPS - fee);
        return totalSupply() + newShares;
    }

    /**
     * @dev Internal harvest implementation that mints fee shares to the fee recipient
     * @return Amount of new shares minted as fees
     */
    function _harvest() internal returns (uint256) {
        uint256 previousTotalUnderlying = totalUnderlyingStamp;
        uint256 fee = computeHarvestFee();
        uint256 newShares = (totalSupply() + VIRTUAL_SHARES) * fee / (MAX_BPS - fee);
        if (newShares != 0) {
            _mint(config.feeRecipient, newShares);
        }
        totalUnderlyingStamp = strategy.convertToAssets(totalAssets());
        lastStamp = block.timestamp;
        emit Harvested(msg.sender, newShares, previousTotalUnderlying, totalUnderlyingStamp);
        return newShares;
    }

    /**
     * @dev Executes a harvest operation, minting fee shares to the fee recipient
     * @return Amount of new shares minted as fees
     */
    function harvest() public onlyRole(HARVESTER_ROLE) returns (uint256) {
        return _harvest();
    }

    /**
     * @dev Calculates the harvest fee based on management and performance parameters
     * @return Fee amount in basis points
     */
    function computeHarvestFee() internal view returns (uint256) {
        uint256 timeElapsed = block.timestamp - lastStamp;

        uint256 mgmtFeeNum = config.managementFee * timeElapsed;
        uint256 perfFeeNum = config.performanceFee * SECONDS_IN_YEAR;

        if (timeElapsed == 0) {
            return 0;
        }

        uint256 currentTotalUnderlying = strategy.convertToAssets(totalAssets());

        if (currentTotalUnderlying == 0) {
            return 0;
        }

        uint256 gain = 0;
        if (currentTotalUnderlying > totalUnderlyingStamp) {
            gain = currentTotalUnderlying - totalUnderlyingStamp;
        }

        uint256 fee =
            (totalUnderlyingStamp * mgmtFeeNum + gain * perfFeeNum) / (currentTotalUnderlying * SECONDS_IN_YEAR);

        return fee;
    }

    /**
     * @dev Internal function to spend allowance
     * @param owner Token owner
     * @param spender Token spender
     * @param amount Amount to spend
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual override {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Adds a contract and function selector to the whitelist
     * @param target The target contract address
     * @param functionSelector The 4-byte function selector
     */
    function addToWhitelist(address target, bytes4 functionSelector) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(target != address(0), "Invalid target address");
        require(functionSelector != bytes4(0), "Invalid function selector");
        require(!whitelistedTargets[target][functionSelector], "Already whitelisted");
        require(target != address(strategy), "Cannot whitelist strategy");
        whitelistedTargets[target][functionSelector] = true;
        emit Whitelisted(target, functionSelector, true);
    }

    /**
     * @dev Removes a contract and function selector from the whitelist
     * @param target The target contract address
     * @param functionSelector The 4-byte function selector
     */
    function removeFromWhitelist(address target, bytes4 functionSelector) external onlyRole(DEFAULT_ADMIN_ROLE) {
        whitelistedTargets[target][functionSelector] = false;
        emit Whitelisted(target, functionSelector, false);
    }

    /// @dev Rewards tokens are sent directly to the fee recipient since they aren't the underlying asset.
    /// @param target The target contract to call (Morpho ETH Bundler V2).
    /// @param rewardsAsset The rewards asset to claim (MORPHO token).
    /// @param payload The payload to pass to the target.
    function claimAdditionalRewards(
        address target,
        address rewardsAsset,
        bytes calldata payload
    )
        external
        nonReentrant
        onlyRole(DEFAULT_ADMIN_ROLE)
    {
        require(target != address(0), "Invalid target");
        require(rewardsAsset != address(0), "Invalid rewards asset");
        require(payload.length >= 4, "Invalid payload");

        bytes4 functionSelector = bytes4(payload[:4]);
        require(whitelistedTargets[target][functionSelector], "Target not whitelisted");

        require(rewardsAsset != address(underlying), "Cannot claim underlying asset");
        require(rewardsAsset != address(strategy), "Cannot claim strategy asset");
        require(rewardsAsset != address(this), "Cannot claim vault asset");

        uint256 rewardsBalanceBefore = IERC20(rewardsAsset).balanceOf(address(this));
        uint256 sharesBefore = totalSupply();
        uint256 assetsBefore = totalAssets();
        (bool success,) = target.call(payload);
        require(success, "Call to target failed");

        uint256 rewardsBalanceAfter = IERC20(rewardsAsset).balanceOf(address(this));
        uint256 rewardsReceived = rewardsBalanceAfter - rewardsBalanceBefore;
        require(sharesBefore == totalSupply(), "Shares changed during claim");
        require(assetsBefore == totalAssets(), "Assets changed during claim");
        if (rewardsReceived > 0) {
            IERC20(rewardsAsset).safeTransfer(config.feeRecipient, rewardsReceived);
        }
        emit RewardsClaimed(target, rewardsAsset, rewardsReceived);
    }

    /**
     * @notice Transfers any non strategy related ERC-20 tokens from the vault to the fee recipient.
     * @dev Uses `safeTransfer` since the vault owns the tokens. No approval is needed.
     * @param tokenAddress The ERC-20 token contract address to sweep.
     */
    function claimMiscellaneousRewards(address tokenAddress) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) {
        require(tokenAddress != address(0), "Invalid target");
        require(tokenAddress != address(underlying), "Cannot claim underlying");
        require(tokenAddress != address(strategy), "Cannot claim from strategy");
        require(tokenAddress != address(this), "Cannot claim from vault");

        IERC20 token = IERC20(tokenAddress);
        uint256 balance = token.balanceOf(address(this));
        require(balance > 0, "No balance to claim");
        token.safeTransfer(config.feeRecipient, balance);
        emit MiscellaneousRewardClaimed(tokenAddress, balance);
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {ERC165Upgradeable} from "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;


    /// @custom:storage-location erc7201:openzeppelin.storage.AccessControl
    struct AccessControlStorage {
        mapping(bytes32 role => RoleData) _roles;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.AccessControl")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;

    function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {
        assembly {
            $.slot := AccessControlStorageLocation
        }
    }

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    function __AccessControl_init() internal onlyInitializing {
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].hasRole[account];
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        return $._roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `callerConfirmation`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address callerConfirmation) public virtual {
        if (callerConfirmation != _msgSender()) {
            revert AccessControlBadConfirmation();
        }

        _revokeRole(role, callerConfirmation);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        AccessControlStorage storage $ = _getAccessControlStorage();
        bytes32 previousAdminRole = getRoleAdmin(role);
        $._roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        if (!hasRole(role, account)) {
            $._roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        AccessControlStorage storage $ = _getAccessControlStorage();
        if (hasRole(role, account)) {
            $._roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC4626.sol)

pragma solidity ^0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ERC20Upgradeable} from "../ERC20Upgradeable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 *
 * This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for
 * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
 * the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this
 * contract and not the "assets" token which is an independent contract.
 *
 * [CAUTION]
 * ====
 * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
 * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
 * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
 * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
 * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
 * verifying the amount received is as expected, using a wrapper that performs these checks such as
 * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
 *
 * Since v4.9, this implementation introduces configurable virtual assets and shares to help developers mitigate that risk.
 * The `_decimalsOffset()` corresponds to an offset in the decimal representation between the underlying asset's decimals
 * and the vault decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which
 * itself determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default
 * offset (0) makes it non-profitable even if an attacker is able to capture value from multiple user deposits, as a result
 * of the value being captured by the virtual shares (out of the attacker's donation) matching the attacker's expected gains.
 * With a larger offset, the attack becomes orders of magnitude more expensive than it is profitable. More details about the
 * underlying math can be found xref:ROOT:erc4626.adoc#inflation-attack[here].
 *
 * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
 * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
 * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
 * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
 * `_convertToShares` and `_convertToAssets` functions.
 *
 * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
 * ====
 */
abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626 {
    using Math for uint256;

    /// @custom:storage-location erc7201:openzeppelin.storage.ERC4626
    struct ERC4626Storage {
        IERC20 _asset;
        uint8 _underlyingDecimals;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC4626")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ERC4626StorageLocation = 0x0773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00;

    function _getERC4626Storage() private pure returns (ERC4626Storage storage $) {
        assembly {
            $.slot := ERC4626StorageLocation
        }
    }

    /**
     * @dev Attempted to deposit more assets than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);

    /**
     * @dev Attempted to mint more shares than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max);

    /**
     * @dev Attempted to withdraw more assets than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);

    /**
     * @dev Attempted to redeem more shares than the max amount for `receiver`.
     */
    error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);

    /**
     * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC-20 or ERC-777).
     */
    function __ERC4626_init(IERC20 asset_) internal onlyInitializing {
        __ERC4626_init_unchained(asset_);
    }

    function __ERC4626_init_unchained(IERC20 asset_) internal onlyInitializing {
        ERC4626Storage storage $ = _getERC4626Storage();
        (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
        $._underlyingDecimals = success ? assetDecimals : 18;
        $._asset = asset_;
    }

    /**
     * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
     */
    function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool ok, uint8 assetDecimals) {
        (bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
            abi.encodeCall(IERC20Metadata.decimals, ())
        );
        if (success && encodedDecimals.length >= 32) {
            uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
            if (returnedDecimals <= type(uint8).max) {
                return (true, uint8(returnedDecimals));
            }
        }
        return (false, 0);
    }

    /**
     * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
     * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
     * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
     *
     * See {IERC20Metadata-decimals}.
     */
    function decimals() public view virtual override(IERC20Metadata, ERC20Upgradeable) returns (uint8) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return $._underlyingDecimals + _decimalsOffset();
    }

    /** @dev See {IERC4626-asset}. */
    function asset() public view virtual returns (address) {
        ERC4626Storage storage $ = _getERC4626Storage();
        return address($._asset);
    }

    /** @dev See {IERC4626-totalAssets}. */
    function totalAssets() public view virtual returns (uint256) {
        return IERC20(asset()).balanceOf(address(this));
    }

    /** @dev See {IERC4626-convertToShares}. */
    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-convertToAssets}. */
    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-maxDeposit}. */
    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxMint}. */
    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /** @dev See {IERC4626-maxWithdraw}. */
    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return _convertToAssets(balanceOf(owner), Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-maxRedeem}. */
    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf(owner);
    }

    /** @dev See {IERC4626-previewDeposit}. */
    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-previewMint}. */
    function previewMint(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Ceil);
    }

    /** @dev See {IERC4626-previewWithdraw}. */
    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        return _convertToShares(assets, Math.Rounding.Ceil);
    }

    /** @dev See {IERC4626-previewRedeem}. */
    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return _convertToAssets(shares, Math.Rounding.Floor);
    }

    /** @dev See {IERC4626-deposit}. */
    function deposit(uint256 assets, address receiver) public virtual returns (uint256) {
        uint256 maxAssets = maxDeposit(receiver);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
        }

        uint256 shares = previewDeposit(assets);
        _deposit(_msgSender(), receiver, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-mint}. */
    function mint(uint256 shares, address receiver) public virtual returns (uint256) {
        uint256 maxShares = maxMint(receiver);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxMint(receiver, shares, maxShares);
        }

        uint256 assets = previewMint(shares);
        _deposit(_msgSender(), receiver, assets, shares);

        return assets;
    }

    /** @dev See {IERC4626-withdraw}. */
    function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) {
        uint256 maxAssets = maxWithdraw(owner);
        if (assets > maxAssets) {
            revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
        }

        uint256 shares = previewWithdraw(assets);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return shares;
    }

    /** @dev See {IERC4626-redeem}. */
    function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) {
        uint256 maxShares = maxRedeem(owner);
        if (shares > maxShares) {
            revert ERC4626ExceededMaxRedeem(owner, shares, maxShares);
        }

        uint256 assets = previewRedeem(shares);
        _withdraw(_msgSender(), receiver, owner, assets, shares);

        return assets;
    }

    /**
     * @dev Internal conversion function (from assets to shares) with support for rounding direction.
     */
    function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) {
        return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
    }

    /**
     * @dev Internal conversion function (from shares to assets) with support for rounding direction.
     */
    function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
        return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
    }

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

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

    /**
     * @dev Withdraw/redeem common workflow.
     */
    function _withdraw(
        address caller,
        address receiver,
        address owner,
        uint256 assets,
        uint256 shares
    ) internal virtual {
        if (caller != owner) {
            _spendAllowance(owner, caller, shares);
        }

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

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

    function _decimalsOffset() internal view virtual returns (uint8) {
        return 0;
    }
}
"
    },
    "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);
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    struct ReentrancyGuardStorage {
        uint256 _status;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
        assembly {
            $.slot := ReentrancyGuardStorageLocation
        }
    }

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if ($._status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        $._status = ENTERED;
    }

    function _nonReentrantAfter() private {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        return $._status == ENTERED;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4626.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
 * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
 */
interface IERC4626 is IERC20, IERC20Metadata {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardle

Tags:
ERC20, ERC165, Multisig, Mintable, Yield, Upgradeable, Multi-Signature, Factory|addr:0x8fc7fded1f0b1782cafd0be8cad530117d08e3a7|verified:true|block:23378266|tx:0x5bfce275b8b060c43e120a8090ec4b916e5a22a09dd2d6f0c4e0dabfed2e29e4|first_check:1758101917

Submitted on: 2025-09-17 11:38:39

Comments

Log in to comment.

No comments yet.