// File: src/WrappedTokenGateway.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {IWrappedToken} from "./interfaces/IWrappedToken.sol";
import {IMaxApyVault} from "./interfaces/IMaxApyVault.sol";

import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

/*KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
KKKKK0OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO0KKKKKKK
KK0dcclllllllllllllllllllllllllllllccccccccccccccccccclx0KKK
KOc,dKNWWWWWWWWWWWWWWWWWWWWWWWWWWWWNNNNNNNNNNNNNNNNNXOl';xKK
Kd'oWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMX; ,kK
Ko'xMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNc .dK
Ko'dMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNc .oK
Kd'oWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNc .oK
KO:,xXWWWWWWWWWWWWWWWWWWWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMNc .oK
KKOl,',;;,,,,,,;;,,,,,,,;;cxXMMMMMMMMMMMMMMMMMMMMMMMMMNc .oK
KKKKOoc;;;;;;;;;;;;;;;;;;;,.cXMMMMMMMMMMMMMMMMMMMMMMMMNc .oK
KKKKKKKKKKKKKKKKKKKK00O00K0:,0MMMMMMMMMMMMMMMMMMMMMMMMNc .oK
KKKKKKKKKKKKKKKKKKklcccccld;,0MMMMMMMMMMMMMMMMMMMMMMMMNc .oK
KKKKKKKKKKKKKKKKkl;ckXNXOc. '0MMMMMMMMMMMMMMMMMMMMMMMMNc .oK
KKKKKKKKKKKKKKkc;l0WMMMMMX; .oKNMMMMMMMMMMMMMMMMMMMMMMNc .oK
KKKKKKKKKKKKkc;l0WMMMMMMMWd.  .,lddddddxONMMMMMMMMMMMMNc .oK
KKKKKKKKKKkc;l0WMMMMMMMMMMWOl::;'.  .....:0WMMMMMMMMMMNc .oK
KKKKKKK0xc;o0WMMMMMMMMMMMMMMMMMWNk'.;xkko'lNMMMMMMMMMMNc .oK
KKKKK0x:;oKWMMMMMMMMMMMMMMMMMMMMMWd..lKKk,lNMMMMMMMMMMNc .oK
KKK0x:;oKWMMMMMMMMMMMMMMMMMMMMMMWO,  c0Kk,lNMMMMMMMMMMNc .oK
KKx:;dKWMMMMMMMMMMMMMMMMMMMMMWN0c.  ;kKKk,lNMMMMMMMMMMNc .oK
Kx,:KWMMMMMMMMMMMMMMMMMMMMMW0c,.  'oOKKKk,lNMMMMMMMMMMNc .oK
Ko'xMMMMMMMMMMMMMMMMMMMMMW0c.   'oOKKKKKk,lNMMMMMMMMMMNc .oK
Ko'xMMMMMMMMMMMMMMMMMMMW0c.  ':oOKKKKKKKk,lNMMMMMMMMMMNc .oK
Ko'xMMMMMMMMMMMMMMMMMW0l.  'oOKKKKKKKKKKk,cNMMMMMMMMMMNc .oK
Ko'xMMMMMMMMMMMMMMMW0l.  'oOKKKKKKKKKKKKk,lNMMMMMMMMMMNc .oK
Ko'dWMMMMMMMMMMMMW0l.  'oOKKKKKKKKKKKKKKk,cNMMMMMMMMMMX: .oK
KO:,xXNWWWWWWWWNOl.  'oOKKKKKKKKKKKKKKKK0c,xNMMMMMMMMNd. .dK
KKOl''',,,,,,,,..  'oOKKKKKKKKKKKKKKKKKKKOl,,ccccccc:'  .c0K
KKKKOoc:;;;;;;;;:ldOKKKKKKKKKKKKKKKKKKKKKKKkl;'......',cx0KK
KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK0OOOOOOO0KKK*/

/// @notice Helper contract to interact with a MaxApy Vault utilizing the chain's native token the protocol
/// has been deployed to
/// @author MaxApy
contract WrappedTokenGateway {
    using SafeTransferLib for address;

    ////////////////////////////////////////////////////////////////
    ///                       CONSTANTS                          ///
    ////////////////////////////////////////////////////////////////

    /// @notice The chain's wrapped native token
    IWrappedToken public immutable wrappedToken;
    /// @notice The MaxApy vault linked to this Gateway contract
    IMaxApyVault public immutable vault;

    ////////////////////////////////////////////////////////////////
    ///                        ERRORS                            ///
    ////////////////////////////////////////////////////////////////
    error InvalidZeroValue();
    error FailedNativeTransfer();
    error ReceiveNotAllowed();

    ////////////////////////////////////////////////////////////////
    ///                        EVENTS                            ///
    ////////////////////////////////////////////////////////////////

    /// @notice Emitted on native vault deposits
    event DepositNative(address indexed recipient, uint256 shares, uint256 amount);

    /// @notice Emitted on native vault withdrawals
    event WithdrawNative(address indexed recipient, uint256 shares, uint256 amount);

    /// @dev `keccak256(bytes("DepositNative(address,uint256,uint256)"))`.
    uint256 internal constant _DEPOSIT_NATIVE_EVENT_SIGNATURE =
        0x6bb902f8baf2580ae3dae24e58f4b874ecca85152076af921bfd172dce1c7e28;

    /// @dev `keccak256(bytes("WithdrawNative(address,uint256,uint256)"))`.
    uint256 internal constant _WITHDRAW_NATIVE_EVENT_SIGNATURE =
        0x5cb35f4e7dbc40dd34f0d58cec4f5548fc47638cb46d7964e4f07c48e97e4c7d;

    ////////////////////////////////////////////////////////////////
    ///                      CONSTRUCTOR                         ///
    ////////////////////////////////////////////////////////////////

    /// @notice Create the WrappedToken Gateway
    /// @param _wrappedToken The wrapped token of the chain the contract will be deployed to
    /// @param _vault The MaxApy vault linked to this Gateway contract
    constructor(IWrappedToken _wrappedToken, IMaxApyVault _vault) {
        wrappedToken = _wrappedToken;
        vault = _vault;
        address(_wrappedToken).safeApprove(address(_vault), type(uint256).max);
    }

    ////////////////////////////////////////////////////////////////
    ///                 GATEWAY CORE LOGIC                       ///
    ////////////////////////////////////////////////////////////////

    /// @notice  Deposits `msg.value` of `_wrappedToken`, issuing shares to `recipient`
    /// @param recipient The address to issue the shares from MaxApy's Vault to
    function depositNative(address recipient) external payable returns (uint256) {
        // Cache `wrappedToken` and `vault` due to assembly's immutable access restrictions
        address cachedWrappedToken = address(wrappedToken);
        address cachedVault = address(vault);

        assembly ("memory-safe") {
            // Check if `msg.value` is 0
            if iszero(callvalue()) {
                // Throw the `InvalidZeroValue()` error
                mstore(0x00, 0xef7a63d0)
                revert(0x1c, 0x04)
            }

            // Cache the free memory pointer
            let m := mload(0x40)

            // Store Wrapped Token's `deposit()` function selector:
            // `bytes4(keccak256("deposit()"))`
            mstore(0x00, 0xd0e30db0)

            // Deposit native token in exchange for wrapped native token
            // Note: using some wrapped tokens' fallback function for deposit allows saving the previous
            // selector loading into memory to call wrappedToken's `deposit()`.
            // This is avoided due to some chain's wrapped native versions not allowing such behaviour
            if iszero(
                call(
                    gas(), // Remaining amount of gas
                    cachedWrappedToken, // Address of `wrappedToken`
                    callvalue(), // `msg.value`
                    0x1c, // byte offset in memory where calldata starts
                    0x24, // size of the calldata to copy
                    0x00, // byte offset in memory to store the return data
                    0x00 // size of the return data
                )
            ) {
                // Throw the `WrappedTokenDepositFailed()` error
                mstore(0x00, 0x22cd2378)
                revert(0x1c, 0x04)
            }

            // Store MaxApy vault's `deposit()` function selector:
            // `bytes4(keccak256("deposit(uint256,address)"))`
            mstore(0x00, 0x6e553f65)
            mstore(0x20, callvalue()) // Append the `amount` argument
            mstore(0x40, recipient) // Append the `recipient` argument

            // Deposit into MaxApy vault
            if iszero(
                call(
                    gas(), // Remaining amount of gas
                    cachedVault, // Address of `vault`
                    0, // `msg.value`
                    0x1c, // byte offset in memory where calldata starts
                    0x44, // size of the calldata to copy
                    0x00, // byte offset in memory to store the return data
                    0x20 // size of the return data
                )
            ) {
                // If call failed, throw the error thrown in the previous `call`
                revert(0x00, 0x04)
            }

            // Emit the `DepositNative` event
            mstore(0x20, callvalue())
            log2(0x00, 0x40, _DEPOSIT_NATIVE_EVENT_SIGNATURE, recipient)

            mstore(0x40, m) // Restore the free memory pointer

            return(0x00, 0x20) // Return `shares` value stored in 0x00 from previous from call's
        }
    }

    /// @notice Withdraws the calling account's tokens from MaxApy's Vault, redeeming
    /// amount `shares` for the corresponding amount of tokens, which will be transferred to
    /// `recipient` in the form of the chain's native token
    /// @param shares How many shares to try and redeem for tokens
    /// @param recipient The address to issue the shares from MaxApy's Vault to
    /// @param maxLoss The maximum acceptable loss to sustain on withdrawal. Up to loss specified amount of shares may be
    /// burnt to cover losses on withdrawal
    function withdrawNative(uint256 shares, address recipient, uint256 maxLoss) external returns (uint256) {
        // Cache `wrappedToken` and `vault` due to assembly's immutable access restrictions
        address cachedWrappedToken = address(wrappedToken);
        address cachedVault = address(vault);

        assembly ("memory-safe") {
            // Check if `shares` passed by user is `type(uint256).max`
            if eq(shares, not(0)) {
                // Store `vault`'s `balanceOf()` function selector:
                // `bytes4(keccak256("balanceOf(address)"))`
                mstore(0x00, 0x70a08231)
                mstore(0x20, caller()) // append the `owner` argument as `msg.sender`

                // query `vault`'s `msg.sender` `balanceOf()`
                if iszero(
                    staticcall(
                        gas(), // Remaining amount of gas
                        cachedVault, // Address of `vault`
                        0x1c, // byte offset in memory where calldata starts
                        0x24, // size of the calldata to copy
                        0x00, // byte offset in memory to store the return data
                        0x20 // size of the return data
                    )
                ) {
                    // Revert if balance query fails
                    revert(0x00, 0x04)
                }

                // Store `msg.sender`'s balance returned by staticcall into `shares`
                shares := mload(0x00)
            }
        }

        // Transfer caller shares
        address(vault).safeTransferFrom(msg.sender, address(this), shares);

        uint256 amountWithdrawn;

        assembly ("memory-safe") {
            // Cache the free memory pointer
            let m := mload(0x40)

            // Store `vault`'s `withdraw()` function selector:
            // `bytes4(keccak256("withdraw(address)"))`
            mstore(0x00, 0xe63697c8)
            mstore(0x20, shares) // append the `shares` argument
            mstore(0x40, address()) // append the `address(this)` argument
            mstore(0x60, maxLoss) // append the `maxLoss` argument

            // Withdraw from MaxApy vault
            if iszero(
                call(
                    gas(), // Remaining amount of gas
                    cachedVault, // Address of `vault`
                    0, // `msg.value`
                    0x1c, // byte offset in memory where calldata starts
                    0x64, // size of the calldata to copy
                    0x00, // byte offset in memory to store the return data
                    0x20 // size of the return data
                )
            ) {
                // If call failed, throw the error thrown in the previous `call`
                revert(0x00, 0x04)
            }

            // Store `amountWithdrawn` returned by the previous call to `withdraw()`
            amountWithdrawn := mload(0x00)

            // Store `wrappedToken`'s `withdraw()` function selector:
            // `bytes4(keccak256("withdraw(uint256)"))`
            mstore(0x00, 0x2e1a7d4d)
            mstore(0x20, amountWithdrawn) // append the `amountWithdrawn` argument

            // Withdraw from wrapped token
            if iszero(
                call(
                    gas(), // Remaining amount of gas
                    cachedWrappedToken, // Address of `vault`
                    0, // `msg.value`
                    0x1c, // byte offset in memory where calldata starts
                    0x24, // size of the calldata to copy
                    0x00, // byte offset in memory to store the return data
                    0x20 // size of the return data
                )
            ) {
                // If call failed, throw the error thrown in the previous `call`
                revert(0x00, 0x04)
            }

            // Transfer native token back to user
            if iszero(call(gas(), recipient, amountWithdrawn, 0x00, 0x00, 0x00, 0x00)) {
                // If call failed, throw the `FailedNativeTransfer()` error
                mstore(0x00, 0x3c3f4130)
                revert(0x1c, 0x04)
            }

            // Emit the `WithdrawNative` event
            mstore(0x00, shares)
            mstore(0x20, amountWithdrawn)
            log2(0x00, 0x40, _WITHDRAW_NATIVE_EVENT_SIGNATURE, recipient)

            mstore(0x60, 0) // Restore the zero slot
            mstore(0x40, m) // Restore the free memory pointer

            return(0x20, 0x20) // Return `amountWithdrawn` value stored in 0x00 from previous from call's
        }
    }

    ////////////////////////////////////////////////////////////////
    ///                 RECEIVE()  function                      ///
    ////////////////////////////////////////////////////////////////

    /// @notice Receive function to accept native transfers
    /// @dev Note only the chain's wrapped token will be able to perform native token transfers
    /// to this contract
    receive() external payable {
        // Cache `wrappedToken` due to assembly immutable access restrictions
        address cachedWrappedToken = address(wrappedToken);

        assembly {
            // Check if caller is not the `wrappedToken`
            if iszero(eq(caller(), cachedWrappedToken)) {
                // Throw the `ReceiveNotAllowed()` error
                mstore(0x00, 0xcb263c3f)
                revert(0x1c, 0x04)
            }
        }
    }
}

// File: src/interfaces/IWrappedToken.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

interface IWrappedToken {
    function deposit() external payable;

    function transfer(address to, uint256 value) external returns (bool);

    function withdraw(uint256) external;
}

// File: src/interfaces/IMaxApyVault.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import {StrategyData} from "../helpers/VaultTypes.sol";

/**
 * @notice MaxApyVault contains the main interface for MaxApy Vaults
 */
interface IMaxApyVault {
    /// User-facing vault functions
    function deposit(uint256 amount, address recipient) external returns (uint256);

    function withdraw(uint256 shares, address recipient, uint256 maxLoss) external returns (uint256);

    function report(uint128 gain, uint128 loss, uint128 debtPayment) external returns (uint256);

    /// ERC20 Token functions
    function name() external returns (string memory);

    function symbol() external returns (string memory);

    function decimals() external returns (uint8);

    function underlyingAsset() external view returns (address);

    function totalSupply() external view returns (uint256);

    function balanceOf(address user) external view returns (uint256);

    /// Ownership
    function transferOwnership(address newOwner) external payable;

    function renounceOwnership() external payable;

    function requestOwnershipHandover() external payable;

    function cancelOwnershipHandover() external payable;

    function completeOwnershipHandover(address pendingOwner) external payable;

    /// View ownership
    function ownershipHandoverExpiresAt(address pendingOwner) external view returns (uint256);

    function ownershipHandoverValidFor() external view returns (uint64);

    function owner() external view returns (address result);

    /// Roles
    function grantRoles(address user, uint256 roles) external payable;

    function revokeRoles(address user, uint256 roles) external payable;

    function renounceRoles(uint256 roles) external payable;

    /// View roles
    function ADMIN_ROLE() external returns (uint256);

    function EMERGENCY_ADMIN_ROLE() external returns (uint256);

    function KEEPER_ROLE() external returns (uint256);

    function STRATEGY_ROLE() external returns (uint256);

    function hasAnyRole(address user, uint256 roles) external view returns (bool result);

    function hasAllRoles(address user, uint256 roles) external view returns (bool result);

    function rolesOf(address user) external view returns (uint256 roles);

    function rolesFromOrdinals(uint8[] memory ordinals) external pure returns (uint256 roles);

    function ordinalsFromRoles(uint256 roles) external pure returns (uint8[] memory ordinals);

    /// Vault configuration
    function debtRatio() external returns (uint256);

    function totalDebt() external returns (uint256);

    function totalIdle() external returns (uint256);

    function strategies(address strategy) external returns (StrategyData memory);

    function withdrawalQueue(uint256 index) external returns (address);

    function emergencyShutdown() external returns (bool);

    /// Vault management
    function setEmergencyShutdown(bool _emergencyShutdown) external;

    function addStrategy(
        address newStrategy,
        uint256 strategyDebtRatio,
        uint256 strategyMaxDebtPerHarvest,
        uint256 strategyMinDebtPerHarvest,
        uint256 strategyPerformanceFee
    ) external;

    function revokeStrategy(address strategy) external;

    function removeStrategy(address strategy) external;

    function updateStrategyData(
        address strategy,
        uint256 newDebtRatio,
        uint256 newMaxDebtPerHarvest,
        uint256 newMinDebtPerHarvest,
        uint256 newPerformanceFee
    ) external;

    function setWithdrawalQueue(address[20] calldata queue) external;

    function setPerformanceFee(uint256 _performanceFee) external;

    function setManagementFee(uint256 _managementFee) external;

    function setLockedProfitDegradation(uint256 _lockedProfitDegradation) external;

    function setDepositLimit(uint256 _depositLimit) external;

    function setTreasury(address _treasury) external;

    /// Vault view functions
    function performanceFee() external returns (uint256);

    function managementFee() external returns (uint256);

    function lockedProfitDegradation() external view returns (uint256);

    function depositLimit() external returns (uint256);

    function MAXIMUM_STRATEGIES() external returns (uint256);

    function DEGRADATION_COEFFICIENT() external view returns (uint256);

    function shareValue(uint256 shares) external view returns (uint256);

    function sharesForAmount(uint256 amount) external view returns (uint256 shares);

    function debtOutstanding(address strategy) external view returns (uint256);

    function totalAssets() external view returns (uint256);

    function lastReport() external view returns (uint256);

    function lockedProfit() external view returns (uint256);

    function treasury() external view returns (address);
}

// File: lib/solady/src/utils/SafeTransferLib.sol



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

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

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

    /// @dev Suggested gas stipend for contract receiving ETH
    /// that disallows any storage writes.
    uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    /// Multiply by a small constant (e.g. 2), if needed.
    uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` (in wei) ETH to `to`.
    /// Reverts upon failure.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gasStipend, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                if iszero(create(amount, 0x0b, 0x16)) {
                    // For better gas estimation.
                    if iszero(gt(gas(), 1000000)) { revert(0, 0) }
                }
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a gas stipend
    /// equal to `_GAS_STIPEND_NO_GRIEF`. This gas stipend is a reasonable default
    /// for 99% of cases and can be overriden with the three-argument version of this
    /// function if necessary.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        // Manually inlined because the compiler doesn't inline functions with branches.
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(_GAS_STIPEND_NO_GRIEF, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                if iszero(create(amount, 0x0b, 0x16)) {
                    // For better gas estimation.
                    if iszero(gt(gas(), 1000000)) { revert(0, 0) }
                }
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// Simply use `gasleft()` for `gasStipend` if you don't need a gas stipend.
    ///
    /// Note: Does NOT revert upon failure.
    /// Returns whether the transfer of ETH is successful instead.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            success := call(gasStipend, to, amount, 0, 0, 0, 0)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x0c, 0x23b872dd000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // 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(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            // Store the function selector of `balanceOf(address)`.
            mstore(0x0c, 0x70a08231000000000000000000000000)
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x00, 0x23b872dd)
            // The `amount` argument is already written to the memory word at 0x6c.
            amount := mload(0x60)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // 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(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            // Store the function selector of `transfer(address,uint256)`.
            mstore(0x00, 0xa9059cbb000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // 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(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x14, to) // Store the `to` argument.
            // The `amount` argument is already written to the memory word at 0x34.
            amount := mload(0x34)
            // Store the function selector of `transfer(address,uint256)`.
            mstore(0x00, 0xa9059cbb000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // 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(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            // Store the function selector of `approve(address,uint256)`.
            mstore(0x00, 0x095ea7b3000000000000000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // 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(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `ApproveFailed()`.
                mstore(0x00, 0x3e3f8f73)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten.
            mstore(0x34, 0)
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            // Store the function selector of `balanceOf(address)`.
            mstore(0x00, 0x70a08231000000000000000000000000)
            amount :=
                mul(
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }
}

// File: src/helpers/VaultTypes.sol



pragma solidity ^0.8.19;

/// @notice Stores all data from a single strategy
/// @dev Packed in two slots
struct StrategyData {
    /// Slot 0
    /// @notice Maximum percentage available to be lent to strategies(in BPS)
    /// @dev in BPS. uint16 is enough to cover the max BPS value of 10_000
    uint16 strategyDebtRatio;
    /// @notice The performance fee
    /// @dev in BPS. uint16 is enough to cover the max BPS value of 10_000
    uint16 strategyPerformanceFee;
    /// @notice Timestamp when the strategy was added.
    /// @dev Overflowing July 21, 2554
    uint48 strategyActivation;
    /// @notice block.timestamp of the last time a report occured
    /// @dev Overflowing July 21, 2554
    uint48 strategyLastReport;
    /// @notice Upper limit on the increase of debt since last harvest
    /// @dev max debt per harvest to be set to a maximum value of 4,722,366,482,869,645,213,695
    uint128 strategyMaxDebtPerHarvest;
    /// Slot 1
    /// @notice Lower limit on the increase of debt since last harvest
    /// @dev min debt per harvest to be set to a maximum value of 16,777,215
    uint128 strategyMinDebtPerHarvest;
    /// @notice Total returns that Strategy has realized for Vault
    /// @dev max strategy total gain of 79,228,162,514,264,337,593,543,950,335
    uint128 strategyTotalGain;
    /// Slot 2
    /// @notice Total outstanding debt that Strategy has
    /// @dev max total debt of 79,228,162,514,264,337,593,543,950,335
    uint128 strategyTotalDebt;
    /// @notice Total losses that Strategy has realized for Vault
    /// @dev max strategy total loss of 79,228,162,514,264,337,593,543,950,335
    uint128 strategyTotalLoss;
}