// File: src/MevEth.sol



/// SPDX-License-Identifier: SSPL-1.-0

pragma solidity ^0.8.19;

/*///////////// Mev Protocol ///////////////////////
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣷⣤⣀⠀⠀⠀⠀⠀⠉⠑⣶⣤⣄⣀⣠⣤⣶⣶⣿⣿⣿⣿⡇⠀⠀⠀
⠀⠀⠀⠀⣀⣴⣶⣿⣷⡄⠀⠀⠀⠀⢹⣿⣿⣿⣿⠏⠁⠀⢀⠄⠀⠀⠈⢀⠄⠀⢀⡖⠁⠀⢀⠀⠈⠻⣿⣿⣿⣿⡏⠀⠀⠀⠀
⠀⠀⢠⣾⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⢸⣿⣿⠏⠀⠀⢀⡴⠁⠀⠀⣠⠖⠁⢀⠞⠋⠀⢠⡇⢸⡄⠀⠀⠈⢻⣿⣿⠁⠀⠀⠀⠀
⠀⣠⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⢸⡿⠁⠀⠀⢀⡞⠀⠀⢀⡴⠃⠀⣰⠋⠀⠀⣰⡿⠀⡜⢳⡀⠘⣦⠀⢿⡇⠀⠀⠀⠀⠀
⢠⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⢰⣿⠃⠀⢀⠆⡞⡄⠀⣠⡞⠁⣀⢾⠃⠀⣀⡜⢱⠇⣰⠁⠈⣷⠂⢸⡇⠸⣵⠀⠀⠀⠀⠀
⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⢠⣿⠇⠀⠀⡜⣸⡟⢀⣴⡏⢠⣾⠋⡎⢀⣼⠋⢀⡎⡰⠃⠀⠀⣿⣓⢒⡇⠀⣿⠀⠀⠀⠀⠀
⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠴⢻⣟⢀⣀⢀⣧⡇⢨⠟⢾⣔⡿⠃⢸⢀⠞⠃⢀⣾⡜⠁⠀⠀⠀⡏⠁⢠⠃⠀⢹⠀⠀⠀⠀⠀
⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⢸⣼⢸⣿⡟⢻⣿⠿⣶⣿⣿⣿⣶⣾⣏⣀⣠⣾⣿⠔⠒⠉⠉⢠⠁⡆⡸⠀⡈⣸⠀⠀⠀⠀⠀
⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⣸⣿⣸⣿⣇⢸⠃⡄⢻⠃⣾⣿⢋⠘⣿⣿⠏⣿⡟⣛⡛⢻⣿⢿⣶⣷⣿⣶⢃⣿⠀⠀⠀⠀⠀
⢸⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⣰⠃⣿⣿⣿⣿⠀⣸⣧⠈⣸⣿⠃⠘⠃⢹⣿⠀⣿⠃⠛⠛⣿⡇⢸⣿⡇⢸⣿⡿⣿⡀⠀⠀⠀⠀
⠀⠻⣿⣿⣿⣿⣦⡀⠀⢀⡔⣹⣼⡟⡟⣿⣿⣿⠛⠻⠶⠿⠷⣾⣿⣿⣬⣿⣠⣿⣀⣿⣿⣿⡇⠸⡿⠀⣾⡏⢠⣿⣇⠀⠀⠀⠀
⠀⠀⠙⢿⣿⣿⣿⣿⣷⡞⢠⣿⢿⡇⣿⡹⡝⢿⡷⣄⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠙⠛⠛⠻⠿⣶⣶⣾⣿⣇⣾⠉⢯⠃⠀⠀⠀
⠀⠀⠀⠀⠙⠿⣿⣿⣿⠇⢸⠇⠘⣇⠸⡇⣿⣮⣳⡀⠉⠂⠀⠀⣀⣤⡤⢤⣀⠀⠀⠀⠀⠀⢈⣿⠟⣠⣾⠿⣿⡆⡄⣧⡀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠙⠻⡘⠾⣄⠀⠘⢦⣿⠃⠹⣿⣿⣶⠤⠀⠀⣿⠋⠉⠻⣿⠁⠀⠠⣀⣤⣾⣵⣾⡿⠃⣾⠏⣿⣧⠋⡇⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣠⠖⠳⣄⡈⠃⠀⠼⠋⠙⢷⣞⢻⣿⣿⣀⡀⠈⠤⣀⠬⠟⠀⢀⣠⣶⠿⢛⡽⠋⣠⣾⣏⣠⡿⣃⣞⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣧⠀⠀⠀⠉⠛⠓⠢⠶⣶⡤⠺⡟⢺⣿⠿⣿⣶⣤⣀⣠⣴⣾⡿⠿⢵⠋⠙⠲⣏⡝⠁⠀⣹⢿⡣⣌⠒⠄⠀
⠀⠀⠀⠀⠀⠀⢸⠈⡄⠀⠇⠀⠀⡖⠁⢢⡞⠀⢰⠻⣆⡏⣇⠙⠻⣿⣿⣿⣿⠋⢀⡴⣪⢷⡀⠀⡘⠀⢀⠜⠁⢀⠟⢆⠑⢄⠀
⠀⠀⠀⠀⠀⠀⠘⡄⠱⠀⠸⡀⠄⠳⡀⠀⢳⡀⢰⠀⢸⢇⡟⠑⠦⢈⡉⠁⢼⢠⡏⣴⠟⢙⠇⠀⡇⢠⠃⢀⡴⠁⠀⠘⠀⠈⡆
⠀⠀⠀⠀⠀⠀⠀⠇⠀⠣⠀⡗⢣⡀⠘⢄⠀⢧⠀⢳⡟⠛⠙⣧⣧⣠⣄⣀⣠⢿⣶⠁⠀⠸⡀⠀⠓⠚⢴⣋⣠⠔⠀⠀⠀⠀⠁
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠧⡤⠙⢤⡈⣦⡼⠀⠀⠧⢶⠚⡇⠈⠁⠈⠃⠀⡰⢿⣄⠀⠀⠑⢤⣀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
///////////////////////////////////////////////////*/

import { Auth } from "./libraries/Auth.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { FixedPointMathLib } from "solmate/utils/FixedPointMathLib.sol";
import { ERC20 } from "solmate/tokens/ERC20.sol";
import { IERC4626 } from "./interfaces/IERC4626.sol";
import { WETH } from "solmate/tokens/WETH.sol";
import { MevEthErrors } from "./interfaces/Errors.sol";
import { IStakingModule } from "./interfaces/IStakingModule.sol";
import { IERC20Burnable } from "./interfaces/IERC20Burnable.sol";
import { ITinyMevEth } from "./interfaces/ITinyMevEth.sol";

/// @title MevEth
/// @author CommodityStream, Inc.
/// @dev Contract that allows deposit of ETH, for a Liquid Staking Receipt (LSR) in return.
/// @dev LSR is represented through an ERC4626 token and interface.
contract MevEth is Auth, ERC20, IERC4626, ITinyMevEth {
    using SafeTransferLib for WETH;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                            Configuration Variables
    //////////////////////////////////////////////////////////////*/

    /// @notice Inidicates if staking is paused.
    bool public stakingPaused;
    /// @notice Indicates if contract is initialized.
    bool public initialized;
    /// @notice withdraw fee denominator
    uint16 internal constant feeDenominator = 10_000;
    /// @notice Timestamp when pending staking module update can be finalized.
    uint64 public pendingStakingModuleCommittedTimestamp;
    /// @notice Timestamp when pending mevEthShareVault update can be finalized.
    uint64 public pendingMevEthShareVaultCommittedTimestamp;
    /// @notice Time delay before staking module or share vault can be finalized.
    uint64 internal constant MODULE_UPDATE_TIME_DELAY = 7 days;
    /// @notice Max amount of ETH that can be deposited.
    uint128 internal constant MAX_DEPOSIT = type(uint128).max;
    /// @notice Min amount of ETH that can be deposited.
    uint128 public constant MIN_DEPOSIT = 0.01 ether;
    /// @notice Min amount of ETH that can be withdrawn via the queue.
    uint128 public MIN_WITHDRAWAL;
    /// @notice The address of the MevEthShareVault.
    address public mevEthShareVault;
    /// @notice The address of the pending MevEthShareVault when a new vault has been committed but not finalized.
    address public pendingMevEthShareVault;
    /// @notice The staking module used to stake Ether.
    IStakingModule public stakingModule;
    /// @notice The pending staking module when a new module has been committed but not finalized.
    IStakingModule public pendingStakingModule;
    /// @notice WETH Implementation used by MevEth.
    WETH public immutable WETH9;
    /// @notice Last rewards payment by block number
    uint256 internal lastRewards;
    /// @notice Struct used to accounting the ETH staked within MevEth.
    Fraction public fraction;
    /// @notice The percent out of 1000 crETH2 can be redeemed for as mevEth
    uint256 public constant CREAM_TO_MEV_ETH_PERCENT = 1130;
    /// @notice The canonical address of the crETH2 address
    address public constant creamToken = 0x49D72e3973900A195A155a46441F0C08179FdB64;
    /// @notice Sandwich protection mapping of last user deposits by block number
    mapping(address => uint256) lastDeposit;
    /// @notice Deposited validators mapping to prevent double deposits
    mapping(bytes => bool) depositedValidators;

    /// @notice Central struct used for share accounting + math.
    /// @custom:field elastic   Represents total amount of staked ether, including rewards accrued / slashed.
    /// @custom:field base      Represents claims to ownership of the staked ether.
    struct Fraction {
        uint128 elastic;
        uint128 base;
    }

    /*//////////////////////////////////////////////////////////////
                                Setup
    //////////////////////////////////////////////////////////////*/

    /// @notice Construction creates mevETH token, sets authority and weth address.
    /// @dev Pending staking module and committed timestamp will both be zero on deployment.
    /// @param authority Address of the controlling admin authority.
    /// @param weth Address of the WETH contract to use for deposits.
    constructor(address authority, address weth) Auth(authority) ERC20("Mev Liquid Staking Receipt", "mevETH", 18) {
        WETH9 = WETH(payable(weth));
        MIN_WITHDRAWAL = MIN_DEPOSIT;
    }

    /// @notice Calculate the needed Ether buffer required when creating a new validator.
    /// @return uint256 The required Ether buffer.
    function calculateNeededEtherBuffer() public view returns (uint256) {
        unchecked {
            return max(withdrawalAmountQueued, (stakingModule.VALIDATOR_DEPOSIT_SIZE() / 100) * 90);
        }
    }

    /*//////////////////////////////////////////////////////////////
                            Admin Control Panel
    //////////////////////////////////////////////////////////////*/

    /// @notice Event emitted when the MevEth is successfully initialized.
    event MevEthInitialized(address indexed mevEthShareVault, address indexed stakingModule);

    /// @notice Initializes the MevEth contract, setting the staking module and share vault addresses.
    /// @param initialShareVault The initial share vault set during initialization.
    /// @param initialStakingModule The initial staking module set during initialization.
    /// @dev This function can only be called once and is protected by the onlyAdmin modifier.
    function init(address initialShareVault, address initialStakingModule) external onlyAdmin {
        // Revert if the initial share vault or staking module is the zero address.
        if (initialShareVault == address(0)) {
            revert MevEthErrors.ZeroAddress();
        }

        if (initialStakingModule == address(0)) {
            revert MevEthErrors.ZeroAddress();
        }

        // Revert if the contract has already been initialized.
        if (initialized) {
            revert MevEthErrors.AlreadyInitialized();
        }

        // Update state variables and emit event to notify offchain listeners that the contract has been initialized.
        initialized = true;
        mevEthShareVault = initialShareVault;
        stakingModule = IStakingModule(initialStakingModule);
        emit MevEthInitialized(initialShareVault, initialStakingModule);
    }

    /// @notice Emitted when staking is paused.
    event StakingPaused();
    /// @notice Emitted when staking is unpaused.
    event StakingUnpaused();

    /// @notice Ensures that staking is not paused when invoking a specific function.
    /// @dev This check is used on the createValidator, deposit and mint functions.
    function _stakingUnpaused() internal view {
        if (stakingPaused) revert MevEthErrors.StakingPaused();
    }

    /// @notice Pauses staking on the MevEth contract.
    /// @dev This function is only callable by addresses with the admin role.
    function pauseStaking() external onlyAdmin {
        stakingPaused = true;
        emit StakingPaused();
    }

    /// @notice Unauses staking on the MevEth contract.
    /// @dev This function is only callable by addresses with the admin role.
    function unpauseStaking() external onlyAdmin {
        stakingPaused = false;
        emit StakingUnpaused();
    }

    /// @notice Event emitted when a new staking module is committed.
    ///   The MODULE_UPDATE_TIME_DELAY must elapse before the staking module update can be finalized.
    event StakingModuleUpdateCommitted(address indexed oldModule, address indexed pendingModule, uint64 indexed eligibleForFinalization);

    /// @notice Event emitted when a new staking module is finalized.
    event StakingModuleUpdateFinalized(address indexed oldModule, address indexed newModule);

    /// @notice Event emitted when a new pending module update is canceled.
    event StakingModuleUpdateCanceled(address indexed oldModule, address indexed pendingModule);

    /// @notice Starts the process to update the staking module.
    ///   To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse
    ///   and thefinalizeUpdateStakingModule function must be called.
    /// @param newModule The new staking module.
    /// @dev This function is only callable by addresses with the admin role.
    function commitUpdateStakingModule(IStakingModule newModule) external onlyAdmin {
        if (address(newModule) == address(0)) {
            revert MevEthErrors.InvalidPendingStakingModule();
        }

        pendingStakingModule = newModule;
        pendingStakingModuleCommittedTimestamp = uint64(block.timestamp);
        emit StakingModuleUpdateCommitted(address(stakingModule), address(newModule), uint64(block.timestamp + MODULE_UPDATE_TIME_DELAY));
    }

    /// @notice Finalizes the staking module update if a pending staking module exists.
    /// @dev This function is only callable by addresses with the admin role.
    function finalizeUpdateStakingModule() external onlyAdmin {
        // Revert if there is no pending staking module or if the the staking module finalization is premature.
        uint64 committedTimestamp = pendingStakingModuleCommittedTimestamp;
        if (address(pendingStakingModule) == address(0) || committedTimestamp == 0) {
            revert MevEthErrors.InvalidPendingStakingModule();
        }

        if (uint64(block.timestamp) < committedTimestamp + MODULE_UPDATE_TIME_DELAY) {
            revert MevEthErrors.PrematureStakingModuleUpdateFinalization();
        }

        // Emit an event to notify offchain listeners that the staking module has been finalized.
        emit StakingModuleUpdateFinalized(address(stakingModule), address(pendingStakingModule));

        // Update the staking module
        stakingModule = pendingStakingModule;

        // Set the pending staking module variables to zero.
        pendingStakingModule = IStakingModule(address(0));
        pendingStakingModuleCommittedTimestamp = 0;
    }

    /// @notice Cancels a pending staking module update.
    /// @dev This function is only callable by addresses with the admin role.
    function cancelUpdateStakingModule() external onlyAdmin {
        // Revert if there is no pending staking module.
        if (address(pendingStakingModule) == address(0) || pendingStakingModuleCommittedTimestamp == 0) {
            revert MevEthErrors.InvalidPendingStakingModule();
        }

        // Emit an event to notify offchain listeners that the staking module has been canceled.
        emit StakingModuleUpdateCanceled(address(stakingModule), address(pendingStakingModule));

        // Set the pending staking module variables to zero.
        pendingStakingModule = IStakingModule(address(0));
        pendingStakingModuleCommittedTimestamp = 0;
    }

    /// @notice Event emitted when a new share vault is committed. To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse and the
    ///         finalizeUpdateMevEthShareVault function must be called.
    event MevEthShareVaultUpdateCommitted(address indexed oldVault, address indexed pendingVault, uint64 indexed eligibleForFinalization);
    /// @notice Event emitted when a new share vault is finalized.
    event MevEthShareVaultUpdateFinalized(address indexed oldVault, address indexed newVault);
    /// @notice Event emitted when a new pending share vault update is canceled.
    event MevEthShareVaultUpdateCanceled(address indexed oldVault, address indexed newVault);

    /// @notice Starts the process to update the share vault. To finalize the update, the MODULE_UPDATE_TIME_DELAY must elapse and the
    ///         finalizeUpdateStakingModule function must be called.
    /// @param newMevEthShareVault The new share vault
    /// @dev This function is only callable by addresses with the admin role
    function commitUpdateMevEthShareVault(address newMevEthShareVault) external onlyAdmin {
        if (newMevEthShareVault == address(0)) {
            revert MevEthErrors.ZeroAddress();
        }
        pendingMevEthShareVault = newMevEthShareVault;
        pendingMevEthShareVaultCommittedTimestamp = uint64(block.timestamp);
        emit MevEthShareVaultUpdateCommitted(mevEthShareVault, newMevEthShareVault, uint64(block.timestamp + MODULE_UPDATE_TIME_DELAY));
    }

    /// @notice Finalizes the share vault update if a pending share vault exists.
    /// @dev This function is only callable by addresses with the admin role.
    function finalizeUpdateMevEthShareVault() external onlyAdmin {
        // Revert if there is no pending share vault or if the the share vault finalization is premature.
        uint64 committedTimestamp = pendingMevEthShareVaultCommittedTimestamp;
        if (pendingMevEthShareVault == address(0) || committedTimestamp == 0) {
            revert MevEthErrors.InvalidPendingMevEthShareVault();
        }

        if (uint64(block.timestamp) < committedTimestamp + MODULE_UPDATE_TIME_DELAY) {
            revert MevEthErrors.PrematureMevEthShareVaultUpdateFinalization();
        }

        /// @custom:: When finalizing the update to the MevEthShareVault, make sure to grant any remaining rewards from the existing share vault.
        // Emit an event to notify offchain listeners that the share vault has been finalized.
        emit MevEthShareVaultUpdateFinalized(mevEthShareVault, address(pendingMevEthShareVault));

        // Update the mev share vault
        mevEthShareVault = pendingMevEthShareVault;

        // Set the pending vault variables to zero
        pendingMevEthShareVault = address(0);
        pendingMevEthShareVaultCommittedTimestamp = 0;
    }

    /// @notice Cancels a pending share vault update.
    /// @dev This function is only callable by addresses with the admin role.
    function cancelUpdateMevEthShareVault() external onlyAdmin {
        // Revert if there is no pending share vault.
        if (pendingMevEthShareVault == address(0) || pendingMevEthShareVaultCommittedTimestamp == 0) {
            revert MevEthErrors.InvalidPendingMevEthShareVault();
        }
        // Emit an event to notify offchain listeners that the share vault has been canceled.
        emit MevEthShareVaultUpdateCanceled(mevEthShareVault, pendingMevEthShareVault);

        //Set the pending vault variables to zero
        pendingMevEthShareVault = address(0);
        pendingMevEthShareVaultCommittedTimestamp = 0;
    }

    /*//////////////////////////////////////////////////////////////
                            Registry For Validators
    //////////////////////////////////////////////////////////////*/

    /// @notice Event emitted when a new validator is created
    event ValidatorCreated(address indexed stakingModule, IStakingModule.ValidatorData newValidator);

    /// @notice This function passes through the needed Ether to the Staking module, and the assosiated credentials with it
    /// @param newData The data needed to create a new validator
    /// @dev This function is only callable by addresses with the operator role and if staking is unpaused
    function createValidator(IStakingModule.ValidatorData calldata newData, bytes32 latestDepositRoot) external onlyOperator {
        // check if staking is paused
        _stakingUnpaused();
        // check validator does not already exist
        if (depositedValidators[newData.pubkey]) revert MevEthErrors.AlreadyDeposited();
        // set validator deposited to true
        depositedValidators[newData.pubkey] = true;
        IStakingModule _stakingModule = stakingModule;
        // check withdrawal address is correct
        if (address(_stakingModule) != address(uint160(uint256(newData.withdrawal_credentials)))) revert MevEthErrors.IncorrectWithdrawalCredentials();
        // Determine how big deposit is for the validator
        uint256 depositSize = _stakingModule.VALIDATOR_DEPOSIT_SIZE();

        if (address(this).balance < depositSize + calculateNeededEtherBuffer()) {
            revert MevEthErrors.NotEnoughEth();
        }

        // Deposit the Ether into the staking contract
        _stakingModule.deposit{ value: depositSize }(newData, latestDepositRoot);

        emit ValidatorCreated(address(_stakingModule), newData);
    }

    /// @notice Event emitted when rewards are granted.
    event Rewards(address sender, uint256 amount);

    /// @notice Grants rewards updating the fraction.elastic.
    /// @dev called from validator rewards updates
    function grantRewards() external payable {
        if (!(msg.sender == address(stakingModule) || msg.sender == mevEthShareVault)) revert MevEthErrors.UnAuthorizedCaller();
        if (msg.value == 0) revert MevEthErrors.ZeroValue();

        fraction.elastic += uint128(msg.value);
        lastRewards = block.number;
        emit Rewards(msg.sender, msg.value);
    }

    /// @notice  Emitted when validator withdraw funds are received.
    event ValidatorWithdraw(address sender, uint256 amount);

    /// @notice Allows the MevEthShareVault or the staking module to withdraw validator funds from the contract.
    /// @dev Before updating the fraction, the withdrawal queue is processed, which pays out any pending withdrawals.
    /// @dev This function is only callable by the MevEthShareVault or the staking module.
    function grantValidatorWithdraw() external payable {
        // Check that the sender is the staking module or the MevEthShareVault.
        if (!(msg.sender == address(stakingModule) || msg.sender == mevEthShareVault)) revert MevEthErrors.InvalidSender();

        // Check that the value is not zero
        if (msg.value != 32 ether) {
            revert MevEthErrors.WrongWithdrawAmount();
        }

        // Emit an event to notify offchain listeners that a validator has withdrawn funds.
        emit ValidatorWithdraw(msg.sender, msg.value);

        // Register our exit with the staking module
        stakingModule.registerExit();
    }

    /*//////////////////////////////////////////////////////////////
                            WITHDRAWAL QUEUE
    //////////////////////////////////////////////////////////////*/

    /// @notice Struct representing a withdrawal ticket which is added to the withdrawal queue.
    /// @custom:field claimed               True if this receiver has received ticket funds.
    /// @custom:field receiver              The receiever of the ETH specified in the WithdrawalTicket.
    /// @custom:field amount                The amount of ETH to send to the receiver when the ticket is processed.
    /// @custom:field accumulatedAmount     Keep a running sum of all requested ETH
    struct WithdrawalTicket {
        bool claimed;
        address receiver;
        uint128 amount;
        uint128 accumulatedAmount;
    }

    /// @notice Event emitted when a withdrawal ticket is added to the queue.
    event WithdrawalQueueOpened(address indexed recipient, uint256 indexed withdrawalId, uint256 assets);
    event WithdrawalQueueClosed(address indexed recipient, uint256 indexed withdrawalId, uint256 assets);

    /// @notice The length of the withdrawal queue.
    uint256 public queueLength;

    /// @notice  mark the latest withdrawal request that was finalised
    uint256 public requestsFinalisedUntil;

    /// @notice Withdrawal amount queued
    uint256 public withdrawalAmountQueued;

    /// @notice The mapping representing the withdrawal queue.
    /// @dev The index in the queue is the key, and the value is the WithdrawalTicket.
    mapping(uint256 ticketNumber => WithdrawalTicket ticket) public withdrawalQueue;

    /// @notice Claim Finalised Withdrawal Ticket
    /// @param withdrawalId Unique ID of the withdrawal ticket
    function claim(uint256 withdrawalId) external {
        if (withdrawalId > requestsFinalisedUntil) revert MevEthErrors.NotFinalised();
        WithdrawalTicket storage ticket = withdrawalQueue[withdrawalId];
        if (ticket.claimed) revert MevEthErrors.AlreadyClaimed();
        withdrawalQueue[withdrawalId].claimed = true;
        withdrawalAmountQueued -= uint256(ticket.amount);
        emit WithdrawalQueueClosed(ticket.receiver, withdrawalId, uint256(ticket.amount));
        WETH9.deposit{ value: uint256(ticket.amount) }();
        WETH9.safeTransfer(ticket.receiver, uint256(ticket.amount));
    }

    /// @notice Processes the withdrawal queue, reserving any pending withdrawals with the contract's available balance.
    function processWithdrawalQueue(uint256 newRequestsFinalisedUntil) external onlyOperator {
        if (newRequestsFinalisedUntil > queueLength) revert MevEthErrors.IndexExceedsQueueLength();
        uint256 balance = address(this).balance;
        if (withdrawalAmountQueued >= balance) revert MevEthErrors.NotEnoughEth();
        uint256 available = balance - withdrawalAmountQueued;

        uint256 finalised = requestsFinalisedUntil;
        if (newRequestsFinalisedUntil < finalised) revert MevEthErrors.AlreadyFinalised();

        uint256 delta = uint256(withdrawalQueue[newRequestsFinalisedUntil].accumulatedAmount - withdrawalQueue[finalised].accumulatedAmount);
        if (available < delta) revert MevEthErrors.NotEnoughEth();

        requestsFinalisedUntil = newRequestsFinalisedUntil;
        withdrawalAmountQueued += delta;
    }

    function setMinWithdrawal(uint128 newMinimum) public onlyAdmin {
        MIN_WITHDRAWAL = newMinimum;
    }

    /*//////////////////////////////////////////////////////////////
                            ERC4626 Support
    //////////////////////////////////////////////////////////////*/
    /// @notice The underlying asset of the mevEth contract
    /// @return assetTokenAddress The address of the asset token
    function asset() external view returns (address assetTokenAddress) {
        assetTokenAddress = address(WETH9);
    }

    /// @notice The total amount of assets controlled by the mevEth contract
    /// @return totalManagedAssets The amount of eth controlled by the mevEth contract
    function totalAssets() external view returns (uint256 totalManagedAssets) {
        // Should return the total amount of Ether managed by the contract
        totalManagedAssets = uint256(fraction.elastic);
    }

    /// @notice Function to convert a specified amount of assets to shares based on the elastic and base.
    /// @param assets The amount of assets to convert to shares
    /// @return shares The value of the given assets in shares
    function convertToShares(uint256 assets) public view returns (uint256 shares) {
        // So if there are no shares, then they will mint 1:1 with assets
        // Otherwise, shares will mint proportional to the amount of assets
        if ((uint256(fraction.elastic) == 0) || (uint256(fraction.base) == 0)) {
            shares = assets;
        } else {
            shares = (assets * uint256(fraction.base)) / uint256(fraction.elastic);
        }
    }

    /// @notice Function to convert a specified amount of shares to assets based on the elastic and base.
    /// @param shares The amount of shares to convert to assets
    /// @return assets The value of the given shares in assets
    function convertToAssets(uint256 shares) public view returns (uint256 assets) {
        // So if there are no shares, then they will mint 1:1 with assets
        // Otherwise, shares will mint proportional to the amount of assets
        if (uint256(fraction.elastic) == 0 || uint256(fraction.base) == 0) {
            assets = shares;
        } else {
            assets = (shares * uint256(fraction.elastic)) / uint256(fraction.base);
        }
    }

    /// @notice Function to indicate the maximum deposit possible.
    /// @return maxAssets The maximum amount of assets that can be deposited.
    function maxDeposit(address) external view returns (uint256 maxAssets) {
        // If staking is paused, then no deposits can be made
        if (stakingPaused) {
            return 0;
        }
        // No practical limit on deposit for Ether
        maxAssets = uint256(MAX_DEPOSIT);
    }

    /// @notice Function to simulate the amount of shares that would be minted for a given deposit at the current ratio.
    /// @param assets The amount of assets that would be deposited
    /// @return shares The amount of shares that would be minted, *under ideal conditions* only
    function previewDeposit(uint256 assets) external view returns (uint256 shares) {
        return convertToShares(assets);
    }

    /// @notice internal deposit function to process Weth or Eth deposits
    /// @param receiver The address user whom should receive the mevEth out
    /// @param assets The amount of assets to deposit
    /// @param shares The amount of shares that should be minted
    function _deposit(address receiver, uint256 assets, uint256 shares) internal {
        // If the deposit is less than the minimum deposit, revert
        if (assets < MIN_DEPOSIT) revert MevEthErrors.DepositTooSmall();

        fraction.elastic += uint128(assets);
        fraction.base += uint128(shares);

        // Update last deposit block for the user recorded for sandwich protection
        lastDeposit[msg.sender] = block.number;
        lastDeposit[receiver] = block.number;

        if (msg.value == 0) {
            WETH9.safeTransferFrom(msg.sender, address(this), assets);
            WETH9.withdraw(assets);
        } else {
            if (msg.value != assets) revert MevEthErrors.WrongDepositAmount();
        }

        // Mint MevEth shares to the receiver
        _mint(receiver, shares);

        // Emit the deposit event to notify offchain listeners that a deposit has occured
        emit Deposit(msg.sender, receiver, assets, shares);
    }

    /// @notice Function to deposit assets into the mevEth contract
    /// @param assets The amount of WETH which should be deposited
    /// @param receiver The address user whom should receive the mevEth out
    /// @return shares The amount of shares minted
    function deposit(uint256 assets, address receiver) external payable returns (uint256 shares) {
        _stakingUnpaused();

        // Convert the assets to shares and update the fraction elastic and base
        shares = convertToShares(assets);

        // Deposit the assets
        _deposit(receiver, assets, shares);
    }

    /// @notice Function to indicate the maximum amount of shares that can be minted at the current ratio.
    /// @return maxShares The maximum amount of shares that can be minted
    function maxMint(address) external view returns (uint256 maxShares) {
        // If staking is paused, no shares can be minted
        if (stakingPaused) {
            return 0;
        }
        // No practical limit on mint for Ether
        return MAX_DEPOSIT;
    }

    /// @notice Function to simulate the amount of assets that would be required to mint a given amount of shares at the current ratio.
    /// @param shares The amount of shares that would be minted
    /// @return assets The amount of assets that would be required, *under ideal conditions* only
    function previewMint(uint256 shares) external view returns (uint256 assets) {
        return convertToAssets(shares);
    }

    /// @notice Function to mint shares of the mevEth contract
    /// @param shares The amount of shares that should be minted
    /// @param receiver The address user whom should receive the mevEth out
    /// @return assets The amount of assets deposited
    function mint(uint256 shares, address receiver) external payable returns (uint256 assets) {
        _stakingUnpaused();

        // Convert the shares to assets and update the fraction elastic and base
        assets = convertToAssets(shares);

        // Deposit the assets
        _deposit(receiver, assets, shares);
    }

    /// @notice Function to indicate the maximum amount of assets that can be withdrawn at the current state.
    /// @param owner The address in question of who would be withdrawing
    /// @return maxAssets The maximum amount of assets that can be withdrawn
    function maxWithdraw(address owner) external view returns (uint256 maxAssets) {
        // Withdrawal is either their maximum balance, or the internal buffer
        maxAssets = min(address(this).balance, convertToAssets(balanceOf[owner]));
    }

    /// @notice Function to simulate the amount of shares that would be allocated for a specified amount of assets.
    /// @param assets The amount of assets that would be withdrawn
    /// @return shares The amount of shares that would be burned, *under ideal conditions* only
    function previewWithdraw(uint256 assets) external view returns (uint256 shares) {
        // withdraw fee fixed at 0.01%
        uint256 fee = assets / uint256(feeDenominator);
        shares = convertToShares(assets + fee);
    }

    ///@notice Function to withdraw assets from the mevEth contract
    /// @param useQueue Flag whether to use the withdrawal queue
    /// @param receiver The address user whom should receive the mevEth out
    /// @param owner The address of the owner of the mevEth
    /// @param assets The amount of assets that should be withdrawn
    /// @param shares shares that will be burned
    function _withdraw(bool useQueue, address receiver, address owner, uint256 assets, uint256 shares) internal {
        // If withdraw is less than the minimum deposit / withdraw amount, revert
        if (assets < MIN_WITHDRAWAL) revert MevEthErrors.WithdrawTooSmall();
        // Sandwich protection
        uint256 blockNumber = block.number;

        if (((blockNumber - lastDeposit[msg.sender]) == 0 || (blockNumber - lastDeposit[owner] == 0)) && (blockNumber - lastRewards) == 0) {
            revert MevEthErrors.SandwichProtection();
        }

        _updateAllowance(owner, shares);

        // Update the elastic and base
        fraction.elastic -= uint128(assets);
        fraction.base -= uint128(shares);

        // Burn the shares and emit a withdraw event for offchain listeners to know that a withdraw has occured
        _burn(owner, shares);

        uint256 availableBalance = address(this).balance - withdrawalAmountQueued; // available balance will be adjusted
        uint256 amountToSend = assets;
        if (availableBalance < assets) {
            if (!useQueue) revert MevEthErrors.NotEnoughEth();
            // Available balance is sent, and the remainder must be withdrawn via the queue
            uint256 amountOwed = assets - availableBalance;
            ++queueLength;
            withdrawalQueue[queueLength] = WithdrawalTicket({
                claimed: false,
                receiver: receiver,
                amount: uint128(amountOwed),
                accumulatedAmount: withdrawalQueue[queueLength - 1].accumulatedAmount + uint128(amountOwed)
            });
            emit WithdrawalQueueOpened(receiver, queueLength, amountOwed);
            amountToSend = availableBalance;
        }
        if (amountToSend != 0) {
            // As with ERC4626, we log assets and shares as if there is no queue, and everything has been withdrawn
            // as this most closely resembles what is happened
            emit Withdraw(msg.sender, owner, receiver, assets, shares);

            WETH9.deposit{ value: amountToSend }();
            WETH9.safeTransfer(receiver, amountToSend);
        }
    }

    /// @dev internal function to update allowance for withdraws if necessary
    /// @param owner owner of tokens
    /// @param shares amount of shares to update
    function _updateAllowance(address owner, uint256 shares) internal {
        uint256 allowed = allowance[owner][msg.sender];
        if (owner != msg.sender) {
            if (allowed < shares) revert MevEthErrors.TransferExceedsAllowance();
            if (allowed != type(uint256).max) {
                unchecked {
                    allowance[owner][msg.sender] -= shares;
                }
            }
        }
    }

    /// @notice Withdraw assets if balance is available
    /// @param assets The amount of assets that should be withdrawn
    /// @param receiver The address user whom should receive the mevEth out
    /// @param owner The address of the owner of the mevEth
    /// @return shares The amount of shares burned
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares) {
        // withdraw fee fixed at 0.01%
        uint256 fee = assets / uint256(feeDenominator);
        // Convert the assets to shares and check if the owner has the allowance to withdraw the shares.
        shares = convertToShares(assets + fee);

        // Withdraw the assets from the MevEth contract
        _withdraw(false, receiver, owner, assets, shares);
    }

    /// @notice Withdraw assets or open queue ticket for claim depending on balance available
    /// @param assets The amount of assets that should be withdrawn
    /// @param receiver The address user whom should receive the mevEth out
    /// @param owner The address of the owner of the mevEth
    /// @return shares The amount of shares burned
    function withdrawQueue(uint256 assets, address receiver, address owner) external returns (uint256 shares) {
        // withdraw fee fixed at 0.01%
        uint256 fee = assets / uint256(feeDenominator);
        // last shareholder has no fee
        if ((fraction.elastic - assets) == 0) fee = 0;
        // Convert the assets to shares and check if the owner has the allowance to withdraw the shares.
        shares = convertToShares(assets + fee);

        // Withdraw the assets from the MevEth contract
        _withdraw(true, receiver, owner, assets, shares);
    }

    ///@notice Function to simulate the maximum amount of shares that can be redeemed by the owner.
    /// @param owner The address in question of who would be redeeming their shares
    /// @return maxShares The maximum amount of shares they could redeem
    function maxRedeem(address owner) external view returns (uint256 maxShares) {
        maxShares = min(convertToShares(address(this).balance), balanceOf[owner]);
    }

    /// @notice Function to simulate the amount of assets that would be withdrawn for a specified amount of shares.
    /// @param shares The amount of shares that would be burned
    /// @return assets The amount of assets that would be withdrawn, *under ideal conditions* only
    function previewRedeem(uint256 shares) external view returns (uint256 assets) {
        // withdraw fee fixed at 0.01%
        uint256 fee = shares / uint256(feeDenominator);
        assets = convertToAssets(shares - fee);
    }

    /// @notice Function to redeem shares from the mevEth contract
    /// @param shares The amount of shares that should be burned
    /// @param receiver The address user whom should receive the wETH out
    /// @param owner The address of the owner of the mevEth
    /// @return assets The amount of assets withdrawn
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {
        // withdraw fee fixed at 0.01%
        uint256 fee = shares / uint256(feeDenominator);
        // last shareholder has no fee
        if ((totalSupply - shares) == 0) fee = 0;
        // Convert the shares to assets and check if the owner has the allowance to withdraw the shares.
        assets = convertToAssets(shares - fee);

        // Withdraw the assets from the MevEth contract
        _withdraw(false, receiver, owner, assets, shares);
    }

    /*//////////////////////////////////////////////////////////////
                            Utility Functions
    //////////////////////////////////////////////////////////////*/

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

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

    /*//////////////////////////////////////////////////////////////
             Special CreamEth2 redeem (from initial migration)
     //////////////////////////////////////////////////////////////*/

    /// @notice Redeem Cream staked eth tokens for mevETH at a fixed ratio
    /// @param creamAmount The amount of Cream tokens to redeem
    function redeemCream(uint256 creamAmount) external {
        _stakingUnpaused();
        if (creamAmount == 0) revert MevEthErrors.ZeroValue();

        // Calculate the equivalent mevETH to be redeemed based on the ratio
        uint256 assets = creamAmount * uint256(CREAM_TO_MEV_ETH_PERCENT) / 1000;
        if (assets < MIN_DEPOSIT) revert MevEthErrors.DepositTooSmall();

        // Convert the shares to assets and update the fraction elastic and base
        uint256 shares = convertToShares(assets);

        fraction.elastic += uint128(assets);
        fraction.base += uint128(shares);

        // Burn CreamEth2 tokens
        IERC20Burnable(creamToken).burnFrom(msg.sender, creamAmount);

        // Mint the equivalent mevETH
        _mint(msg.sender, shares);

        // Emit event
        emit CreamRedeemed(msg.sender, creamAmount, shares);
    }

    // Event emitted when Cream tokens are redeemed for mevETH
    event CreamRedeemed(address indexed redeemer, uint256 creamAmount, uint256 mevEthAmount);

    /// @dev Only Weth withdraw is defined for the behaviour. Deposits should be directed to deposit / mint. Rewards via grantRewards and validator withdraws
    /// via grantValidatorWithdraw.
    receive() external payable {
        if (msg.sender != address(WETH9)) revert MevEthErrors.InvalidSender();
    }

    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        uint256 lastDepositFrom = lastDeposit[msg.sender];
        if (lastDepositFrom > lastDeposit[to]) {
            lastDeposit[to] = lastDepositFrom;
        }

        return super.transfer(to, amount);
    }

    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        uint256 lastDepositFrom = lastDeposit[from];
        if (lastDepositFrom > lastDeposit[to]) {
            lastDeposit[to] = lastDepositFrom;
        }

        return super.transferFrom(from, to, amount);
    }
}

// File: src/libraries/Auth.sol



/// SPDX-License-Identifier: SSPL-1.-0

/**
 * @custom:org.protocol='mevETH LST Protocol'
 * @custom:org.security='mailto:security@manifoldfinance.com'
 * @custom:org.vcs-commit=$GIT_COMMIT_SHA
 * @custom:org.vendor='CommodityStream, Inc'
 * @custom:org.schema-version="1.0"
 * @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
 * @custom:org.preferred-languages="en"
 */

pragma solidity ^0.8.19;

contract Auth {
    error Unauthorized();
    error AlreadySet();
    error NoAdmin();

    event AdminAdded(address indexed newAdmin);
    event AdminDeleted(address indexed oldAdmin);
    event OperatorAdded(address indexed newOperator);
    event OperatorDeleted(address indexed oldOperator);

    // admin counter (assuming 255 admins to be max)
    uint8 adminsCounter;

    // Keeps track of all operators
    mapping(address => bool) public operators;

    // Keeps track of all admins
    mapping(address => bool) public admins;

    /**
     * @notice This constructor sets the initialAdmin address as an admin and operator.
     * @dev The adminsCounter is incremented unchecked.
     */
    constructor(address initialAdmin) {
        admins[initialAdmin] = true;
        unchecked {
            ++adminsCounter;
        }
        operators[initialAdmin] = true;
    }

    /*//////////////////////////////////////////////////////////////
                           Access Control Modifiers
    //////////////////////////////////////////////////////////////*/

    modifier onlyAdmin() {
        if (!admins[msg.sender]) {
            revert Unauthorized();
        }
        _;
    }

    modifier onlyOperator() {
        if (!operators[msg.sender]) {
            revert Unauthorized();
        }
        _;
    }

    /*//////////////////////////////////////////////////////////////
                           Maintenance Functions
    //////////////////////////////////////////////////////////////*/
    /**
     * @notice addAdmin() function allows an admin to add a new admin to the contract.
     * @dev This function is only accessible to the existing admins and requires the address of the new admin.
     * If the new admin is already set, the function will revert. Otherwise, the adminsCounter will be incremented and the new admin will be added to the admins
     * mapping. An AdminAdded event will be emitted.
     */
    function addAdmin(address newAdmin) external onlyAdmin {
        if (admins[newAdmin]) revert AlreadySet();
        ++adminsCounter;
        admins[newAdmin] = true;
        emit AdminAdded(newAdmin);
    }

    /**
     * @notice Deletes an admin from the list of admins.
     * @dev Only admins can delete other admins. If the adminsCounter is 0, the transaction will revert.
     */
    function deleteAdmin(address oldAdmin) external onlyAdmin {
        if (!admins[oldAdmin]) revert AlreadySet();
        --adminsCounter;
        if (adminsCounter == 0) revert NoAdmin();
        admins[oldAdmin] = false;
        emit AdminDeleted(oldAdmin);
    }

    /**
     * @notice Adds a new operator to the list of operators
     * @dev Only the admin can add a new operator
     * @param newOperator The address of the new operator
     */
    function addOperator(address newOperator) external onlyAdmin {
        if (operators[newOperator]) revert AlreadySet();
        operators[newOperator] = true;
        emit OperatorAdded(newOperator);
    }

    function deleteOperator(address oldOperator) external onlyAdmin {
        if (!operators[oldOperator]) revert AlreadySet();
        operators[oldOperator] = false;
        emit OperatorDeleted(oldOperator);
    }
}

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



// SPDX-License-Identifier: MIT
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/Rari-Capital/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 {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

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

        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;

        assembly {
            // We'll write our calldata to this slot below, but restore it later.
            let memPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(4, from) // Append the "from" argument.
            mstore(36, to) // Append the "to" argument.
            mstore(68, amount) // Append the "amount" argument.

            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 that's the total length of our calldata (4 + 32 * 3)
                // Counterintuitively, this call() must be positioned after the or() in the
                // surrounding and() because and() evaluates its arguments from right to left.
                call(gas(), token, 0, 0, 100, 0, 32)
            )

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, memPointer) // Restore the memPointer.
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

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

        assembly {
            // We'll write our calldata to this slot below, but restore it later.
            let memPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(0, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(4, to) // Append the "to" argument.
            mstore(36, amount) // Append the "amount" argument.

            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 that's the total length of our calldata (4 + 32 * 2)
                // Counterintuitively, this call() must be positioned after the or() in the
                // surrounding and() because and() evaluates its arguments from right to left.
                call(gas(), token, 0, 0, 68, 0, 32)
            )

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, memPointer) // Restore the memPointer.
        }

        require(success, "TRANSFER_FAILED");
    }

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

        assembly {
            // We'll write our calldata to this slot below, but restore it later.
            let memPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(0, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(4, to) // Append the "to" argument.
            mstore(36, amount) // Append the "amount" argument.

            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 that's the total length of our calldata (4 + 32 * 2)
                // Counterintuitively, this call() must be positioned after the or() in the
                // surrounding and() because and() evaluates its arguments from right to left.
                call(gas(), token, 0, 0, 68, 0, 32)
            )

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, memPointer) // Restore the memPointer.
        }

        require(success, "APPROVE_FAILED");
    }
}

// File: lib/solmate/src/utils/FixedPointMathLib.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    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.
    }

    function powWad(int256 x, int256 y) internal pure returns (int256) {
        // Equivalent to x to the power of y because x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)
        return expWad((lnWad(x) * y) / int256(WAD)); // Using ln(x) means x must be greater than 0.
    }

    function expWad(int256 x) internal pure returns (int256 r) {
        unchecked {
            // When the result is < 0.5 we return zero. This happens when
            // x <= floor(log(0.5e18) * 1e18) ~ -42e18
            if (x <= -42139678854452767551) return 0;

            // When the result is > (2**255 - 1) / 1e18 we can not represent it as an
            // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135.
            if (x >= 135305999368893231589) revert("EXP_OVERFLOW");

            // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96
            // for more intermediate precision and a binary basis. This base conversion
            // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
            x = (x << 78) / 5**18;

            // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
            // of two such that exp(x) = exp(x') * 2**k, where k is an integer.
            // Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
            int256 k = ((x << 96) / 54916777467707473351141471128 + 2**95) >> 96;
            x = x - k * 54916777467707473351141471128;

            // k is in the range [-61, 195].

            // Evaluate using a (6, 7)-term rational approximation.
            // p is made monic, we'll multiply by a scale factor later.
            int256 y = x + 1346386616545796478920950773328;
            y = ((y * x) >> 96) + 57155421227552351082224309758442;
            int256 p = y + x - 94201549194550492254356042504812;
            p = ((p * y) >> 96) + 28719021644029726153956944680412240;
            p = p * x + (4385272521454847904659076985693276 << 96);

            // We leave p in 2**192 basis so we don't need to scale it back up for the division.
            int256 q = x - 2855989394907223263936484059900;
            q = ((q * x) >> 96) + 50020603652535783019961831881945;
            q = ((q * x) >> 96) - 533845033583426703283633433725380;
            q = ((q * x) >> 96) + 3604857256930695427073651918091429;
            q = ((q * x) >> 96) - 14423608567350463180887372962807573;
            q = ((q * x) >> 96) + 26449188498355588339934803723976023;

            assembly {
                // Div in assembly because solidity adds a zero check despite the unchecked.
                // The q polynomial won't have zeros in the domain as all its roots are complex.
                // No scaling is necessary because p is already 2**96 too large.
                r := sdiv(p, q)
            }

            // r should be in the range (0.09, 0.25) * 2**96.

            // We now need to multiply r by:
            // * the scale factor s = ~6.031367120.
            // * the 2**k factor from the range reduction.
            // * the 1e18 / 2**96 factor for base conversion.
            // We do this all at once, with an intermediate result in 2**213
            // basis, so the final right shift is always by a positive amount.
            r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k));
        }
    }

    function lnWad(int256 x) internal pure returns (int256 r) {
        unchecked {
            require(x > 0, "UNDEFINED");

            // We want to convert x from 10**18 fixed point to 2**96 fixed point.
            // We do this by multiplying by 2**96 / 10**18. But since
            // ln(x * C) = ln(x) + ln(C), we can simply do nothing here
            // and add ln(2**96 / 10**18) at the end.

            // Reduce range of x to (1, 2) * 2**96
            // ln(2^k * x) = k * ln(2) + ln(x)
            int256 k = int256(log2(uint256(x))) - 96;
            x <<= uint256(159 - k);
            x = int256(uint256(x) >> 159);

            // Evaluate using a (8, 8)-term rational approximation.
            // p is made monic, we will multiply by a scale factor later.
            int256 p = x + 3273285459638523848632254066296;
            p = ((p * x) >> 96) + 24828157081833163892658089445524;
            p = ((p * x) >> 96) + 43456485725739037958740375743393;
            p = ((p * x) >> 96) - 11111509109440967052023855526967;
            p = ((p * x) >> 96) - 45023709667254063763336534515857;
            p = ((p * x) >> 96) - 14706773417378608786704636184526;
            p = p * x - (795164235651350426258249787498 << 96);

            // We leave p in 2**192 basis so we don't need to scale it back up for the division.
            // q is monic by convention.
            int256 q = x + 5573035233440673466300451813936;
            q = ((q * x) >> 96) + 71694874799317883764090561454958;
            q = ((q * x) >> 96) + 283447036172924575727196451306956;
            q = ((q * x) >> 96) + 401686690394027663651624208769553;
            q = ((q * x) >> 96) + 204048457590392012362485061816622;
            q = ((q * x) >> 96) + 31853899698501571402653359427138;
            q = ((q * x) >> 96) + 909429971244387300277376558375;
            assembly {
                // Div in assembly because solidity adds a zero check despite the unchecked.
                // The q polynomial is known not to have zeros in the domain.
                // No scaling required because p is already 2**96 too large.
                r := sdiv(p, q)
            }

            // r is in the range (0, 0.125) * 2**96

            // Finalization, we need to:
            // * multiply by the scale factor s = 5.549…
            // * add ln(2**96 / 10**18)
            // * add k * ln(2)
            // * multiply by 10**18 / 2**96 = 5**18 >> 78

            // mul s * 5e18 * 2**96, base is now 5**18 * 2**192
            r *= 1677202110996718588342820967067443963516166;
            // add ln(2) * k * 5e18 * 2**192
            r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k;
            // add ln(2**96 / 10**18) * 5e18 * 2**192
            r += 600920179829731861736702779321621459595472258049074101567377883020018308;
            // base conversion: mul 2**18 / 2**192
            r >>= 174;
        }
    }

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

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        assembly {
            // Store x * y in z for now.
            z := mul(x, y)

            // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
            if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
                revert(0, 0)
            }

            // Divide z by the denominator.
            z := div(z, denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        assembly {
            // Store x * y in z for now.
            z := mul(x, y)

            // Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
            if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
                revert(0, 0)
            }

            // First, divide z - 1 by the denominator and add 1.
            // We allow z - 1 to underflow if z is 0, because we multiply the
            // end result by 0 if z is zero, ensuring we return 0 if z is zero.
            z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        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) {
        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 log2(uint256 x) internal pure returns (uint256 r) {
        require(x > 0, "UNDEFINED");

        assembly {
            r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(r, shl(3, lt(0xff, shr(r, x))))
            r := or(r, shl(2, lt(0xf, shr(r, x))))
            r := or(r, shl(1, lt(0x3, shr(r, x))))
            r := or(r, lt(0x1, shr(r, x)))
        }
    }

    function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        assembly {
            // z will equal 0 if y is 0, unlike in Solidity where it will revert.
            z := mod(x, y)
        }
    }

    function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        assembly {
            // z will equal 0 if y is 0, unlike in Solidity where it will revert.
            z := div(x, y)
        }
    }

    /// @dev Will return 0 instead of reverting if y is zero.
    function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        assembly {
            // Add 1 to x * y if x % y > 0.
            z := add(gt(mod(x, y), 0), div(x, y))
        }
    }
}

// File: lib/solmate/src/tokens/ERC20.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

// File: src/interfaces/IERC4626.sol



/// SPDX-License-Identifier: SSPL-1.-0

/**
 * @custom:org.protocol='mevETH LST Protocol'
 * @custom:org.security='mailto:security@manifoldfinance.com'
 * @custom:org.vcs-commit=$GIT_COMMIT_SHA
 * @custom:org.vendor='CommodityStream, Inc'
 * @custom:org.schema-version="1.0"
 * @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
 * @custom:org.preferred-languages="en"
 */

pragma solidity ^0.8.19;

// Also a superset of ERC20 but due to some solmate <-> OZ IERC20 nastiness this interface doesn't include it
interface IERC4626 {
    /// @return assetTokenAddress The address of the asset token
    function asset() external view returns (address assetTokenAddress);

    /// @return totalManagedAssets The amount of eth controlled by the vault
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /// @param assets The amount of assets to convert to shares
    /// @return shares The value of the given assets in shares
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /// @param shares The amount of shares to convert to assets
    /// @return assets The value of the given shares in assets
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /// @param reciever The address in question of who would be depositing, doesn't matter in this case
    /// @return maxAssets The maximum amount of assets that can be deposited
    function maxDeposit(address reciever) external view returns (uint256 maxAssets);

    /// @param assets The amount of assets that would be deposited
    /// @return shares The amount of shares that would be minted, *under ideal conditions* only
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /// @param assets The amount of WETH which should be deposited
    /// @param receiver The address user whom should recieve the mevEth out
    /// @return shares The amount of shares minted
    function deposit(uint256 assets, address receiver) external payable returns (uint256 shares);

    /// @param reciever The address in question of who would be minting, doesn't matter in this case
    /// @return maxShares The maximum amount of shares that can be minted
    function maxMint(address reciever) external view returns (uint256 maxShares);

    /// @param shares The amount of shares that would be minted
    /// @return assets The amount of assets that would be required, *under ideal conditions* only
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /// @param shares The amount of shares that should be minted
    /// @param receiver The address user whom should recieve the mevEth out
    /// @return assets The amount of assets deposited
    function mint(uint256 shares, address receiver) external payable returns (uint256 assets);

    /// @param owner The address in question of who would be withdrawing
    /// @return maxAssets The maximum amount of assets that can be withdrawn
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /// @param assets The amount of assets that would be withdrawn
    /// @return shares The amount of shares that would be burned, *under ideal conditions* only
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /// @param assets The amount of assets that should be withdrawn
    /// @param receiver The address user whom should recieve the mevEth out
    /// @param owner The address of the owner of the mevEth
    /// @return shares The amount of shares burned
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /// @param owner The address in question of who would be redeeming their shares
    /// @return maxShares The maximum amount of shares they could redeem
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /// @param shares The amount of shares that would be burned
    /// @return assets The amount of assets that would be withdrawn, *under ideal conditions* only
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /// @param shares The amount of shares that should be burned
    /// @param receiver The address user whom should recieve the wETH out
    /// @param owner The address of the owner of the mevEth
    /// @return assets The amount of assets withdrawn
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    /**
     * @dev Emitted when a deposit is made, either through mint or deposit
     */
    event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);

    /**
     * @dev Emitted when a withdrawal is made, either through redeem or withdraw
     */
    event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
}

// File: lib/solmate/src/tokens/WETH.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

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

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

/// @notice Minimalist and modern Wrapped Ether implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/WETH.sol)
/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol)
contract WETH is ERC20("Wrapped Ether", "WETH", 18) {
    using SafeTransferLib for address;

    event Deposit(address indexed from, uint256 amount);

    event Withdrawal(address indexed to, uint256 amount);

    function deposit() public payable virtual {
        _mint(msg.sender, msg.value);

        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) public virtual {
        _burn(msg.sender, amount);

        emit Withdrawal(msg.sender, amount);

        msg.sender.safeTransferETH(amount);
    }

    receive() external payable virtual {
        deposit();
    }
}

// File: src/interfaces/Errors.sol



/// SPDX-License-Identifier: SSPL-1.-0

/**
 * @custom:org.protocol='mevETH LST Protocol'
 * @custom:org.security='mailto:security@manifoldfinance.com'
 * @custom:org.vcs-commit=$GIT_COMMIT_SHA
 * @custom:org.vendor='CommodityStream, Inc'
 * @custom:org.schema-version="1.0"
 * @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
 * @custom:org.preferred-languages="en"
 */

pragma solidity ^0.8.19;

interface MevEthErrors {
    /// Errors
    error StakingPaused();
    error NotEnoughEth();
    error ZeroValue();
    error InvalidOperator();
    error DepositTooSmall();
    error InvalidSender();
    error PrematureStakingModuleUpdateFinalization();
    error PrematureMevEthShareVaultUpdateFinalization();
    error InvalidPendingStakingModule();
    error InvalidPendingMevEthShareVault();
    error TransferExceedsAllowance();
    error TransferFailed();
    error ZeroAddress();
    error AlreadyInitialized();
    error SendError();
    error FeesTooHigh();
    error WrongDepositAmount();
    error WrongWithdrawAmount();
    error UnAuthorizedCaller();
    error WithdrawTooSmall();
    error NotFinalised();
    error AlreadyClaimed();
    error AlreadyFinalised();
    error IndexExceedsQueueLength();
    error DepositWasFrontrun();
    error SandwichProtection();
    error NonZeroVaultBalance();
    error AlreadyDeposited();
    error IncorrectWithdrawalCredentials();
}

// File: src/interfaces/IStakingModule.sol



/// SPDX-License-Identifier: SSPL-1.-0

/**
 * @custom:org.protocol='mevETH LST Protocol'
 * @custom:org.security='mailto:security@manifoldfinance.com'
 * @custom:org.vcs-commit=$GIT_COMMIT_SHA
 * @custom:org.vendor='CommodityStream, Inc'
 * @custom:org.schema-version="1.0"
 * @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
 * @custom:org.preferred-languages="en"
 */

pragma solidity ^0.8.19;

interface IStakingModule {
    /**
     * @dev Structure for passing information about the validator deposit data.
     * @param operator - address of the operator.
     * @param pubkey - BLS public key of the validator, generated by the operator.
     * @param withdrawal_credentials - withdrawal credentials used for generating the deposit data.
     * @param signature - BLS signature of the validator, generated by the operator.
     * @param deposit_data_root - hash tree root of the deposit data, generated by the operator.
     */
    struct ValidatorData {
        address operator;
        bytes pubkey;
        bytes32 withdrawal_credentials;
        bytes signature;
        bytes32 deposit_data_root; // more efficient to be calculated off-chain
    }

    /**
     * @dev Allows users to deposit funds into the contract.
     * @param data ValidatorData calldata containing the validator's public key, withdrawal credentials, and amount of tokens to be deposited.
     * @param latestDepositRoot bytes32 containing the latest deposit root.
     */
    function deposit(ValidatorData calldata data, bytes32 latestDepositRoot) external payable;

    function validators() external view returns (uint256);

    function mevEth() external view returns (address);

    /**
     * @notice VALIDATOR_DEPOSIT_SIZE()
     *
     * This function returns the size of the validator deposit.
     *
     * @dev This function is used to determine the size of the validator deposit. It is used to ensure that validators have the correct amount of funds in order
     * to participate in the network.
     */
    function VALIDATOR_DEPOSIT_SIZE() external view returns (uint256);

    // onlyAdmin Functions
    /**
     * @notice This function is used to pay rewards to the users.
     * @dev This function is used to pay rewards to the users. It takes in a uint256 rewards parameter which is the amount of rewards to be paid.
     */
    function payRewards(uint256 rewards) external;
    /**
     * @notice This function allows a validator to withdraw their rewards from the contract.
     * @dev This function is called by a validator to withdraw their rewards from the contract. It will transfer the rewards to the validator's address.
     */
    function payValidatorWithdraw() external;
    function recoverToken(address token, address recipient, uint256 amount) external;
    /**
     * @notice record() function is used to record the data in the smart contract.
     * @dev record() function takes no parameters and returns four uint128 values.
     */
    function record() external returns (uint128, uint128, uint128, uint128);
    /**
     * @notice registerExit() allows users to exit the system.
     * @dev registerExit() is a function that allows users to exit the system. It is triggered by an external call.
     */
    function registerExit() external;

    function batchMigrate(IStakingModule.ValidatorData[] calldata batchData) external;
}

// File: src/interfaces/IERC20Burnable.sol



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

interface IERC20Burnable {
    function burnFrom(address account, uint256 amount) external;
}

// File: src/interfaces/ITinyMevEth.sol



/// SPDX-License-Identifier: SSPL-1.-0

/**
 * @custom:org.protocol='mevETH LST Protocol'
 * @custom:org.security='mailto:security@manifoldfinance.com'
 * @custom:org.vcs-commit=$GIT_COMMIT_SHA
 * @custom:org.vendor='CommodityStream, Inc'
 * @custom:org.schema-version="1.0"
 * @custom.org.encryption="manifoldfinance.com/.well-known/pgp-key.asc"
 * @custom:org.preferred-languages="en"
 */

pragma solidity ^0.8.19;

/// @title TinyMevEth
/// @notice smol interface for interacting with MevEth
interface ITinyMevEth {
    /**
     * @dev Function to grant rewards to other users.
     * @notice This function is payable and should be called with the amount of rewards to be granted.
     */
    function grantRewards() external payable;
    /**
     * @dev Function to allow a validator to withdraw funds from the contract.
     * @notice This function must be called with a validator address and a payable amount.
     */
    function grantValidatorWithdraw() external payable;
}