OracleVault

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/vaults/multisig/phase1/OracleVault.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25

pragma solidity ^0.8.25;

import {AsyncVault, InitializeParams} from "./AsyncVault.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {IPriceOracle} from "src/interfaces/IPriceOracle.sol";

struct ChangeProposal {
    address addr;
    uint256 timestamp;
}

/**
 * @title   OracleVault
 * @author  RedVeil
 * @notice  ERC-7540 (https://eips.ethereum.org/EIPS/eip-7540) compliant async redeem vault using a PushOracle for pricing and a Safe for managing assets
 * @dev     Oracle and safe security is handled in other contracts. We simply assume they are secure and don't implement any further checks in this contract
 */
contract OracleVault is AsyncVault {
    address public safe;

    /**
     * @notice Constructor for the OracleVault
     * @param params The parameters to initialize the vault with
     * @param oracle_ The oracle to use for pricing
     * @param safe_ The safe which will manage the assets
     */
    constructor(
        InitializeParams memory params,
        address oracle_,
        address safe_
    ) AsyncVault(params) {
        if (safe_ == address(0) || oracle_ == address(0))
            revert Misconfigured();

        safe = safe_;
        oracle = IPriceOracle(oracle_);
    }

    /*//////////////////////////////////////////////////////////////
                            ACCOUNTING LOGIC
    //////////////////////////////////////////////////////////////*/

    IPriceOracle public oracle;

    /// @notice Total amount of underlying `asset` token managed by the safe.
    function totalAssets() public view override returns (uint256) {
        return oracle.getQuote(totalSupply, share, address(asset));
    }

    /*//////////////////////////////////////////////////////////////
                            ERC-4626 OVERRIDES
    //////////////////////////////////////////////////////////////*/

    /// @dev Internal function to handle the deposit and mint
    function afterDeposit(uint256 assets, uint256) internal override {
        // Deposit and mint already have the `whenNotPaused` modifier so we don't need to check it here
        _takeFees();

        // Transfer assets to the safe
        SafeTransferLib.safeTransfer(asset, safe, assets);
    }

    /*//////////////////////////////////////////////////////////////
                    BaseControlledAsyncRedeem OVERRIDES
    //////////////////////////////////////////////////////////////*/

    /// @dev Internal function to transfer assets from the safe to the vault before fulfilling a redeem
    function beforeFulfillRedeem(uint256 assets, uint256) internal override {
        SafeTransferLib.safeTransferFrom(asset, safe, address(this), assets);
    }

    /*//////////////////////////////////////////////////////////////
                        SWITCH SAFE LOGIC
    //////////////////////////////////////////////////////////////*/

    ChangeProposal public proposedSafe;

    event SafeProposed(address indexed proposedSafe);
    event SafeChanged(address indexed oldSafe, address indexed newSafe);

    /**
     * @notice Proposes a new safe that can be accepted by the owner after a delay
     * @param newSafe The new safe to propose
     * @dev !!! This is a dangerous operation and should be used with extreme caution !!!
     */
    function proposeSafe(address newSafe) external onlyOwner {
        require(newSafe != address(0), "SafeVault/invalid-safe");

        proposedSafe = ChangeProposal({
            addr: newSafe,
            timestamp: block.timestamp
        });

        emit SafeProposed(newSafe);
    }

    /**
     * @notice Accepts the proposed safe
     * @dev !!! This is a dangerous operation and should be used with extreme caution !!!
     * @dev This will pause the vault to ensure the oracle is set up correctly and no one sends deposits with faulty prices
     * @dev Its important to ensure that the oracle will be switched before unpausing the vault again
     */
    function acceptSafe() external onlyOwner {
        ChangeProposal memory proposal = proposedSafe;

        require(proposal.addr != address(0), "SafeVault/no-safe-proposed");
        require(
            proposal.timestamp + 3 days <= block.timestamp,
            "SafeVault/safe-not-yet-acceptable"
        );
        require(block.timestamp <= proposal.timestamp + 14 days, "SafeVault/safe-expired");

        emit SafeChanged(safe, proposal.addr);

        safe = proposal.addr;

        delete proposedSafe;

        // Pause to ensure that no deposits get through with faulty prices
        _pause();
    }

    /*//////////////////////////////////////////////////////////////
                        SWITCH ORACLE LOGIC
    //////////////////////////////////////////////////////////////*/

    ChangeProposal public proposedOracle;

    event OracleProposed(address indexed proposedOracle);
    event OracleChanged(address indexed oldOracle, address indexed newOracle);

    /**
     * @notice Proposes a new oracle that can be accepted by the owner after a delay
     * @param newOracle The new oracle to propose
     * @dev !!! This is a dangerous operation and should be used with extreme caution !!!
     */
    function proposeOracle(address newOracle) external onlyOwner {
        require(newOracle != address(0), "SafeVault/invalid-oracle");

        proposedOracle = ChangeProposal({
            addr: newOracle,
            timestamp: block.timestamp
        });

        emit OracleProposed(newOracle);
    }

    /**
     * @notice Accepts the proposed oracle
     * @dev !!! This is a dangerous operation and should be used with extreme caution !!!
     * @dev This will pause the vault to ensure the oracle is set up correctly and no one sends deposits with faulty prices
     * @dev Its important to ensure that the oracle will be switched before unpausing the vault again
     */
    function acceptOracle() external onlyOwner {
        ChangeProposal memory proposal = proposedOracle;

        require(proposal.addr != address(0), "SafeVault/no-oracle-proposed");
        require(
            proposal.timestamp + 3 days <= block.timestamp,
            "SafeVault/oracle-not-yet-acceptable"
        );
        require(block.timestamp <= proposal.timestamp + 14 days, "SafeVault/oracle-expired");


        emit OracleChanged(address(oracle), proposal.addr);

        oracle = IPriceOracle(proposal.addr);

        delete proposedOracle;

        // Pause to ensure that no deposits get through with faulty prices
        _pause();
    }

    /*//////////////////////////////////////////////////////////////
                        Bridge LOGIC
    //////////////////////////////////////////////////////////////*/

    bytes32 public constant BRIDGE_ROLE = keccak256("BRIDGE_ROLE");

    function bridgeMint(address to, uint256 shares) external {
        require(hasRole[BRIDGE_ROLE][msg.sender], "BaseERC7540/not-authorized");

        _mint(to, shares);
    }

    function bridgeBurn(address from, uint256 shares) external {
        require(hasRole[BRIDGE_ROLE][msg.sender], "BaseERC7540/not-authorized");

        _burn(from, shares);
    }
}
"
    },
    "src/vaults/multisig/phase1/AsyncVault.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25

pragma solidity ^0.8.25;

import {BaseControlledAsyncRedeem, RequestBalance} from "./BaseControlledAsyncRedeem.sol";
import {BaseERC7540, ERC20} from "./BaseERC7540.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {Owned} from "../../../utils/Owned.sol";

/// @notice Handles the initialize parameters of the vault
struct InitializeParams {
    /// @notice The address of the asset that the vault will manage
    address asset;
    /// @notice The name of the vault
    string name;
    /// @notice The symbol of the vault
    string symbol;
    /// @notice The trusted manager of the vault (handles all sensitive management logic)
    address owner;
    /// @notice The limits of the vault
    Limits limits;
    /// @notice The fees of the vault
    Fees fees;
}

/// @notice Stores the deposit limit and minAmounts of the vault
struct Limits {
    /// @notice Maximum amount of assets that can be deposited into the vault
    uint256 depositLimit;
    /// @notice Minimum amount of shares that can be minted / redeemed from the vault
    uint256 minAmount;
}

/// @notice Stores all fee related variables
struct Fees {
    /// @notice Performance fee rate in 1e18 (100% = 1e18)
    uint64 performanceFee;
    /// @notice Management fee rate in 1e18 (100% = 1e18)
    uint64 managementFee;
    /// @notice Withdrawal incentive fee rate in 1e18 (100% = 1e18)
    uint64 withdrawalIncentive;
    /// @notice Timestamp of the last time the fees were updated (used for management fee calculations)
    uint64 feesUpdatedAt;
    /// @notice High water mark of the vault (used for performance fee calculations)
    uint256 highWaterMark;
    /// @notice Address of the fee recipient
    address feeRecipient;
}

/**
 * @title   AsyncVault
 * @author  RedVeil
 * @notice  Abstract contract containing reusable logic that are the basis of ERC-7540 compliant async redeem vauls
 * @notice  Besides the basic logic for ERC-7540 this contract contains most other logic to manage a modern DeFi vault
 * @dev     Logic to account and manage assets must be implemented by inheriting contracts
 */
abstract contract AsyncVault is BaseControlledAsyncRedeem {
    using FixedPointMathLib for uint256;

    error ZeroAmount();
    error Misconfigured();

    /**
     * @notice Constructor for AsyncVault
     * @param params The initialization parameters
     */
    constructor(
        InitializeParams memory params
    ) BaseERC7540(params.owner, params.asset, params.name, params.symbol) {
        _setLimits(params.limits);
        _setFees(params.fees);
    }

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT/WITHDRAWAL LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Deposit assets into the vault
     * @param assets The amount of assets to deposit
     * @return shares The amount of shares required
     */
    function deposit(uint256 assets) external returns (uint256) {
        return deposit(assets, msg.sender);
    }

    /**
     * @notice Mint shares into the vault
     * @param shares The amount of shares to mint
     * @return assets The amount of assets received
     */
    function mint(uint256 shares) external returns (uint256) {
        return mint(shares, msg.sender);
    }

    /**
     * @notice Withdraw assets from the vault
     * @param assets The amount of assets to withdraw
     * @return shares The amount of shares required
     */
    function withdraw(uint256 assets) external returns (uint256) {
        return withdraw(assets, msg.sender, msg.sender);
    }

    /**
     * @notice Redeem shares from the vault
     * @param shares The amount of shares to redeem
     * @return assets The amount of assets received
     */
    function redeem(uint256 shares) external returns (uint256) {
        return redeem(shares, msg.sender, msg.sender);
    }

    /*//////////////////////////////////////////////////////////////
                            ACCOUNTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Simulates a deposit into the vault and returns the amount of shares that would be received by the user
     * @param assets The amount of assets to deposit
     * @return shares The amount of shares that would be received by the user
     * @dev This function will return 0 if the vault is paused or if the deposit doesnt meet the limits
     */
    function previewDeposit(
        uint256 assets
    ) public view override returns (uint256) {
        Limits memory limits_ = limits;
        uint256 shares = convertToShares(assets);

        if (
            paused ||
            totalAssets() + assets > limits_.depositLimit ||
            shares < limits_.minAmount
        ) return 0;

        return super.previewDeposit(assets);
    }

    /**
     * @notice Simulates a mint into the vault and returns the amount of assets required to mint the given amount of shares
     * @param shares The amount of shares to mint
     * @return assets The amount of assets required to mint the given amount of shares
     * @dev This function will return 0 if the vault is paused or if the mint doesnt meet the limits
     */
    function previewMint(
        uint256 shares
    ) public view override returns (uint256) {
        Limits memory limits_ = limits;
        uint256 assets = convertToAssets(shares);

        if (
            paused ||
            totalAssets() + assets > limits_.depositLimit ||
            shares < limits_.minAmount
        ) return 0;

        return super.previewMint(shares);
    }

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Returns the maximum amount of assets that can be deposited into the vault
     * @return assetsThe maxDeposit of the controller
     * @dev Will return 0 if the vault is paused or if the deposit limit is reached
     */
    function maxDeposit(address) public view override returns (uint256) {
        uint256 assets = totalAssets();
        uint256 depositLimit_ = limits.depositLimit;

        if (paused) return 0;
        if (depositLimit_ == type(uint256).max) return depositLimit_;

        return assets >= depositLimit_ ? 0 : depositLimit_ - assets;
    }
    /**
     * @notice Returns the maximum amount of shares that can be minted into the vault
     * @return shares The maxMint of the controller
     * @dev Will return 0 if the vault is paused or if the deposit limit is reached
     * @dev Overflows if depositLimit is close to maxUint (convertToShares multiplies depositLimit with totalSupply)
     */
    function maxMint(address) public view override returns (uint256) {
        uint256 assets = totalAssets();
        uint256 depositLimit_ = limits.depositLimit;

        if (paused) return 0;
        if (depositLimit_ == type(uint256).max) return depositLimit_;

        return
            assets >= depositLimit_
                ? 0
                : convertToShares(depositLimit_ - assets);
    }

    /*//////////////////////////////////////////////////////////////
                        REQUEST REDEEM LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Requests a redeem for the caller
     * @param shares The amount of shares to redeem
     * @return requestId The requestId of the redeem request
     */
    function requestRedeem(uint256 shares) external returns (uint256) {
        return requestRedeem(shares, msg.sender, msg.sender);
    }

    /**
     * @notice Requests a redeem of shares from the vault
     * @param shares The amount of shares to redeem
     * @param controller The user that will be receiving pending shares
     * @param owner The owner of the shares to redeem
     * @return requestId The requestId of the redeem request
     * @dev This redeem request is added to any pending redeem request of the controller
     * @dev This function will revert if the shares are less than the minAmount
     */
    function requestRedeem(
        uint256 shares,
        address controller,
        address owner
    ) public override returns (uint256 requestId) {
        require(shares >= limits.minAmount, "ERC7540Vault/min-amount");

        // Take fees
        if (!paused) _takeFees();

        // Calculate the withdrawal incentive fee from the assets
        Fees memory fees_ = fees;
        uint256 feeShares = shares.mulDivDown(
            uint256(fees_.withdrawalIncentive),
            1e18
        );

        // Send the withdrawal incentive fee to the fee recipient
        handleWithdrawalIncentive(feeShares, owner, fees_.feeRecipient);

        // process request
        return _requestRedeem(shares - feeShares, controller, owner);
    }

    /*//////////////////////////////////////////////////////////////
                        FULFILL REDEEM LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Fulfills multiple redeem requests of the controller to allow the controller to withdraw their assets
     * @param shares The amount of shares to redeem
     * @param controllers The controllers to redeem for
     * @return total The total amount of assets received
     * @dev This function will revert if the shares and controllers arrays are not the same length
     * @dev This function will also take the withdrawal incentive fee from the assets to incentivse the manager to fulfill the requests
     */
    function fulfillMultipleRedeems(
        uint256[] memory shares,
        address[] memory controllers
    ) external onlyOwner returns (uint256 total) {
        if (shares.length != controllers.length) revert Misconfigured();

        uint256 totalShares;
        for (uint256 i; i < shares.length; i++) {
            uint256 shares = shares[i];

            if (shares == 0) revert("ZERO_SHARES");

            RequestBalance memory currentBalance = requestBalances[
                controllers[i]
            ];

            // calculate amount of fulfilled assets
            uint256 assets = shares.mulDivDown(
                currentBalance.pendingAssets,
                currentBalance.pendingShares
            );

            // Fulfill the redeem request
            _fulfillRedeem(assets, shares, controllers[i]);

            // Add to the total fees
            total += assets;
            totalShares += shares;
        }

        // Burn controller's shares
        _burn(address(this), totalShares);

        return total;
    }

    /**
     * @notice Handles the withdrawal incentive fee by sending it to the fee recipient
     * @param feeShares The amount of shares to send as fees
     * @param owner The user that wants to withdraw
     * @param feeRecipient The address to send the fee to
     * @dev This function is expected to be overriden in inheriting contracts
     */
    function handleWithdrawalIncentive(
        uint256 feeShares,
        address owner,
        address feeRecipient
    ) internal virtual {
        if (feeShares > 0) {
            // Transfer feeShares from owner to feeRecipient
            SafeTransferLib.safeTransferFrom(
                this,
                owner,
                feeRecipient,
                feeShares
            );
        }
    }

    /*//////////////////////////////////////////////////////////////
                            ERC-4626 OVERRIDES
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Takes fees before a withdraw (if the contract is not paused)
     * @dev This function is expected to be overriden in inheriting contracts
     */
    function beforeWithdraw(uint256 assets, uint256) internal virtual override {
        if (!paused) _takeFees();
    }

    /**
     * @notice Takes fees before a deposit
     * @dev This function is expected to be overriden in inheriting contracts
     */
    function beforeDeposit(uint256 assets, uint256) internal virtual override {
        // deposit and mint already have the `whenNotPaused` modifier so we don't need to check it here
        _takeFees();
    }

    /*//////////////////////////////////////////////////////////////
                            FEE LOGIC
    //////////////////////////////////////////////////////////////*/

    Fees public fees;

    event FeesUpdated(Fees prev, Fees next);

    error InvalidFee(uint256 fee);

    /// @notice Returns the fees parameters of the vault
    function getFees() public view returns (Fees memory) {
        return fees;
    }

    /// @notice Returns the accrued fees of the vault
    function accruedFees() public view returns (uint256) {
        Fees memory fees_ = fees;

        return _accruedFees(fees_);
    }

    /// @dev Internal function to calculate the accrued fees
    function _accruedFees(Fees memory fees_) internal view returns (uint256) {
        return _accruedPerformanceFee(fees_) + _accruedManagementFee(fees_);
    }

    /**
     * @notice Performance fee that has accrued since last fee harvest.
     * @return accruedPerformanceFee In underlying `asset` token.
     * @dev Performance fee is based on a high water mark value. If vault share value has increased above the
     *   HWM in a fee period, issue fee shares to the vault equal to the performance fee.
     */
    function _accruedPerformanceFee(
        Fees memory fees_
    ) internal view returns (uint256) {
        uint256 shareValue = convertToAssets(10 ** decimals);
        uint256 performanceFee = uint256(fees_.performanceFee);

        return
            performanceFee > 0 && shareValue > fees_.highWaterMark
                ? performanceFee.mulDivUp(
                    (shareValue - fees_.highWaterMark) * totalSupply,
                    (10 ** (18 + decimals))
                )
                : 0;
    }

    /**
     * @notice Management fee that has accrued since last fee harvest.
     * @return accruedManagementFee In underlying `asset` token.
     * @dev Management fee is annualized per minute, based on 525,600 minutes per year. Total assets are calculated using
     *  the average of their current value and the value at the previous fee harvest checkpoint. This method is similar to
     *  calculating a definite integral using the trapezoid rule.
     */
    function _accruedManagementFee(
        Fees memory fees_
    ) internal view returns (uint256) {
        uint256 managementFee = uint256(fees_.managementFee);

        return
            managementFee > 0
                ? managementFee.mulDivDown(
                    totalAssets() * (block.timestamp - fees_.feesUpdatedAt),
                    31536000 // seconds per year
                ) / 1e18
                : 0;
    }

    /**
     * @notice Sets the fees of the vault
     * @param fees_ The fees to set
     * @dev This function will revert if the fees are greater than 20% performanceFee, 5% managementFee, or 5% withdrawalIncentive
     * @dev This function will also take the fees before setting them to ensure the new fees rates arent applied to any pending fees
     */
    function setFees(Fees memory fees_) public onlyOwner whenNotPaused {
        _takeFees();

        _setFees(fees_);
    }

    /// @dev Internal function to set the fees
    function _setFees(Fees memory fees_) internal {
        // Dont take more than 20% performanceFee, 5% managementFee, 5% withdrawalIncentive
        if (
            fees_.performanceFee > 2e17 ||
            fees_.managementFee > 5e16 ||
            fees_.withdrawalIncentive > 5e16
        ) revert Misconfigured();
        if (fees_.feeRecipient == address(0)) revert Misconfigured();

        // Dont rely on user input here
        fees_.feesUpdatedAt = uint64(block.timestamp);

        // initialise or copy current HWM
        if (fees.highWaterMark == 0) {
            fees_.highWaterMark = convertToAssets(10 ** decimals); // from constructor
        } else {
            fees_.highWaterMark = fees.highWaterMark; // from setFees
        }

        emit FeesUpdated(fees, fees_);

        fees = fees_;
    }

    /**
     * @notice Mints fees as shares of the vault to the fee recipient
     * @dev It will also update the all other fee related variables
     */
    function takeFees() external whenNotPaused {
        _takeFees();
    }

    /// @dev Internal function to take the fees
    function _takeFees() internal {
        Fees memory fees_ = fees;
        uint256 perfFee = _accruedPerformanceFee(fees_);
        uint256 mgmtFee = _accruedManagementFee(fees_);
        uint256 shareValue = convertToAssets(10 ** decimals);

        // Mint fees to the fee recipient
        if (perfFee + mgmtFee > 0)
            _mint(fees_.feeRecipient, convertToShares(perfFee + mgmtFee));

        // Update the high water mark (used by performance fee)
        if (shareValue > fees_.highWaterMark) fees.highWaterMark = shareValue;

        // Update the fees updated at timestamp (used by management fee)
        if (mgmtFee > 0) fees.feesUpdatedAt = uint64(block.timestamp);
    }

    /*//////////////////////////////////////////////////////////////
                          LIMIT LOGIC
    //////////////////////////////////////////////////////////////*/

    Limits public limits;

    event LimitsUpdated(Limits prev, Limits next);

    /**
     * @notice Sets the deposit limit and minAmounts of the vault to limit user exposure to strategy risks
     * @param limits_ The limits to set
     */
    function setLimits(Limits memory limits_) external onlyOwner {
        _setLimits(limits_);
    }

    /// @dev Internal function to set the limits
    function _setLimits(Limits memory limits_) internal {
        // cache
        uint256 totalSupply_ = totalSupply;
        if (totalSupply_ > 0 && limits_.depositLimit < totalAssets())
            revert Misconfigured();
        if (limits_.minAmount > (10 ** decimals)) revert Misconfigured();

        emit LimitsUpdated(limits, limits_);

        limits = limits_;
    }
}
"
    },
    "lib/solmate/src/utils/SafeTransferLib.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}
"
    },
    "lib/solmate/src/utils/FixedPointMathLib.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant MAX_UINT256 = 2**256 - 1;

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // Divide x * y by the denominator.
            z := div(mul(x, y), denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // If x * y modulo the denominator is strictly greater than 0,
            // 1 is added to round up the division of x * y by the denominator.
            z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let y := x // We start y at x, which will help us make our initial estimate.

            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // We check y >= 2^(k + 8) but shift right by k bits
            // each branch to ensure that if x >= 256, then y >= 256.
            if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                y := shr(128, y)
                z := shl(64, z)
            }
            if iszero(lt(y, 0x1000000000000000000)) {
                y := shr(64, y)
                z := shl(32, z)
            }
            if iszero(lt(y, 0x10000000000)) {
                y := shr(32, y)
                z := shl(16, z)
            }
            if iszero(lt(y, 0x1000000)) {
                y := shr(16, y)
                z := shl(8, z)
            }

            // Goal was to get z*z*y within a small factor of x. More iterations could
            // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
            // We ensured y >= 256 so that the relative difference between y and y+1 is small.
            // That's not possible if x < 256 but we can just verify those cases exhaustively.

            // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
            // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
            // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.

            // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
            // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.

            // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
            // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

            // There is no overflow risk here since y < 2^136 after the first branch above.
            z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If x+1 is a perfect square, the Babylonian method cycles between
            // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Mod x by y. Note this will return
            // 0 instead of reverting if y is zero.
            z := mod(x, y)
        }
    }

    function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // Divide x by y. Note this will return
            // 0 instead of reverting if y is zero.
            r := div(x, y)
        }
    }

    function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Add 1 to x * y if x % y > 0. Note this will
            // return 0 instead of reverting if y is zero.
            z := add(gt(mod(x, y), 0), div(x, y))
        }
    }
}
"
    },
    "src/interfaces/IPriceOracle.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;

/// @title IPriceOracle
/// @custom:security-contact security@euler.xyz
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice Common PriceOracle interface.
interface IPriceOracle {
    /// @notice Get the name of the oracle.
    /// @return The name of the oracle.
    function name() external view returns (string memory);

    /// @notice One-sided price: How much quote token you would get for inAmount of base token, assuming no price spread.
    /// @param inAmount The amount of `base` to convert.
    /// @param base The token that is being priced.
    /// @param quote The token that is the unit of account.
    /// @return outAmount The amount of `quote` that is equivalent to `inAmount` of `base`.
    function getQuote(
        uint256 inAmount,
        address base,
        address quote
    ) external view returns (uint256 outAmount);

    /// @notice Two-sided price: How much quote token you would get/spend for selling/buying inAmount of base token.
    /// @param inAmount The amount of `base` to convert.
    /// @param base The token that is being priced.
    /// @param quote The token that is the unit of account.
    /// @return bidOutAmount The amount of `quote` you would get for selling `inAmount` of `base`.
    /// @return askOutAmount The amount of `quote` you would spend for buying `inAmount` of `base`.
    function getQuotes(
        uint256 inAmount,
        address base,
        address quote
    ) external view returns (uint256 bidOutAmount, uint256 askOutAmount);
}
"
    },
    "src/vaults/multisig/phase1/BaseControlledAsyncRedeem.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.25

pragma solidity ^0.8.25;

import {BaseERC7540} from "./BaseERC7540.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {IERC7540Redeem} from "ERC-7540/interfaces/IERC7540.sol";

/// @notice Stores the requestBalance of a controller
struct RequestBalance {
    /// @notice The amount of shares that have been requested to be redeemed
    uint256 pendingShares;
    /// @notice The amount of shares that have been requested to be redeemed
    uint256 pendingAssets;
    /// @notice The timestamp of the last redeem request (will be used to ensure timely fulfillment of redeem requests)
    uint256 requestTime;
    /// @notice The amount of shares that have been freed up by a fulfilled redeem request
    uint256 claimableShares;
    /// @notice The amount of assets that have been freed up by a fulfilled redeem request
    uint256 claimableAssets;
}

/**
 * @title   BaseControlledAsyncRedeem
 * @author  RedVeil
 * @notice  Abstract contract containing reusable logic for controlled async redeem flows
 * @dev     Based on https://github.com/ERC4626-Alliance/ERC-7540-Reference/blob/main/src/BaseControlledAsyncRedeem.sol
 */
abstract contract BaseControlledAsyncRedeem is BaseERC7540, IERC7540Redeem {
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                        ERC4626 OVERRIDDEN LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Deposit assets into the vault
     * @param assets The amount of assets to deposit
     * @param receiver The address to receive the shares
     * @return shares The amount of shares received
     * @dev This function is synchronous and will revert if the vault is paused
     * @dev It will first use claimable balances of previous redeem requests before transferring assets from the sender
     */
    function deposit(
        uint256 assets,
        address receiver
    ) public override whenNotPaused returns (uint256 shares) {
        // Additional logic for inheriting contracts
        beforeDeposit(assets, shares);

        // Check for rounding error since we round down in previewDeposit.
        require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");

        // Need to transfer before minting or ERC777s could reenter.
        SafeTransferLib.safeTransferFrom(
            asset,
            msg.sender,
            address(this),
            assets
        );

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        // Additional logic for inheriting contracts
        afterDeposit(assets, shares);
    }

    /**
     * @notice Mints shares from the vault
     * @param shares The amount of shares to mint
     * @param receiver The address to receive the shares
     * @return assets The amount of assets deposited
     * @dev This function is synchronous and will revert if the vault is paused
     * @dev It will first use claimable balances of previous redeem requests before minting shares
     */
    function mint(
        uint256 shares,
        address receiver
    ) public override whenNotPaused returns (uint256 assets) {
        // Additional logic for inheriting contracts
        beforeDeposit(assets, shares);

        require(shares != 0, "ZERO_SHARES");

        assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.

        // Need to transfer before minting or ERC777s could reenter.
        SafeTransferLib.safeTransferFrom(
            asset,
            msg.sender,
            address(this),
            assets
        );

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        // Additional logic for inheriting contracts
        afterDeposit(assets, shares);
    }

    /// @dev Additional logic for inheriting contracts before depositing
    function beforeDeposit(uint256 assets, uint256 shares) internal virtual {}

    /**
     * @notice Withdraws assets from the vault which have beenpreviously freed up by a fulfilled redeem request
     * @param assets The amount of assets to withdraw
     * @param receiver The address to receive the assets
     * @param controller The controller to withdraw from
     * @return shares The amount of shares burned
     * @dev This function is asynchronous and will not revert if the vault is paused
     * @dev msg.sender must be the controller or an operator for the controller
     * @dev Requires sufficient claimableAssets in the controller's requestBalance
     */
    function withdraw(
        uint256 assets,
        address receiver,
        address controller
    ) public virtual override returns (uint256 shares) {
        require(
            controller == msg.sender || isOperator[controller][msg.sender],
            "ERC7540Vault/invalid-caller"
        );
        require(assets != 0, "ZERO_ASSETS");

        RequestBalance storage currentBalance = requestBalances[controller];
        shares = assets.mulDivUp(
            currentBalance.claimableShares,
            currentBalance.claimableAssets
        );

        // Modify the currentBalance state accordingly
        _withdrawClaimableBalance(assets, currentBalance);

        // Additional logic for inheriting contracts
        beforeWithdraw(assets, shares);

        // Transfer assets to the receiver
        SafeTransferLib.safeTransfer(asset, receiver, assets);

        emit Withdraw(msg.sender, receiver, controller, assets, shares);

        // Additional logic for inheriting contracts
        afterWithdraw(assets, shares);
    }

    /**
     * @notice Modifies the currentBalance state to reflect a withdrawal of claimableAssets
     * @param assets The amount of assets to withdraw
     * @param currentBalance The requestBalance of the controller
     * @dev Claiming partially introduces precision loss. The user therefore receives a rounded down amount,
     * while the claimable balance is reduced by a rounded up amount.
     */
    function _withdrawClaimableBalance(
        uint256 assets,
        RequestBalance storage currentBalance
    ) internal {
        uint256 sharesUp = assets.mulDivUp(
            currentBalance.claimableShares,
            currentBalance.claimableAssets
        );

        currentBalance.claimableAssets -= assets;
        currentBalance.claimableShares = currentBalance.claimableShares >
            sharesUp
            ? currentBalance.claimableShares - sharesUp
            : 0;
    }

    /**
     * @notice Redeems shares from the vault which have beenpreviously freed up by a fulfilled redeem request
     * @param shares The amount of shares to redeem
     * @param receiver The address to receive the assets
     * @param controller The controller to redeem from
     * @return assets The amount of assets received
     * @dev This function is asynchronous and will not revert if the vault is paused
     * @dev msg.sender must be the controller or an operator for the controller
     * @dev Requires sufficient claimableShares in the controller's requestBalance
     */
    function redeem(
        uint256 shares,
        address receiver,
        address controller
    ) public virtual override returns (uint256 assets) {
        require(
            controller == msg.sender || isOperator[controller][msg.sender],
            "ERC7540Vault/invalid-caller"
        );
        require(shares != 0, "ZERO_SHARES");

        RequestBalance storage currentBalance = requestBalances[controller];
        assets = shares.mulDivDown(
            currentBalance.claimableAssets,
            currentBalance.claimableShares
        );

        // Modify the currentBalance state accordingly
        _redeemClaimableBalance(shares, currentBalance);

        // Additional logic for inheriting contracts
        beforeWithdraw(assets, shares);

        // Transfer assets to the receiver
        SafeTransferLib.safeTransfer(asset, receiver, assets);

        emit Withdraw(msg.sender, receiver, controller, assets, shares);

        // Additional logic for inheriting contracts
        afterWithdraw(assets, shares);
    }

    /**
     * @notice Modifies the currentBalance state to reflect a withdrawal of claimableAssets
     * @param shares The amount of shares to redeem
     * @param currentBalance The requestBalance of the controller
     * @dev Claiming partially introduces precision loss. The user therefore receives a rounded down amount,
     * while the claimable balance is reduced by a rounded up amount.
     */
    function _redeemClaimableBalance(
        uint256 shares,
        RequestBalance storage currentBalance
    ) internal {
        uint256 assetsUp = shares.mulDivUp(
            currentBalance.claimableAssets,
            currentBalance.claimableShares
        );

        currentBalance.claimableAssets = currentBalance.claimableAssets >
            assetsUp
            ? currentBalance.claimableAssets - assetsUp
            : 0;
        currentBalance.claimableShares -= shares;
    }

    /// @dev Additional logic for inheriting contracts after withdrawing
    function afterWithdraw(uint256 assets, uint256 shares) internal virtual {}

    /*//////////////////////////////////////////////////////////////
                        ACCOUNTNG LOGIC
    //////////////////////////////////////////////////////////////*/
    /// @dev controller => requestBalance
    mapping(address => RequestBalance) public requestBalances;

    /**
     * @notice Returns the requestBalance of a controller
     * @param controller The controller to get the requestBalance of
     * @return requestBalance The requestBalance of the controller
     */
    function getRequestBalance(
        address controller
    ) public view returns (RequestBalance memory) {
        return requestBalances[controller];
    }

    /**
     * @notice Returns the requested shares for redeem that have not yet been fulfilled of a controller
     * @param controller The controller to get the pendingShares of
     * @return pendingShares The pendingShares of the controller
     */
    function pendingRedeemRequest(
        uint256,
        address controller
    ) public view returns (uint256) {
        return requestBalances[controller].pendingShares;
    }

    /**
     * @notice Returns the shares that have been freed up by a fulfilled redeem request of a controller
     * @param controller The controller to get the claimableShares of
     * @return claimableShares The claimableShares of the controller
     */
    function claimableRedeemRequest(
        uint256,
        address controller
    ) public view returns (uint256) {
        return requestBalances[controller].claimableShares;
    }

    /**
     * @notice Simulates a deposit into the vault and returns the amount of shares that would be received by the user
     * @param assets The amount of assets to deposit
     * @return shares The amount of shares that would be received by the user
     * @dev This function will return 0 if the vault is paused
     */
    function previewDeposit(
        uint256 assets
    ) public view virtual override returns (uint256) {
        return paused ? 0 : super.previewDeposit(assets);
    }

    /**
     * @notice Simulates a mint into the vault and returns the amount of assets required to mint the given amount of shares
     * @param shares The amount of shares to mint
     * @return assets The amount of assets required to mint the given amount of shares
     * @dev This function will return 0 if the vault is paused
     */
    function previewMint(
        uint256 shares
    ) public view virtual override returns (uint256) {
        return paused ? 0 : super.previewMint(shares);
    }

    /// @dev Previewing withdraw is not supported for async flows (we would require the controller to be known which we do not have in ERC4626)
    function previewWithdraw(
        uint256
    ) public pure virtual override returns (uint256) {
        revert("ERC7540Vault/async-flow");
    }

    /// @dev Previewing redeem is not supported for async flows (we would require the controller to be known which we do not have in ERC4626)
    function previewRedeem(
        uint256
    ) public pure virtual override returns (uint256 assets) {
        revert("ERC7540Vault/async-flow");
    }

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Returns the maximum amount of assets that can be deposited into the vault
     * @return assets The maxDeposit of the controller
     * @dev Will return 0 if the vault is paused
     */
    function maxDeposit(
        address
    ) public view virtual override returns (uint256) {
        return paused ? 0 : type(uint256).max;
    }

    /**
     * @notice Returns the maximum amount of shares that can be minted into the vault
     * @return shares The maxMint of the controller
     * @dev Will return 0 if the vault is paused
     */
    function maxMint(address) public view virtual override returns (uint256) {
        return paused ? 0 : type(uint256).max;
    }

    /**
     * @notice Returns the maximum amount of assets that can be withdrawn from the vault
     * @param controller The controller to get the maxWithdraw of
     * @return assets The maxWithdraw of the controller
     * @dev This is simply the claimableAssets of the controller (i.e. the assets that have been freed up by a fulfilled redeem request)
     */
    function maxWithdraw(
        address controller
    ) public view virtual override returns (uint256) {
        return requestBalances[controller].claimableAssets;
    }

    /**
     * @notice Returns the maximum amount of shares that can be redeemed from the vault
     * @param controller The controller to get the maxRedeem of
     * @return shares The maxRedeem of the controller
     * @dev This is simply the claimableShares of the controller (i.e. the shares that have been freed up by a fulfilled redeem request)
     */
    function maxRedeem(
        address controller
    ) public view virtual override returns (uint256) {
        return requestBalances[controller].claimableShares;
    }

    /*//////////////////////////////////////////////////////////////
                        REQUEST REDEEM LOGIC
    //////////////////////////////////////////////////////////////*/

    event RedeemRequested(
        address indexed controller,
        address indexed owner,
        uint256 requestId,
        address sender,
        uint256 shares,
        uint256 assets
    );

    /**
     * @notice Requests a redeem of shares from the vault
     * @param shares The amount of shares to redeem
     * @param controller The user that will be receiving pending shares
     * @param owner The owner of the shares to redeem
     * @return requestId The requestId of the redeem request
     * @dev This redeem request is added to any pending redeem request of the controller
     */
    function requestRedeem(
        uint256 shares,
        address controller,
        address owner
    ) external virtual returns (uint256 requestId) {
        return _requestRedeem(shares, controller, owner);
    }

    /// @dev Internal function to request a redeem
    function _requestRedeem(
        uint256 shares,
        address controller,
        address owner
    ) internal returns (uint256 requestId) {
        require(
            owner == msg.sender || isOperator[owner][msg.sender],
            "ERC7540Vault/invalid-owner"
        );
        require(
            ERC20(address(this)).balanceOf(owner) >= shares,
            "ERC7540Vault/insufficient-balance"
        );

        // Update the controller's requestBalance
        uint256 assets = convertToAssets(shares);
        require(assets != 0, "ZERO_ASSETS"); 

         // Transfer shares from owner to vault (these will be burned on withdrawal)
        SafeTransferLib.safeTransferFrom(this, owner, address(this), shares);

        RequestBalance storage currentBalance = requestBalances[controller];
        currentBalance.pendingShares += shares;
        currentBalance.pendingAssets += assets;
        currentBalance.requestTime = block.timestamp;

        emit RedeemRequested(
            controller,
            owner,
            REQUEST_ID,
            msg.sender,
            shares,
            assets
        );
        return REQUEST_ID;
    }

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT FULFILLMENT LOGIC
    //////////////////////////////////////////////////////////////*/

    event RedeemRequestFulfilled(
        address indexed controller,
        address indexed fulfiller,
        uint256 shares,
        uint256 assets
    );

    /**
     * @notice Fulfills a redeem request of the controller to allow the controller to withdraw their assets
     * @param shares The amount of shares to redeem
     * @param controller The controller to redeem for
     * @return assets The amount of assets claimable by the controller
     */
    function fulfillRedeem(
        uint256 shares,
        address controller
    ) external virtual onlyOwner returns (uint256) {
        // Burn controller's shares
        _burn(address(this), shares);

        RequestBalance memory currentBalance = requestBalances[controller];

        if (shares == 0) revert("ZERO_SHARES");

        // calculate amount of fulfilled assets
        uint256 assets = shares.mulDivDown(
            currentBalance.pendingAssets,
            currentBalance.pendingShares
        );

        return _fulfillRedeem(assets, shares, controller);
    }

    /// @dev Internal function to fulfill a redeem request
    function _fulfillRedeem(
        uint256 assets,
        uint256 shares,
        address controller
    ) internal virtual returns (uint256) {
        if (assets == 0 || shares == 0) revert("ZERO_SHARES");

        RequestBalance storage currentBalance = requestBalances[controller];

        // Check that there are pending shares to fulfill
        require(
            currentBalance.pendingShares != 0 &&
                shares <= currentBalance.pendingShares,
            "ZERO_SHARES"
        );

        // Additional logic for inheriting contracts
        beforeFulfillRedeem(assets, shares);

        // Update the controller's requestBalance
        currentBalance.claimableShares += shares;
        currentBalance.claimableAssets += assets;
        currentBalance.pendingShares -= shares;
        currentBalance.pendingAssets -= assets;

        // Reset the requestTime and pendingAssets if there are no more pending shares
        if (currentBalance.pendingShares == 0) {
            currentBalance.requestTime = 0;
            currentBalance.pendingAssets = 0;
        }

        emit RedeemRequestFulfilled(controller, msg.sender, shares, assets);

        // Additional logic for inheriting contracts
        afterFulfillRedeem(assets, shares);

        return assets;
    }

    /// @dev Additional logic for inheriting contracts before fulfilling a redeem request
    function beforeFulfillRedeem(
        uint256 assets,
        uint256 shares
    ) internal virtual {}

    /// @dev Additional logic for inheriting contracts after fulfilling a redeem request
    function afterFulfillRedeem(
        uint256 assets,
        uint256 shares
    ) internal virtual {}

    /*/////////////////////////////////////////

Tags:
ERC165, Multisig, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xdb06a9d79f5ff660f611234c963c255e03cb5554|verified:true|block:23667662|tx:0x6ff0ca8ce83dd041457ec6bc24cf2f6fdca354eddb785f3f6bd98e9726aee1b9|first_check:1761566211

Submitted on: 2025-10-27 12:56:52

Comments

Log in to comment.

No comments yet.