// File: src/PirexEth.sol



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

import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {Errors} from "./libraries/Errors.sol";
import {DataTypes} from "./libraries/DataTypes.sol";
import {IPirexFees} from "./interfaces/IPirexFees.sol";
import {PirexEthValidators} from "./PirexEthValidators.sol";

/**
 * @title  Main contract for handling interactions with pxETH
 * @notice This contract manages various interactions with pxETH, such as deposits, redemptions, and fee adjustments.
 * @dev    This contract inherits from PirexEthValidators and utilizes SafeTransferLib for ERC20 token transfers.
 * @author redactedcartel.finance
 */
contract PirexEth is PirexEthValidators {
    /**
     * @notice Smart contract uses the SafeTransferLib library for secure ERC20 token transfers.
     * @dev    The SafeTransferLib library provides enhanced safety checks and error handling for ERC20 token transfers,
     *         reducing the risk of common vulnerabilities such as reentrancy attacks. By using this library,
     *         the smart contract ensures safer and more reliable interactions with ERC20 tokens.
     */
    using SafeTransferLib for ERC20;

    /**
     * @notice Immutable reference to the Pirex fee repository and distribution contract.
     * @dev    The `pirexFees` variable holds the address of the Pirex fee repository and distribution contract (IPirexFees).
     *         This contract is responsible for managing and distributing fees collected within the Pirex ecosystem.
     *         As an immutable variable, its value is set at deployment and cannot be changed thereafter.
     */
    IPirexFees public immutable pirexFees;

    /**
     * @notice Mapping of maximum fees allowed for different operations in the contract.
     * @dev    The `maxFees` mapping associates each fee type (Deposit, Redemption, InstantRedemption) with its corresponding maximum fee percentage.
     *         For example, a value of 200000 represents a maximum fee of 20% (200000 / 1000000).
     *         Developers can access and modify these maximum fees directly through this public mapping.
     */
    mapping(DataTypes.Fees => uint32) public maxFees;

    /**
     * @notice Mapping of fees for different operations in the contract.
     * @dev    The `fees` mapping associates each fee type (Deposit, Redemption, InstantRedemption) with its corresponding fee percentage.
     *         For example, a value of 5000 represents a 0.5% fee (5000 / 1000000).
     *         Developers can access and modify these fees directly through this public mapping.
     */
    mapping(DataTypes.Fees => uint32) public fees;

    /**
     * @notice Current pause state of the contract.
     * @dev    The `paused` state variable indicates whether certain functionalities of the contract are currently paused or active.
     *         A value of 1 denotes a paused state, while 0 indicates the contract is not paused.
     */
    uint256 public paused;

    // Events
    /**
     * @notice Event emitted when ETH is deposited, minting pxETH, and optionally compounding into the vault.
     * @dev    Use this event to log details about the deposit, including the caller's address, the receiver's address, whether compounding occurred, the deposited amount, received pxETH amount, and fee amount.
     * @param  caller          address  indexed  Address of the entity initiating the deposit.
     * @param  receiver        address  indexed  Address of the receiver of the minted pxETH or apxEth.
     * @param  shouldCompound  bool     indexed  Boolean indicating whether compounding into the vault occurred.
     * @param  deposited       uint256           Amount of ETH deposited.
     * @param  receivedAmount  uint256           Amount of pxETH minted for the receiver.
     * @param  feeAmount       uint256           Amount of pxETH distributed as fees.
     */
    event Deposit(
        address indexed caller,
        address indexed receiver,
        bool indexed shouldCompound,
        uint256 deposited,
        uint256 receivedAmount,
        uint256 feeAmount
    );

    /**
     * @notice Event emitted when a redemption is initiated by burning pxETH in return for upxETH.
     * @dev    Use this event to log details about the redemption initiation, including the redeemed asset amount, post-fee amount, and the receiver's address.
     * @param  assets         uint256           Amount of pxETH burnt for the redemption.
     * @param  postFeeAmount  uint256           Amount of pxETH distributed to the receiver after deducting fees.
     * @param  receiver       address  indexed  Address of the receiver of the upxETH.
     */
    event InitiateRedemption(
        uint256 assets,
        uint256 postFeeAmount,
        address indexed receiver
    );

    /**
     * @notice Event emitted when ETH is redeemed using UpxETH.
     * @dev    Use this event to log details about the redemption, including the tokenId, redeemed asset amount, and the receiver's address.
     * @param  tokenId   uint256           Identifier for the redemption batch.
     * @param  assets    uint256           Amount of ETH redeemed.
     * @param  receiver  address  indexed  Address of the receiver of the redeemed ETH.
     */
    event RedeemWithUpxEth(
        uint256 tokenId,
        uint256 assets,
        address indexed receiver
    );

    /**
     * @notice Event emitted when pxETH is redeemed for ETH with fees.
     * @dev    Use this event to log details about pxETH redemption, including the redeemed asset amount, post-fee amount, and the receiver's address.
     * @param  assets         uint256           Amount of pxETH redeemed.
     * @param  postFeeAmount  uint256           Amount of ETH received by the receiver after deducting fees.
     * @param  _receiver      address  indexed  Address of the receiver of the redeemed ETH.
     */
    event RedeemWithPxEth(
        uint256 assets,
        uint256 postFeeAmount,
        address indexed _receiver
    );

    /**
     * @notice Event emitted when the fee amount for a specific fee type is set.
     * @dev    Use this event to log changes in the fee amount for a particular fee type, including the fee type and the new fee amount.
     * @param  f    DataTypes.Fees  indexed (Deposit, Redemption, InstantRedemption) for which the fee amount is being set.
     * @param  fee  uint32                  New fee amount for the specified fee type.
     */
    event SetFee(DataTypes.Fees indexed f, uint32 fee);

    /**
     * @notice Event emitted when the maximum fee for a specific fee type is set.
     * @dev    Use this event to log changes in the maximum fee for a particular fee type, including the fee type and the new maximum fee.
     * @param  f       DataTypes.Fees  indexed  Deposit, Redemption or InstantRedemption for which the maximum fee is being set.
     * @param  maxFee  uint32                   New maximum fee amount for the specified fee type.
     */
    event SetMaxFee(DataTypes.Fees indexed f, uint32 maxFee);

    /**
     * @notice Event emitted when the contract's pause state is toggled.
     * @dev    Use this event to log changes in the contract's pause state, including the account triggering the change and the new state.
     * @param  account  address  Address of the entity toggling the pause state.
     * @param  state    uint256  New pause state: 1 for paused, 0 for not paused.
     */
    event SetPauseState(address account, uint256 state);

    /**
     * @notice Event emitted when an emergency withdrawal occurs.
     * @dev    Use this event to log details about emergency withdrawals, including the receiver's address, the token involved, and the withdrawn amount.
     * @param  receiver address  indexed  Address of the receiver of the emergency withdrawal.
     * @param  token    address  indexed  Address of the token involved in the emergency withdrawal.
     * @param  amount   uint256           Amount withdrawn in the emergency withdrawal.
     */
    event EmergencyWithdrawal(
        address indexed receiver,
        address indexed token,
        uint256 amount
    );

    // Modifiers
    /**
     * @dev Use this modifier to check if the contract is not currently paused before allowing function execution.
     */
    modifier whenNotPaused() {
        if (paused == _PAUSED) revert Errors.Paused();
        _;
    }

    /**
     * @notice Contract constructor to initialize PirexEthValidator with necessary parameters and configurations.
     * @dev    This constructor sets up the PirexEthValidator contract, configuring key parameters and initializing state variables.
     * @param  _pxEth                      address  PxETH contract address
     * @param  _admin                      address  Admin address
     * @param  _beaconChainDepositContract address  The address of the beacon chain deposit contract
     * @param  _upxEth                     address  UpxETH address
     * @param  _depositSize                uint256  Amount of eth to stake
     * @param  _preDepositAmount           uint256  Amount of ETH for pre-deposit
     * @param  _pirexFees                  address  PirexFees contract address
     * @param  _initialDelay               uint48   Delay required to schedule the acceptance
     *                                              of an access control transfer started
     */
    constructor(
        address _pxEth,
        address _admin,
        address _beaconChainDepositContract,
        address _upxEth,
        uint256 _depositSize,
        uint256 _preDepositAmount,
        address _pirexFees,
        uint48 _initialDelay
    )
        PirexEthValidators(
            _pxEth,
            _admin,
            _beaconChainDepositContract,
            _upxEth,
            _depositSize,
            _preDepositAmount,
            _initialDelay
        )
    {
        if (_pirexFees == address(0)) revert Errors.ZeroAddress();

        pirexFees = IPirexFees(_pirexFees);
        maxFees[DataTypes.Fees.Deposit] = 200_000;
        maxFees[DataTypes.Fees.Redemption] = 200_000;
        maxFees[DataTypes.Fees.InstantRedemption] = 200_000;
        paused = _NOT_PAUSED;
    }

    /*//////////////////////////////////////////////////////////////
                            MUTATIVE FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Set fee
     * @dev    This function allows an entity with the GOVERNANCE_ROLE to set the fee amount for a specific fee type.
     * @param  f    DataTypes.Fees  Fee
     * @param  fee  uint32          Fee amount
     */
    function setFee(
        DataTypes.Fees f,
        uint32 fee
    ) external onlyRole(GOVERNANCE_ROLE) {
        if (fee > maxFees[f]) revert Errors.InvalidFee();

        fees[f] = fee;

        emit SetFee(f, fee);
    }

    /**
     * @notice Set Max fee
     * @dev    This function allows an entity with the GOVERNANCE_ROLE to set the maximum fee for a specific fee type.
     * @param  f       DataTypes.Fees  Fee
     * @param  maxFee  uint32          Max fee amount
     */
    function setMaxFee(
        DataTypes.Fees f,
        uint32 maxFee
    ) external onlyRole(GOVERNANCE_ROLE) {
        if (maxFee < fees[f] || maxFee > DENOMINATOR) revert Errors.InvalidMaxFee();

        maxFees[f] = maxFee;

        emit SetMaxFee(f, maxFee);
    }

    /**
     * @notice Toggle the contract's pause state
     * @dev    This function allows an entity with the GOVERNANCE_ROLE to toggle the contract's pause state.
     */
    function togglePauseState() external onlyRole(GOVERNANCE_ROLE) {
        paused = paused == _PAUSED ? _NOT_PAUSED : _PAUSED;

        emit SetPauseState(msg.sender, paused);
    }

    /**
     * @notice Emergency withdrawal for all ERC20 tokens (except pxETH) and ETH
     * @dev    This function should only be called under major emergency
     * @param  receiver address  Receiver address
     * @param  token    address  Token address
     * @param  amount   uint256  Token amount
     */
    function emergencyWithdraw(
        address receiver,
        address token,
        uint256 amount
    ) external onlyRole(GOVERNANCE_ROLE) onlyWhenDepositEtherPaused {
        if (paused == _NOT_PAUSED) revert Errors.NotPaused();
        if (receiver == address(0)) revert Errors.ZeroAddress();
        if (amount == 0) revert Errors.ZeroAmount();
        if (token == address(pxEth)) revert Errors.InvalidToken();

        if (token == address(0)) {
            // Update pendingDeposit when affected by emergency withdrawal
            uint256 remainingBalance = address(this).balance - amount;
            if (pendingDeposit > remainingBalance) {
                pendingDeposit = remainingBalance;
            }

            // Handle ETH withdrawal
            (bool _success, ) = payable(receiver).call{value: amount}("");
            assert(_success);
        } else {
            ERC20(token).safeTransfer(receiver, amount);
        }

        emit EmergencyWithdrawal(receiver, token, amount);
    }

    /**
     * @notice Handle pxETH minting in return for ETH deposits
     * @dev    This function handles the minting of pxETH in return for ETH deposits.
     * @param  receiver        address  Receiver of the minted pxETH or apxEth
     * @param  shouldCompound  bool     Whether to also compound into the vault
     * @return postFeeAmount   uint256  pxETH minted for the receiver
     * @return feeAmount       uint256  pxETH distributed as fees
     */
    function deposit(
        address receiver,
        bool shouldCompound
    )
        external
        payable
        whenNotPaused
        nonReentrant
        returns (uint256 postFeeAmount, uint256 feeAmount)
    {
        if (msg.value == 0) revert Errors.ZeroAmount();
        if (receiver == address(0)) revert Errors.ZeroAddress();

        // Get the pxETH amounts for the receiver and the protocol (fees)
        (postFeeAmount, feeAmount) = _computeAssetAmounts(
            DataTypes.Fees.Deposit,
            msg.value
        );

        // Mint pxETH for the receiver (or this contract if compounding) excluding fees
        _mintPxEth(shouldCompound ? address(this) : receiver, postFeeAmount);

        if (shouldCompound) {
            // Deposit pxETH excluding fees into the autocompounding vault
            // then mint shares (apxETH) for the user
            autoPxEth.deposit(postFeeAmount, receiver);
        }

        // Mint pxETH for fee distribution contract
        if (feeAmount != 0) {
            _mintPxEth(address(pirexFees), feeAmount);
        }

        // Redirect the deposit to beacon chain deposit contract
        _addPendingDeposit(msg.value);

        emit Deposit(
            msg.sender,
            receiver,
            shouldCompound,
            msg.value,
            postFeeAmount,
            feeAmount
        );
    }

    /**
     * @notice Initiate redemption by burning pxETH in return for upxETH
     * @dev    This function is used to initiate redemption by burning pxETH and receiving upxETH.
     * @param  _assets                      uint256  If caller is AutoPxEth then apxETH; pxETH otherwise.
     * @param  _receiver                    address  Receiver for upxETH.
     * @param  _shouldTriggerValidatorExit  bool     Whether the initiation should trigger voluntary exit.
     * @return postFeeAmount                uint256  pxETH burnt for the receiver.
     * @return feeAmount                    uint256  pxETH distributed as fees.
     */
    function initiateRedemption(
        uint256 _assets,
        address _receiver,
        bool _shouldTriggerValidatorExit
    )
        external
        override
        whenNotPaused
        nonReentrant
        returns (uint256 postFeeAmount, uint256 feeAmount)
    {
        if (_assets == 0) revert Errors.ZeroAmount();
        if (_receiver == address(0)) revert Errors.ZeroAddress();

        uint256 _pxEthAmount;

        if (msg.sender == address(autoPxEth)) {
            // The pxETH amount is calculated as per apxETH-ETH ratio during current block
            _pxEthAmount = autoPxEth.redeem(
                _assets,
                address(this),
                address(this)
            );
        } else {
            _pxEthAmount = _assets;
        }

        // Get the pxETH amounts for the receiver and the protocol (fees)
        (postFeeAmount, feeAmount) = _computeAssetAmounts(
            DataTypes.Fees.Redemption,
            _pxEthAmount
        );

        uint256 _requiredValidators = (pendingWithdrawal + postFeeAmount) /
            DEPOSIT_SIZE;

        if (_shouldTriggerValidatorExit && _requiredValidators == 0)
            revert Errors.NoValidatorExit();

        if (_requiredValidators > getStakingValidatorCount())
            revert Errors.NotEnoughValidators();

        emit InitiateRedemption(_pxEthAmount, postFeeAmount, _receiver);

        address _owner = msg.sender == address(autoPxEth)
            ? address(this)
            : msg.sender;

        _burnPxEth(_owner, postFeeAmount);

        if (feeAmount != 0) {
            // Allow PirexFees to distribute fees directly from sender
            pxEth.operatorApprove(_owner, address(pirexFees), feeAmount);

            // Distribute fees
            pirexFees.distributeFees(_owner, address(pxEth), feeAmount);
        }

        _initiateRedemption(
            postFeeAmount,
            _receiver,
            _shouldTriggerValidatorExit
        );
    }

    /**
     * @notice Bulk redeem back ETH using a set of upxEth identifiers
     * @dev    This function allows the bulk redemption of ETH using upxEth tokens.
     * @param  _tokenIds  uint256[]  Redeem batch identifiers
     * @param  _amounts   uint256[]  Amounts of ETH to redeem for each identifier
     * @param  _receiver  address    Address of the ETH receiver
     */
    function bulkRedeemWithUpxEth(
        uint256[] calldata _tokenIds,
        uint256[] calldata _amounts,
        address _receiver
    ) external whenNotPaused nonReentrant {
        uint256 tLen = _tokenIds.length;
        uint256 aLen = _amounts.length;

        if (tLen == 0) revert Errors.EmptyArray();
        if (tLen != aLen) revert Errors.MismatchedArrayLengths();

        for (uint256 i; i < tLen; ++i) {
            _redeemWithUpxEth(_tokenIds[i], _amounts[i], _receiver);
        }
    }

    /**
     * @notice Redeem back ETH using a single upxEth identifier
     * @dev    This function allows the redemption of ETH using upxEth tokens.
     * @param  _tokenId  uint256  Redeem batch identifier
     * @param  _assets   uint256  Amount of ETH to redeem
     * @param  _receiver  address  Address of the ETH receiver
     */
    function redeemWithUpxEth(
        uint256 _tokenId,
        uint256 _assets,
        address _receiver
    ) external whenNotPaused nonReentrant {
        _redeemWithUpxEth(_tokenId, _assets, _receiver);
    }

    /**
     * @notice Instant redeem back ETH using pxETH
     * @dev    This function burns pxETH, calculates fees, and transfers ETH to the receiver.
     * @param  _assets        uint256   Amount of pxETH to redeem.
     * @param  _receiver      address   Address of the ETH receiver.
     * @return postFeeAmount  uint256   Post-fee amount for the receiver.
     * @return feeAmount      uinit256  Fee amount sent to the PirexFees.
     */
    function instantRedeemWithPxEth(
        uint256 _assets,
        address _receiver
    )
        external
        whenNotPaused
        nonReentrant
        returns (uint256 postFeeAmount, uint256 feeAmount)
    {
        if (_assets == 0) revert Errors.ZeroAmount();
        if (_receiver == address(0)) revert Errors.ZeroAddress();

        // Get the pxETH amounts for the receiver and the protocol (fees)
        (postFeeAmount, feeAmount) = _computeAssetAmounts(
            DataTypes.Fees.InstantRedemption,
            _assets
        );

        if (postFeeAmount > buffer) revert Errors.NotEnoughBuffer();

        if (feeAmount != 0) {
            // Allow PirexFees to distribute fees directly from sender
            pxEth.operatorApprove(msg.sender, address(pirexFees), feeAmount);

            // Distribute fees
            pirexFees.distributeFees(msg.sender, address(pxEth), feeAmount);
        }

        _burnPxEth(msg.sender, postFeeAmount);
        buffer -= postFeeAmount;

        (bool _success, ) = payable(_receiver).call{value: postFeeAmount}("");
        assert(_success);

        emit RedeemWithPxEth(_assets, postFeeAmount, _receiver);
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Redeem back ETH using upxEth
     * @dev    This function allows the redemption of ETH using upxEth tokens.
     * @param  _tokenId  uint256  Redeem batch identifier
     * @param  _assets   uint256  Amount of ETH to redeem
     * @param  _receiver  address  Address of the ETH receiver
     */
    function _redeemWithUpxEth(
        uint256 _tokenId,
        uint256 _assets,
        address _receiver
    ) internal {
        if (_assets == 0) revert Errors.ZeroAmount();
        if (_receiver == address(0)) revert Errors.ZeroAddress();

        DataTypes.ValidatorStatus _validatorStatus = status[
            batchIdToValidator[_tokenId]
        ];

        if (
            _validatorStatus != DataTypes.ValidatorStatus.Dissolved &&
            _validatorStatus != DataTypes.ValidatorStatus.Slashed
        ) {
            revert Errors.StatusNotDissolvedOrSlashed();
        }

        if (outstandingRedemptions < _assets) revert Errors.NotEnoughETH();

        outstandingRedemptions -= _assets;
        upxEth.burn(msg.sender, _tokenId, _assets);

        (bool _success, ) = payable(_receiver).call{value: _assets}("");
        assert(_success);

        emit RedeemWithUpxEth(_tokenId, _assets, _receiver);
    }

    /**
     * @dev     This function calculates the post-fee asset amount and fee amount based on the specified fee type and total assets.
     * @param   f              DataTypes.Fees  representing the fee type.
     * @param   assets         uint256         Total ETH or pxETH asset amount.
     * @return  postFeeAmount  uint256         Post-fee asset amount (for mint/burn/claim/etc.).
     * @return  feeAmount      uint256         Fee amount.
     */
    function _computeAssetAmounts(
        DataTypes.Fees f,
        uint256 assets
    ) internal view returns (uint256 postFeeAmount, uint256 feeAmount) {
        feeAmount = (assets * fees[f]) / DENOMINATOR;
        postFeeAmount = assets - feeAmount;
    }
}

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



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/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: lib/solmate/src/utils/SafeTransferLib.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

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

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

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

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

        require(success, "ETH_TRANSFER_FAILED");
    }

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

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

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

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
            mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 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 the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

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

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

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 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 the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

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

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

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 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 the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

// File: src/libraries/Errors.sol



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

library Errors {
    /**
     * @dev Zero address specified
     */
    error ZeroAddress();

    /**
     * @dev Zero amount specified
     */
    error ZeroAmount();

    /**
     * @dev Invalid fee specified
     */
    error InvalidFee();

    /**
     * @dev Invalid max fee specified
     */
    error InvalidMaxFee();

    /**
     * @dev Zero multiplier used
     */
    error ZeroMultiplier();

    /**
     * @dev ETH deposit is paused
     */
    error DepositingEtherPaused();

    /**
     * @dev ETH deposit is not paused
     */
    error DepositingEtherNotPaused();

    /**
     * @dev Contract is paused
     */
    error Paused();

    /**
     * @dev Contract is not paused
     */
    error NotPaused();

    /**
     * @dev Validator not yet dissolved
     */
    error NotDissolved();

    /**
     * @dev Validator not yet withdrawable
     */
    error NotWithdrawable();

    /**
     * @dev Validator has been previously used before
     */
    error NoUsedValidator();

    /**
     * @dev Not oracle adapter
     */
    error NotOracleAdapter();

    /**
     * @dev Not reward recipient
     */
    error NotRewardRecipient();

    /**
     * @dev Exceeding max value
     */
    error ExceedsMax();

    /**
     * @dev No rewards available
     */
    error NoRewards();

    /**
     * @dev Not PirexEth
     */
    error NotPirexEth();

    /**
     * @dev Not minter
     */
    error NotMinter();

    /**
     * @dev Not burner
     */
    error NotBurner();

    /**
     * @dev Empty string
     */
    error EmptyString();

    /**
     * @dev Validator is Not Staking
     */
    error ValidatorNotStaking();

    /**
     * @dev not enough buffer
     */
    error NotEnoughBuffer();

    /**
     * @dev validator queue empty
     */
    error ValidatorQueueEmpty();

    /**
     * @dev out of bounds
     */
    error OutOfBounds();

    /**
     * @dev cannot trigger validator exit
     */
    error NoValidatorExit();

    /**
     * @dev cannot initiate redemption partially
     */
    error NoPartialInitiateRedemption();

    /**
     * @dev not enough validators
     */
    error NotEnoughValidators();

    /**
     * @dev not enough ETH
     */
    error NotEnoughETH();

    /**
     * @dev max processed count is invalid (< 1)
     */
    error InvalidMaxProcessedCount();

    /**
     * @dev fromIndex and toIndex are invalid
     */
    error InvalidIndexRanges();

    /**
     * @dev ETH is not allowed
     */
    error NoETHAllowed();

    /**
     * @dev ETH is not passed
     */
    error NoETH();

    /**
     * @dev validator status is neither dissolved nor slashed
     */
    error StatusNotDissolvedOrSlashed();

    /**
     * @dev validator status is neither withdrawable nor staking
     */
    error StatusNotWithdrawableOrStaking();

    /**
     * @dev account is not approved
     */
    error AccountNotApproved();

    /**
     * @dev invalid token specified
     */
    error InvalidToken();

    /**
     * @dev not same as deposit size
     */
    error InvalidAmount();

    /**
     * @dev contract not recognised
     */
    error UnrecorgnisedContract();

    /**
     * @dev empty array
     */
    error EmptyArray();

    /**
     * @dev arrays length mismatch
     */
    error MismatchedArrayLengths();
}

// File: src/libraries/DataTypes.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/**
 * @title DataTypes
 * @notice Library containing various data structures and enums for the PirexEth.
 * @dev This library provides data structures and enums crucial for the functionality of the Pirex protocol.
 * @author redactedcartel.finance
 */
library DataTypes {
    // Validator struct type
    struct Validator {
        // Publickey of the validator
        bytes pubKey;
        // Signature associated with the validator
        bytes signature;
        // Root hash of deposit data for the validator
        bytes32 depositDataRoot;
        // beneficiazry address to receive pxEth against preDeposit
        address receiver;
    }

    // ValidatorDeque struct type
    struct ValidatorDeque {
        // Beginning index of the validator deque
        int128 _begin;
        // End index of the validator deque
        int128 _end;
        // Mapping of validator index to Validator struct
        mapping(int128 => Validator) _validators;
    }

    // Burner Account Type
    struct BurnerAccount {
        // Address of the burner account
        address account;
        // Amount associated with the burner account
        uint256 amount;
    }

    // Configurable fees
    enum Fees {
        // Fee type for deposit
        Deposit,
        // Fee type for redemption
        Redemption,
        // Fee type for instant redemption
        InstantRedemption
    }

    // Configurable contracts
    enum Contract {
        // PxEth contract
        PxEth,
        // UpxEth contract
        UpxEth,
        // AutoPxEth contract
        AutoPxEth,
        // OracleAdapter contract
        OracleAdapter,
        // PirexEth contract
        PirexEth,
        // RewardRecipient contract
        RewardRecipient
    }

    // Validator statuses
    enum ValidatorStatus {
        // The validator is not staking and has no defined status.
        None,
        // The validator is actively participating in the staking process.
        // It could be in one of the following states: pending_initialized, pending_queued, or active_ongoing.
        Staking,
        // The validator has proceed with the withdrawal process.
        // It represents a meta state for active_exiting, exited_unslashed, and the withdrawal process being possible.
        Withdrawable,
        // The validator's status indicating that ETH is released to the pirexEthValidators
        // It represents the withdrawal_done status.
        Dissolved,
        // The validator's status indicating that it has been slashed due to misbehavior.
        // It serves as a meta state encompassing active_slashed, exited_slashed,
        // and the possibility of starting the withdrawal process (withdrawal_possible) or already completed (withdrawal_done)
        // with the release of ETH, subject to a penalty for the misbehavior.
        Slashed
    }
}

// File: src/interfaces/IPirexFees.sol



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

/**
 * @title IPirexFees
 * @notice Interface for managing fee distribution in the PirexEth.
 * @dev This interface defines functions related to the distribution of fees in the Pirex protocol.
 * @author redactedcartel.finance
 */
interface IPirexFees {
    /**
     * @notice Distributes fees from a specified source.
     * @dev This function is responsible for distributing fees in the specified token amount.
     * @param from   address Address representing the source of fees.
     * @param token  address Address of the fee token.
     * @param amount uint256 The amount of the fee token to be distributed.
     */
    function distributeFees(
        address from,
        address token,
        uint256 amount
    ) external;
}

// File: src/PirexEthValidators.sol



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

import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {AccessControlDefaultAdminRules} from "openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol";
import {UpxEth} from "./tokens/UpxEth.sol";
import {Errors} from "./libraries/Errors.sol";
import {DataTypes} from "./libraries/DataTypes.sol";
import {ValidatorQueue} from "./libraries/ValidatorQueue.sol";
import {IOracleAdapter} from "./interfaces/IOracleAdapter.sol";
import {IPirexEth} from "./interfaces/IPirexEth.sol";
import {IDepositContract} from "./interfaces/IDepositContract.sol";
import {AutoPxEth} from "./AutoPxEth.sol";
import {PxEth} from "./PxEth.sol";

/**
 * @title  PirexEthValidators
 * @notice Manages validators and deposits for the Eth2.0 deposit contract
 * @dev    This contract includes functionality for handling validator-related operations and deposits.
 * @author redactedcartel.finance
 */
abstract contract PirexEthValidators is
    ReentrancyGuard,
    AccessControlDefaultAdminRules,
    IPirexEth
{
    /**
     * @dev This library provides enhanced safety features for ERC20 token transfers, reducing the risk of common vulnerabilities.
     */
    using ValidatorQueue for DataTypes.ValidatorDeque;
    /**
     * @dev This library extends the functionality of the DataTypes.ValidatorDeque data structure to facilitate validator management.
     */
    using SafeTransferLib for ERC20;

    /**
     * @notice Denominator used for mathematical calculations.
     * @dev    This constant is used as a divisor in various mathematical calculations
     *         throughout the contract to achieve precise percentages and ratios.
     */
    uint256 internal constant DENOMINATOR = 1_000_000;

    // Roles
    /**
     * @notice The role assigned to external keepers responsible for specific protocol functions.
     * @dev    This role is assigned to external entities that are responsible for performing specific
     *         functions within the protocol, such as validator upkeep and maintenance.
     */
    bytes32 internal constant KEEPER_ROLE = keccak256("KEEPER_ROLE");

    /**
     * @notice The role assigned to governance entities responsible for managing protocol parameters.
     * @dev    This role is assigned to governance entities that have the authority to manage and
     *         update various protocol parameters, ensuring the smooth operation and evolution of the protocol.
     */
    bytes32 internal constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");

    /**
     * @notice Paused status indicator when depositing Ether is not paused.
     * @dev    This constant represents the status indicator when depositing Ether is not paused.
     *         It is used as a reference for the depositEtherPaused state variable to determine whether
     *         depositing Ether is currently allowed or paused.
     */
    uint256 internal constant _NOT_PAUSED = 1;

    /**
     * @notice Paused status indicator when depositing Ether is paused.
     * @dev    This constant represents the status indicator when depositing Ether is paused.
     *         It is used as a reference for the depositEtherPaused state variable to determine
     *         whether depositing Ether is currently allowed or paused.
     */
    uint256 internal constant _PAUSED = 2;

    /**
     * @notice The address of the external beacon chain deposit contract.
     * @dev    This variable holds the immutable address of the external beacon chain deposit contract.
     *         It is used to interact with the contract for depositing validators to the Ethereum 2.0 beacon chain.
     */
    address public immutable beaconChainDepositContract;

    /**
     * @notice The amount of Ether that a validator must deposit before being added to the initialized validator queue.
     * @dev    This variable represents the immutable pre-deposit amount required for a validator to be added to the initialized validator queue.
     *         Validators need to deposit this amount of Ether to be put in initialized validator queue.
     */
    uint256 public immutable preDepositAmount;

    /**
     * @notice The default deposit size for validators, set once during contract initialization.
     * @dev    This variable represents the immutable default deposit size for validators.
     *         It is set during the contract initialization and represents the amount of Ether a validator needs to deposit
     *         to participate in the Ethereum 2.0 staking process.
     */
    uint256 public immutable DEPOSIT_SIZE;

    /**
     * @notice The withdrawal credentials used when processing validator withdrawals.
     * @dev    This variable holds the withdrawal credentials, which are used to receive valdiator rewards
     */
    bytes public withdrawalCredentials;

    /**
     * @notice Buffer for instant withdrawals and emergency top-ups.
     * @dev    This variable represents the buffer amount,
     *         which is utilized for immediate withdrawals and emergency top-ups.
     *         It serves as a reserve to facilitate rapid withdrawals or cover unexpected events within the protocol.
     */
    uint256 public buffer;

    /**
     * @notice Maximum buffer size for instant withdrawals and emergency top-ups.
     * @dev    This variable represents the upper limit for the buffer size,
     *         determining the maximum amount that can be reserved for immediate withdrawals,
     *         and emergency top-ups in the protocol.
     */
    uint256 public maxBufferSize;

    /**
     * @notice Percentage of pxEth total supply allocated to determine the max buffer size.
     * @dev    This variable represents the percentage of the total supply of pxEth that is allocated
     *         to determine the maximum buffer size. It influences the dynamic adjustment of the buffer
     *         size based on the total supply of pxEth in the protocol.
     */
    uint256 public maxBufferSizePct;

    /**
     * @notice Maximum count of validators to be processed in a single `_deposit` call.
     * @dev    This variable determines the maximum number of validators that can be processed in a single call to the `_deposit` function.
     *         It helps control the efficiency and gas cost of the depositing process.
     */
    uint256 public maxProcessedValidatorCount = 20;

    // Pirex contracts
    /**
     * @notice The UpxEth contract responsible for managing the upxEth token.
     * @dev    This variable holds the address of the UpxEth contract,
     *         which represents pending redemption.
     */
    UpxEth public upxEth;

    /**
     * @notice The PxEth contract responsible for managing the pxEth token.
     * @dev    This variable holds the address of the PxEth contract,
     *         which represents ETH deposit made to Dinero protocol.
     */
    PxEth public pxEth;

    /**
     * @notice The AutoPxEth contract responsible for automated management of the pxEth token.
     * @dev    This variable holds the address of the AutoPxEth contract,
     *         which represents pxEth deposit to auto compounding vault.
     */
    AutoPxEth public autoPxEth;

    /**
     * @notice The OracleAdapter contract responsible for interfacing with the oracle for protocol data.
     * @dev    This variable holds the address of the OracleAdapter contract,
     *         which is used to request validator exit and update its status to dissolves or slashed.
     */
    IOracleAdapter public oracleAdapter;

    /**
     * @notice The address designated as the reward recipient for protocol incentives.
     * @dev    This variable holds the address of the entity designated to receive consensus,
     *         execution and MEV rewards.
     */
    address public rewardRecipient;

    /**
     * @notice Indicator for whether depositing Ether to the beacon chain deposit contract is paused or not.
     * @dev    This variable holds the status indicator (paused or not) for depositing Ether to the beacon chain deposit contract.
     */
    uint256 public depositEtherPaused;

    /**
     * @notice Buffer for pending deposits to be staked, r
     *         equired to be greater than or equal to multiples of DEPOSIT_SIZE, including preDepositAmount.
     * @dev    This variable holds the amount of pending deposits that are waiting to be staked.
     *         It ensures that the buffer size is sufficient for multiples of DEPOSIT_SIZE, including preDepositAmount.
     */
    uint256 public pendingDeposit;

    /**
     * @notice Queue to prioritize validator spinning on a FIFO basis.
     * @dev    This internal variable represents a deque (double-ended queue) used to prioritize validator
     *         spinning based on a First-In-First-Out (FIFO) basis.
     */
    DataTypes.ValidatorDeque internal _initializedValidators;

    /**
     * @notice Queue to prioritize the next validator to be exited when required on a FIFO basis.
     * @dev    This internal variable represents a deque (double-ended queue) used to prioritize validators
     *         for exiting based on a First-In-First-Out (FIFO) basis.
     */
    DataTypes.ValidatorDeque internal _stakingValidators;

    /**
     * @notice Buffer for withdrawals to be unstaked, required to be greater than or equal to multiples of DEPOSIT_SIZE.
     * @dev    This variable holds the amount of Ether that is pending withdrawal,
     *         and it must be greater than or equal to multiples of DEPOSIT_SIZE.
     */
    uint256 public pendingWithdrawal;

    /**
     * @notice ETH available for redemptions.
     * @dev    This variable represents the amount of Ether available for redemptions by burning upxEth.
     */
    uint256 public outstandingRedemptions;

    /**
     * @notice Batch Id for validator's voluntary exit.
     * @dev    This variable represents the batch ID for a validator's voluntary exit.
     */
    uint256 public batchId;

    /**
     * @notice End block for the ETH rewards calculation.
     * @dev    This variable represents the block number until which ETH rewards are computed.
     */
    uint256 public endBlock;

    /**
     * @notice Validator statuses, mapping from validator public key to their status.
     * @dev    This mapping tracks the status of each validator, using their public key as the identifier.
     */
    mapping(bytes => DataTypes.ValidatorStatus) public status;

    /**
     * @notice Mapping from batchId to validator public key.
     * @dev    This mapping tracks the batch ID of each unstaked validator
     */
    mapping(uint256 => bytes) public batchIdToValidator;

    /**
     * @notice Accounts designated for burning pxEth when the buffer is used for top-up and the validator is slashed.
     * @dev    This mapping identifies accounts designated for burning pxEth under specific conditions.
     */
    mapping(address => bool) public burnerAccounts;

    // Events
    /**
     * @notice Emitted when a validator is deposited, indicating the addition of a new validator.
     * @dev    This event is triggered when a user deposits ETH for staking, creating a new validator.
     *         Validators play a crucial role in the proof-of-stake consensus mechanism and contribute
     *         to the security and functionality of the network. The `pubKey` parameter represents the public key of the deposited validator.
     * @param pubKey bytes Public key of the deposited validator.
     */
    event ValidatorDeposit(bytes pubKey);

    /**
     * @notice Emitted when a contract address is set.
     * @dev    This event is triggered when a contract address is set for a specific contract type.
     * @param  c                DataTypes.Contract  The type of the contract being set.
     * @param  contractAddress  address             The address of the contract being set.
     */
    event SetContract(DataTypes.Contract indexed c, address contractAddress);

    /**
     * @notice Emitted when the status of depositing Ether is paused or unpaused.
     * @dev    This event is triggered when there is a change in the status of depositing Ether.
     *         The `newStatus` parameter indicates whether depositing Ether is currently paused or unpaused.
     *         Pausing depositing Ether can be useful in certain scenarios, such as during contract upgrades or emergency situations.
     * @param  newStatus  uint256  The new status indicating whether depositing Ether is paused or unpaused.
     */
    event DepositEtherPaused(uint256 newStatus);

    /**
     * @notice Emitted when harvesting rewards.
     * @dev    This event is triggered when rewards are harvested. The `amount` parameter indicates the amount of rewards harvested,
     *         and the `endBlock` parameter specifies the block until which ETH rewards are computed.
     * @param  amount    uint256  The amount of rewards harvested.
     * @param  endBlock  uint256  The block until which ETH rewards are computed.
     */
    event Harvest(uint256 amount, uint256 endBlock);

    /**
     * @notice Emitted when the max buffer size percentage is set.
     * @dev    This event is triggered when the max buffer size percentage is updated.
     *         The `pct` parameter represents the new max buffer size percentage.
     * @param  pct  uint256  The new max buffer size percentage.
     */
    event SetMaxBufferSizePct(uint256 pct);

    /**
     * @notice Emitted when a burner account is approved.
     * @dev    This event is triggered when a burner account is approved.
     *         The `account` parameter represents the approved burner account.
     * @param  account  address  The approved burner account.
     */
    event ApproveBurnerAccount(address indexed account);

    /**
     * @notice Emitted when a burner account is revoked.
     * @dev    This event is triggered when a burner account is revoked.
     *         The `account` parameter represents the revoked burner account.
     * @param  account  address  The revoked burner account.
     */
    event RevokeBurnerAccount(address indexed account);

    /**
     * @notice Emitted when a validator is dissolved.
     * @dev    This event is triggered when a validator is dissolved, indicating the update of the validator state.
     * @param  pubKey  bytes  Public key of the dissolved validator.
     */
    event DissolveValidator(bytes pubKey);

    /**
     * @notice Emitted when a validator is slashed.
     * @dev    This event is triggered when a validator is slashed, indicating the slashing action and its details.
     * @param  pubKey          bytes    Public key of the slashed validator.
     * @param  useBuffer       bool     Indicates whether a buffer is used during slashing.
     * @param  releasedAmount  uint256  Amount released from the Beacon chain.
     * @param  penalty         uint256  Penalty amount.
     */
    event SlashValidator(
        bytes pubKey,
        bool useBuffer,
        uint256 releasedAmount,
        uint256 penalty
    );

    /**
     * @notice Emitted when a validator's stake is topped up.
     * @dev    This event is triggered when a validator's stake is topped up, indicating the top-up action and its details.
     * @param  pubKey       bytes    Public key of the topped-up validator.
     * @param  useBuffer    bool     Indicates whether a buffer is used during topping up.
     * @param  topUpAmount  uint256  Amount topped up.
     */
    event TopUp(bytes pubKey, bool useBuffer, uint256 topUpAmount);

    /**
     * @notice Emitted when the maximum processed validator count is set.
     * @dev    This event is triggered when the maximum count of processed validators is set, indicating a change in the processing limit.
     * @param  count  uint256  The new maximum count of processed validators.
     */
    event SetMaxProcessedValidatorCount(uint256 count);

    /**
     * @notice Emitted when the max buffer size is updated.
     * @dev    This event is triggered when max buffer size is updated
     * @param  maxBufferSize  uint256  The updated maximum buffer size.
     */
    event UpdateMaxBufferSize(uint256 maxBufferSize);

    /**
     * @notice Emitted when the withdrawal credentials are set.
     * @dev    This event is triggered when the withdrawal credentials are updated, indicating a change in the credentials used for validator withdrawals.
     * @param  withdrawalCredentials  bytes  The new withdrawal credentials.
     */
    event SetWithdrawCredentials(bytes withdrawalCredentials);

    // Modifiers
    /**
     * @dev Reverts if the sender is not the specified reward recipient. Used to control access to functions that
     *      are intended for the designated recipient of rewards.
     */
    modifier onlyRewardRecipient() {
        if (msg.sender != rewardRecipient) revert Errors.NotRewardRecipient();
        _;
    }

    /**
     * @dev Reverts if depositing Ether is not paused. Used to control access to functions that should only be
     *      callable when depositing Ether is in a paused state.
     */
    modifier onlyWhenDepositEtherPaused() {
        if (depositEtherPaused == _NOT_PAUSED)
            revert Errors.DepositingEtherNotPaused();
        _;
    }

    /*//////////////////////////////////////////////////////////////
                        CONSTRUCTOR/INITIALIZATION LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Initializes the PirexEthValidators contract.
     * @dev    Initializes the contract with the provided parameters and sets up the initial state.
     * @param  _pxEth                      address  PxETH contract address
     * @param  _admin                      address  Admin address
     * @param  _beaconChainDepositContract address  The address of the deposit precompile
     * @param  _upxEth                     address  UpxETH address
     * @param  _depositSize                uint256  Amount of ETH to stake
     * @param  _preDepositAmount           uint256  Amount of ETH for pre-deposit
     * @param  _initialDelay               uint48   Delay required to schedule the acceptance
     *                                              of an access control transfer started
     */
    constructor(
        address _pxEth,
        address _admin,
        address _beaconChainDepositContract,
        address _upxEth,
        uint256 _depositSize,
        uint256 _preDepositAmount,
        uint48 _initialDelay
    ) AccessControlDefaultAdminRules(_initialDelay, _admin) {
        if (_pxEth == address(0)) revert Errors.ZeroAddress();
        if (_beaconChainDepositContract == address(0))
            revert Errors.ZeroAddress();
        if (_upxEth == address(0)) revert Errors.ZeroAddress();
        if (_depositSize < 1 ether && _depositSize % 1 gwei != 0)
            revert Errors.ZeroMultiplier();
        if (
            _preDepositAmount > _depositSize ||
            _preDepositAmount < 1 ether ||
            _preDepositAmount % 1 gwei != 0
        ) revert Errors.ZeroMultiplier();

        pxEth = PxEth(_pxEth);
        DEPOSIT_SIZE = _depositSize;
        beaconChainDepositContract = _beaconChainDepositContract;
        preDepositAmount = _preDepositAmount;
        upxEth = UpxEth(_upxEth);
        depositEtherPaused = _NOT_PAUSED;
    }

    /*//////////////////////////////////////////////////////////////
                                VIEW
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Get the number of initialized validators
     * @dev    Returns the count of validators that are ready to be staked.
     * @return uint256 count of validators ready to be staked
     */
    function getInitializedValidatorCount() external view returns (uint256) {
        return _initializedValidators.count();
    }

    /**
     * @notice Get the number of staked validators
     * @dev    Returns the count of validators with staking status.
     * @return uint256 count of validators with staking status
     */
    function getStakingValidatorCount() public view returns (uint256) {
        return _stakingValidators.count();
    }

    /**
     * @notice Get the initialized validator info at the specified index
     * @dev    Returns the details of the initialized validator at the given index.
     * @param  _i  uint256  Index
     * @return     bytes    Public key
     * @return     bytes    Withdrawal credentials
     * @return     bytes    Signature
     * @return     bytes32  Deposit data root hash
     * @return     address  pxETH receiver
     */
    function getInitializedValidatorAt(
        uint256 _i
    )
        external
        view
        returns (bytes memory, bytes memory, bytes memory, bytes32, address)
    {
        return _initializedValidators.get(withdrawalCredentials, _i);
    }

    /**
     * @notice Get the staking validator info at the specified index
     * @dev    Returns the details of the staking validator at the given index.
     * @param  _i  uint256  Index
     * @return     bytes    Public key
     * @return     bytes    Withdrawal credentials
     * @return     bytes    Signature
     * @return     bytes32  Deposit data root hash
     * @return     address  pxETH receiver
     */
    function getStakingValidatorAt(
        uint256 _i
    )
        external
        view
        returns (bytes memory, bytes memory, bytes memory, bytes32, address)
    {
        return _stakingValidators.get(withdrawalCredentials, _i);
    }

    /*//////////////////////////////////////////////////////////////
                        RESTRICTED FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Set a contract address
     * @dev    Allows the governance role to set the address for a contract in the system.
     * @param  _contract        DataTypes.Contract  Contract
     * @param  contractAddress  address             Contract address
     */
    function setContract(
        DataTypes.Contract _contract,
        address contractAddress
    ) external onlyRole(GOVERNANCE_ROLE) {
        if (contractAddress == address(0)) revert Errors.ZeroAddress();

        emit SetContract(_contract, contractAddress);

        if (_contract == DataTypes.Contract.UpxEth) {
            upxEth = UpxEth(contractAddress);
        } else if (_contract == DataTypes.Contract.PxEth) {
            pxEth = PxEth(contractAddress);
        } else if (_contract == DataTypes.Contract.AutoPxEth) {
            ERC20 pxEthERC20 = ERC20(address(pxEth));
            address oldVault = address(autoPxEth);

            if (oldVault != address(0)) {
                pxEthERC20.safeApprove(oldVault, 0);
            }

            autoPxEth = AutoPxEth(contractAddress);
            pxEthERC20.safeApprove(address(autoPxEth), type(uint256).max);
        } else if (_contract == DataTypes.Contract.OracleAdapter) {
            oracleAdapter = IOracleAdapter(contractAddress);
        } else if (_contract == DataTypes.Contract.RewardRecipient) {
            rewardRecipient = contractAddress;
            withdrawalCredentials = abi.encodePacked(
                bytes1(0x01),
                bytes11(0x0),
                contractAddress
            );

            emit SetWithdrawCredentials(withdrawalCredentials);
        } else {
            revert Errors.UnrecorgnisedContract();
        }
    }

    /**
     * @notice Set the percentage that will be applied to total supply of pxEth to determine maxBufferSize
     * @dev    Allows the governance role to set the percentage of the total supply of pxEth that will be used as maxBufferSize.
     * @param  _pct  uint256  Max buffer size percentage
     */
    function setMaxBufferSizePct(
        uint256 _pct
    ) external onlyRole(GOVERNANCE_ROLE) {
        if (_pct > DENOMINATOR) {
            revert Errors.ExceedsMax();
        }

        maxBufferSizePct = _pct;

        emit SetMaxBufferSizePct(_pct);
    }

    /**
     * @notice Set the maximum count of validators to be processed in a single _deposit call
     * @dev    Only the role with the GOVERNANCE_ROLE can execute this function.
     * @param _count  uint256  Maximum count of validators to be processed
     */
    function setMaxProcessedValidatorCount(
        uint256 _count
    ) external onlyRole(GOVERNANCE_ROLE) {
        if (_count == 0) {
            revert Errors.InvalidMaxProcessedCount();
        }

        maxProcessedValidatorCount = _count;

        emit SetMaxProcessedValidatorCount(_count);
    }

    /**
     * @notice Toggle the ability to deposit ETH to validators
     * @dev    Only the role with the GOVERNANCE_ROLE can execute this function.
     */
    function togglePauseDepositEther() external onlyRole(GOVERNANCE_ROLE) {
        depositEtherPaused = depositEtherPaused == _NOT_PAUSED
            ? _PAUSED
            : _NOT_PAUSED;

        emit DepositEtherPaused(depositEtherPaused);
    }

    /**
     * @notice Approve or revoke addresses as burner accounts
     * @dev    Only the role with the GOVERNANCE_ROLE can execute this function.
     * @param _accounts  address[]  An array of addresses to be approved or revoked as burner accounts.
     * @param _state     bool       A boolean indicating whether to approve (true) or revoke (false) the burner account state.
     */
    function toggleBurnerAccounts(
        address[] calldata _accounts,
        bool _state
    ) external onlyRole(GOVERNANCE_ROLE) {
        uint256 _len = _accounts.length;

        for (uint256 _i; _i < _len; ) {
            address account = _accounts[_i];

            burnerAccounts[account] = _state;

            if (_state) {
                emit ApproveBurnerAccount(account);
            } else {
                emit RevokeBurnerAccount(account);
            }

            unchecked {
                ++_i;
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                            MUTATIVE FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Update validator to Dissolve once Oracle confirms ETH release
     * @dev    Only the reward recipient can initiate the dissolution process.
     * @param  _pubKey  bytes  The public key of the validator to be dissolved.
     */
    function dissolveValidator(
        bytes calldata _pubKey
    ) external payable override onlyRewardRecipient {
        uint256 _amount = msg.value;
        if (_amount != DEPOSIT_SIZE) revert Errors.InvalidAmount();
        if (status[_pubKey] != DataTypes.ValidatorStatus.Withdrawable)
            revert Errors.NotWithdrawable();

        status[_pubKey] = DataTypes.ValidatorStatus.Dissolved;

        outstandingRedemptions += _amount;

        emit DissolveValidator(_pubKey);
    }

    /**
     * @notice Update validator state to be slashed
     * @dev    Only the reward recipient can initiate the slashing process.
     * @param  _pubKey          bytes                      The public key of the validator to be slashed.
     * @param  _removeIndex     uint256                    Index of the validator to be slashed.
     * @param  _amount          uint256                    ETH amount released from the Beacon chain.
     * @param  _unordered       bool                       Whether to remove from the staking validator queue in order or not.
     * @param  _useBuffer       bool                       Whether to use the buffer to compensate for the loss.
     * @param  _burnerAccounts  DataTypes.BurnerAccount[]  Burner accounts providing additional compensation.
     */
    function slashValidator(
        bytes calldata _pubKey,
        uint256 _removeIndex,
        uint256 _amount,
        bool _unordered,
        bool _useBuffer,
        DataTypes.BurnerAccount[] calldata _burnerAccounts
    ) external payable override onlyRewardRecipient {
        uint256 _ethAmount = msg.value;
        uint256 _defaultDepositSize = DEPOSIT_SIZE;
        DataTypes.ValidatorStatus _status = status[_pubKey];

        if (
            _status != DataTypes.ValidatorStatus.Staking &&
            _status != DataTypes.ValidatorStatus.Withdrawable
        ) revert Errors.StatusNotWithdrawableOrStaking();

        if (_useBuffer) {
            _updateBuffer(_defaultDepositSize - _ethAmount, _burnerAccounts);
        } else if (_ethAmount != _defaultDepositSize) {
            revert Errors.InvalidAmount();
        }

        // It is possible that validator can be slashed while exiting
        if (_status == DataTypes.ValidatorStatus.Staking) {
            bytes memory _removedPubKey;

            if (!_unordered) {
                _removedPubKey = _stakingValidators.removeOrdered(_removeIndex);
            } else {
                _removedPubKey = _stakingValidators.removeUnordered(
                    _removeIndex
                );
            }

            assert(keccak256(_pubKey) == keccak256(_removedPubKey));

            _addPendingDeposit(_defaultDepositSize);
        } else {
            outstandingRedemptions += _defaultDepositSize;
        }
        status[_pubKey] = DataTypes.ValidatorStatus.Slashed;

        emit SlashValidator(
            _pubKey,
            _useBuffer,
            _amount,
            DEPOSIT_SIZE - _amount
        );
    }

    /**
     * @notice Add multiple synced validators in the queue to be ready for staking.
     * @dev    Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
     * @param  _validators  DataTypes.Validator[]  An array of validator details (public key, withdrawal credentials, etc.).
     */
    function addInitializedValidators(
        DataTypes.Validator[] memory _validators
    ) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
        uint256 _arrayLength = _validators.length;
        for (uint256 _i; _i < _arrayLength; ) {
            if (
                status[_validators[_i].pubKey] != DataTypes.ValidatorStatus.None
            ) revert Errors.NoUsedValidator();

            _initializedValidators.add(_validators[_i], withdrawalCredentials);

            unchecked {
                ++_i;
            }
        }
    }

    /**
     * @notice Swap initialized validators specified by the indexes.
     * @dev    Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
     * @param  _fromIndex  uint256  The index of the validator to be swapped from.
     * @param  _toIndex    uint256  The index of the validator to be swapped to.
     */
    function swapInitializedValidator(
        uint256 _fromIndex,
        uint256 _toIndex
    ) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
        _initializedValidators.swap(_fromIndex, _toIndex);
    }

    /**
     * @notice Pop initialized validators from the queue.
     * @dev    Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
     * @param  _times  uint256  The count of pop operations to be performed.
     */
    function popInitializedValidator(
        uint256 _times
    ) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
        _initializedValidators.pop(_times);
    }

    /**
     * @notice Remove an initialized validator from the queue.
     * @dev    Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
     * @param  _pubKey       bytes    The public key of the validator to be removed.
     * @param  _removeIndex  uint256  The index of the validator to be removed.
     * @param  _unordered    bool     A flag indicating whether removal should be unordered (true) or ordered (false).
     */
    function removeInitializedValidator(
        bytes calldata _pubKey,
        uint256 _removeIndex,
        bool _unordered
    ) external onlyWhenDepositEtherPaused onlyRole(GOVERNANCE_ROLE) {
        bytes memory _removedPubKey;

        if (_unordered) {
            _removedPubKey = _initializedValidators.removeUnordered(
                _removeIndex
            );
        } else {
            _removedPubKey = _initializedValidators.removeOrdered(_removeIndex);
        }

        assert(keccak256(_removedPubKey) == keccak256(_pubKey));
    }

    /**
     * @notice Clear all initialized validators from the queue.
     * @dev    Only callable when depositing Ether is paused and by a user with the GOVERNANCE_ROLE.
     */
    function clearInitializedValidator()
        external
        onlyWhenDepositEtherPaused
        onlyRole(GOVERNANCE_ROLE)
    {
        _initializedValidators.clear();
    }

    /**
     * @notice Trigger a privileged deposit to the ETH 2.0 deposit contract.
     * @dev    Only callable by a user with the KEEPER_ROLE and ensures that depositing Ether is not paused.
     *         This function initiates the deposit process to the ETH 2.0 deposit contract.
     */
    function depositPrivileged() external nonReentrant onlyRole(KEEPER_ROLE) {
        // Initial pause check
        if (depositEtherPaused == _PAUSED)
            revert Errors.DepositingEtherPaused();

        _deposit();
    }

    /**
     * @notice Top up ETH to a staking validator if the current balance drops below the effective balance.
     * @dev    Only callable by a user with the KEEPER_ROLE.
     * @param  _pubKey           bytes                      Validator public key.
     * @param  _signature        bytes                      A BLS12-381 signature.
     * @param  _depositDataRoot  bytes32                    The SHA-256 hash of the SSZ-encoded DepositData object.
     * @param  _topUpAmount      uint256                    Top-up amount in ETH.
     * @param  _useBuffer        bool                       Whether to use a buffer to compensate for the loss.
     * @param  _burnerAccounts   DataTypes.BurnerAccount[]  Array of burner accounts.
     */
    function topUpStake(
        bytes calldata _pubKey,
        bytes calldata _signature,
        bytes32 _depositDataRoot,
        uint256 _topUpAmount,
        bool _useBuffer,
        DataTypes.BurnerAccount[] calldata _burnerAccounts
    ) external payable nonReentrant onlyRole(KEEPER_ROLE) {
        if (status[_pubKey] != DataTypes.ValidatorStatus.Staking)
            revert Errors.ValidatorNotStaking();

        if (_useBuffer) {
            if (msg.value > 0) {
                revert Errors.NoETHAllowed();
            }
            _updateBuffer(_topUpAmount, _burnerAccounts);
        } else if (msg.value != _topUpAmount) {
            revert Errors.NoETH();
        }

        (bool success, ) = beaconChainDepositContract.call{value: _topUpAmount}(
            abi.encodeCall(
                IDepositContract.deposit,
                (_pubKey, withdrawalCredentials, _signature, _depositDataRoot)
            )
        );

        assert(success);

        emit TopUp(_pubKey, _useBuffer, _topUpAmount);
    }

    /**
     * @notice Harvest and mint staking rewards when available.
     * @dev    Only callable by the reward recipient.
     * @param  _endBlock  uint256  Block until which ETH rewards are computed.
     */
    function harvest(
        uint256 _endBlock
    ) external payable override onlyRewardRecipient {
        if (msg.value != 0) {
            // update end block
            endBlock = _endBlock;

            // Mint pxETH directly for the vault
            _mintPxEth(address(autoPxEth), msg.value);

            // Update rewards tracking with the newly added rewards
            autoPxEth.notifyRewardAmount();

            // Direct the excess balance for pending deposit
            _addPendingDeposit(msg.value);

            emit Harvest(msg.value, _endBlock);
        }
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev   Mints the specified amount of pxETH and updates the maximum buffer size.
     * @param _account  address  The address to which pxETH will be minted.
     * @param _amount   uint256  The amount of pxETH to be minted.
     */
    function _mintPxEth(address _account, uint256 _amount) internal {
        pxEth.mint(_account, _amount);
        uint256 _maxBufferSize = (pxEth.totalSupply() * maxBufferSizePct) /
            DENOMINATOR;
        maxBufferSize = _maxBufferSize;
        emit UpdateMaxBufferSize(_maxBufferSize);
    }

    /**
     * @dev   Burns the specified amount of pxETH from the given account and updates the maximum buffer size.
     * @param _account  address  The address from which pxETH will be burned.
     * @param _amount   uint256  The amount of pxETH to be burned.
     */
    function _burnPxEth(address _account, uint256 _amount) internal {
        pxEth.burn(_account, _amount);
        uint256 _maxBufferSize = (pxEth.totalSupply() * maxBufferSizePct) /
            DENOMINATOR;
        maxBufferSize = _maxBufferSize;
        emit UpdateMaxBufferSize(_maxBufferSize);
    }

    /**
     * @dev Processes the deposit of validators, taking into account the maximum processed validator count,
     *      the remaining deposit amount, and the status of initialized validators. It iterates through initialized
     *      validators, deposits them into the Beacon chain, mints pxETH if needed, and updates the validator status.
     */
    function _deposit() internal {
        uint256 remainingCount = maxProcessedValidatorCount;
        uint256 _remainingdepositAmount = DEPOSIT_SIZE - preDepositAmount;

        while (
            _initializedValidators.count() != 0 &&
            pendingDeposit >= _remainingdepositAmount &&
            remainingCount > 0
        ) {
            // Get validator information
            (
                bytes memory _pubKey,
                bytes memory _withdrawalCredentials,
                bytes memory _signature,
                bytes32 _depositDataRoot,
                address _receiver
            ) = _initializedValidators.getNext(withdrawalCredentials);

            // Make sure the validator hasn't been deposited into already
            // to prevent sending an extra eth equal to `_remainingdepositAmount`
            // until withdrawals are allowed
            if (status[_pubKey] != DataTypes.ValidatorStatus.None)
                revert Errors.NoUsedValidator();

            (bool success, ) = beaconChainDepositContract.call{
                value: _remainingdepositAmount
            }(
                abi.encodeCall(
                    IDepositContract.deposit,
                    (
                        _pubKey,
                        _withdrawalCredentials,
                        _signature,
                        _depositDataRoot
                    )
                )
            );

            assert(success);

            pendingDeposit -= _remainingdepositAmount;

            if (preDepositAmount != 0) {
                _mintPxEth(_receiver, preDepositAmount);
            }

            unchecked {
                --remainingCount;
            }

            status[_pubKey] = DataTypes.ValidatorStatus.Staking;

            _stakingValidators.add(
                DataTypes.Validator(
                    _pubKey,
                    _signature,
                    _depositDataRoot,
                    _receiver
                ),
                _withdrawalCredentials
            );

            emit ValidatorDeposit(_pubKey);
        }
    }

    /**
     * @dev   Adds the specified amount to the pending deposit, considering the available buffer space and deposit pause status.
     *        If the buffer space is available, it may be fully or partially utilized. The method then checks if depositing
     *        ETH is not paused and spins up a validator if conditions are met.
     * @param _amount  uint256  The amount of ETH to be added to the pending deposit.
     */
    function _addPendingDeposit(uint256 _amount) internal virtual {
        uint256 _remainingBufferSpace = (
            maxBufferSize > buffer ? maxBufferSize - buffer : 0
        );
        uint256 _remainingAmount = _amount;

        if (_remainingBufferSpace != 0) {
            bool _canBufferSpaceFullyUtilized = _remainingBufferSpace <=
                _remainingAmount;
            buffer += _canBufferSpaceFullyUtilized
                ? _remainingBufferSpace
                : _remainingAmount;
            _remainingAmount -= _canBufferSpaceFullyUtilized
                ? _remainingBufferSpace
                : _remainingAmount;
        }

        pendingDeposit += _remainingAmount;

        if (depositEtherPaused == _NOT_PAUSED) {
            // Spin up a validator when possible
            _deposit();
        }
    }

    /**
     * @dev   Initiates the redemption process by adding the specified amount of pxETH to the pending withdrawal.
     *        Iteratively processes pending withdrawals in multiples of DEPOSIT_SIZE, triggering validator exits, updating
     *        batch information, and changing validator statuses accordingly. The process continues until the remaining
     *        pending withdrawal is less than DEPOSIT_SIZE. If `_shouldTriggerValidatorExit` is true and there's remaining
     *        pxETH after the redemption process, the function reverts, preventing partial initiation of redemption.
     * @param _pxEthAmount                 uint256  The amount of pxETH to be redeemed.
     * @param _receiver                    address  The receiver address for upxETH.
     * @param _shouldTriggerValidatorExit  bool     Whether to initiate partial redemption with a validator exit or not.
     */
    function _initiateRedemption(
        uint256 _pxEthAmount,
        address _receiver,
        bool _shouldTriggerValidatorExit
    ) internal {
        pendingWithdrawal += _pxEthAmount;

        while (pendingWithdrawal / DEPOSIT_SIZE != 0) {
            uint256 _allocationPossible = DEPOSIT_SIZE +
                _pxEthAmount -
                pendingWithdrawal;

            upxEth.mint(_receiver, batchId, _allocationPossible, "");

            (bytes memory _pubKey, , , , ) = _stakingValidators.getNext(
                withdrawalCredentials
            );

            pendingWithdrawal -= DEPOSIT_SIZE;
            _pxEthAmount -= _allocationPossible;

            oracleAdapter.requestVoluntaryExit(_pubKey);

            batchIdToValidator[batchId++] = _pubKey;
            status[_pubKey] = DataTypes.ValidatorStatus.Withdrawable;
        }

        if (_shouldTriggerValidatorExit && _pxEthAmount > 0)
            revert Errors.NoPartialInitiateRedemption();

        if (_pxEthAmount > 0) {
            upxEth.mint(_receiver, batchId, _pxEthAmount, "");
        }
    }

    /**
     * @dev   Checks if the contract has enough buffer to cover the specified amount. Iterates through the provided
     *        `_burnerAccounts`, verifies each account's approval status, burns the corresponding amount of pxETH, and
     *        updates the buffer accordingly. Reverts if there is insufficient buffer, if an account is not approved, or
     *        if the sum of burned amounts does not match the specified amount.
     * @param _amount          uint256                    The amount to be updated in the buffer.
     * @param _burnerAccounts  DataTypes.BurnerAccount[]  An array of burner account details (account and amount).
     */
    function _updateBuffer(
        uint256 _amount,
        DataTypes.BurnerAccount[] calldata _burnerAccounts
    ) private {
        if (buffer < _amount) {
            revert Errors.NotEnoughBuffer();
        }
        uint256 _len = _burnerAccounts.length;
        uint256 _sum;

        for (uint256 _i; _i < _len; ) {
            if (!burnerAccounts[_burnerAccounts[_i].account])
                revert Errors.AccountNotApproved();

            _sum += _burnerAccounts[_i].amount;

            _burnPxEth(_burnerAccounts[_i].account, _burnerAccounts[_i].amount);

            unchecked {
                ++_i;
            }
        }

        assert(_sum == _amount);
        buffer -= _amount;
    }
}

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



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

// File: lib/openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControlDefaultAdminRules.sol)

pragma solidity ^0.8.0;

import "./AccessControl.sol";
import "./IAccessControlDefaultAdminRules.sol";
import "../utils/math/SafeCast.sol";
import "../interfaces/IERC5313.sol";

/**
 * @dev Extension of {AccessControl} that allows specifying special rules to manage
 * the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions
 * over other roles that may potentially have privileged rights in the system.
 *
 * If a specific role doesn't have an admin role assigned, the holder of the
 * `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it.
 *
 * This contract implements the following risk mitigations on top of {AccessControl}:
 *
 * * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced.
 * * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account.
 * * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted.
 * * The delay can be changed by scheduling, see {changeDefaultAdminDelay}.
 * * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`.
 *
 * Example usage:
 *
 * ```solidity
 * contract MyToken is AccessControlDefaultAdminRules {
 *   constructor() AccessControlDefaultAdminRules(
 *     3 days,
 *     msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder
 *    ) {}
 * }
 * ```
 *
 * _Available since v4.9._
 */
abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRules, IERC5313, AccessControl {
    // pending admin pair read/written together frequently
    address private _pendingDefaultAdmin;
    uint48 private _pendingDefaultAdminSchedule; // 0 == unset

    uint48 private _currentDelay;
    address private _currentDefaultAdmin;

    // pending delay pair read/written together frequently
    uint48 private _pendingDelay;
    uint48 private _pendingDelaySchedule; // 0 == unset

    /**
     * @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address.
     */
    constructor(uint48 initialDelay, address initialDefaultAdmin) {
        require(initialDefaultAdmin != address(0), "AccessControl: 0 default admin");
        _currentDelay = initialDelay;
        _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin);
    }

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

    /**
     * @dev See {IERC5313-owner}.
     */
    function owner() public view virtual returns (address) {
        return defaultAdmin();
    }

    ///
    /// Override AccessControl role management
    ///

    /**
     * @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
     */
    function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
        require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly grant default admin role");
        super.grantRole(role, account);
    }

    /**
     * @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`.
     */
    function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
        require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly revoke default admin role");
        super.revokeRole(role, account);
    }

    /**
     * @dev See {AccessControl-renounceRole}.
     *
     * For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling
     * {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule
     * has also passed when calling this function.
     *
     * After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions.
     *
     * NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin},
     * thereby disabling any functionality that is only available for it, and the possibility of reassigning a
     * non-administrated role.
     */
    function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
        if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
            (address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin();
            require(
                newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule),
                "AccessControl: only can renounce in two delayed steps"
            );
            delete _pendingDefaultAdminSchedule;
        }
        super.renounceRole(role, account);
    }

    /**
     * @dev See {AccessControl-_grantRole}.
     *
     * For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the
     * role has been previously renounced.
     *
     * NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE`
     * assignable again. Make sure to guarantee this is the expected behavior in your implementation.
     */
    function _grantRole(bytes32 role, address account) internal virtual override {
        if (role == DEFAULT_ADMIN_ROLE) {
            require(defaultAdmin() == address(0), "AccessControl: default admin already granted");
            _currentDefaultAdmin = account;
        }
        super._grantRole(role, account);
    }

    /**
     * @dev See {AccessControl-_revokeRole}.
     */
    function _revokeRole(bytes32 role, address account) internal virtual override {
        if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) {
            delete _currentDefaultAdmin;
        }
        super._revokeRole(role, account);
    }

    /**
     * @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override {
        require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't violate default admin rules");
        super._setRoleAdmin(role, adminRole);
    }

    ///
    /// AccessControlDefaultAdminRules accessors
    ///

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function defaultAdmin() public view virtual returns (address) {
        return _currentDefaultAdmin;
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) {
        return (_pendingDefaultAdmin, _pendingDefaultAdminSchedule);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function defaultAdminDelay() public view virtual returns (uint48) {
        uint48 schedule = _pendingDelaySchedule;
        return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay;
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) {
        schedule = _pendingDelaySchedule;
        return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? (_pendingDelay, schedule) : (0, 0);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) {
        return 5 days;
    }

    ///
    /// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin
    ///

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _beginDefaultAdminTransfer(newAdmin);
    }

    /**
     * @dev See {beginDefaultAdminTransfer}.
     *
     * Internal function without access restriction.
     */
    function _beginDefaultAdminTransfer(address newAdmin) internal virtual {
        uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay();
        _setPendingDefaultAdmin(newAdmin, newSchedule);
        emit DefaultAdminTransferScheduled(newAdmin, newSchedule);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _cancelDefaultAdminTransfer();
    }

    /**
     * @dev See {cancelDefaultAdminTransfer}.
     *
     * Internal function without access restriction.
     */
    function _cancelDefaultAdminTransfer() internal virtual {
        _setPendingDefaultAdmin(address(0), 0);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function acceptDefaultAdminTransfer() public virtual {
        (address newDefaultAdmin, ) = pendingDefaultAdmin();
        require(_msgSender() == newDefaultAdmin, "AccessControl: pending admin must accept");
        _acceptDefaultAdminTransfer();
    }

    /**
     * @dev See {acceptDefaultAdminTransfer}.
     *
     * Internal function without access restriction.
     */
    function _acceptDefaultAdminTransfer() internal virtual {
        (address newAdmin, uint48 schedule) = pendingDefaultAdmin();
        require(_isScheduleSet(schedule) && _hasSchedulePassed(schedule), "AccessControl: transfer delay not passed");
        _revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin());
        _grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
        delete _pendingDefaultAdmin;
        delete _pendingDefaultAdminSchedule;
    }

    ///
    /// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay
    ///

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _changeDefaultAdminDelay(newDelay);
    }

    /**
     * @dev See {changeDefaultAdminDelay}.
     *
     * Internal function without access restriction.
     */
    function _changeDefaultAdminDelay(uint48 newDelay) internal virtual {
        uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay);
        _setPendingDelay(newDelay, newSchedule);
        emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule);
    }

    /**
     * @inheritdoc IAccessControlDefaultAdminRules
     */
    function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        _rollbackDefaultAdminDelay();
    }

    /**
     * @dev See {rollbackDefaultAdminDelay}.
     *
     * Internal function without access restriction.
     */
    function _rollbackDefaultAdminDelay() internal virtual {
        _setPendingDelay(0, 0);
    }

    /**
     * @dev Returns the amount of seconds to wait after the `newDelay` will
     * become the new {defaultAdminDelay}.
     *
     * The value returned guarantees that if the delay is reduced, it will go into effect
     * after a wait that honors the previously set delay.
     *
     * See {defaultAdminDelayIncreaseWait}.
     */
    function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) {
        uint48 currentDelay = defaultAdminDelay();

        // When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up
        // to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day
        // to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new
        // delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like
        // using milliseconds instead of seconds.
        //
        // When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees
        // that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled.
        // For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days.
        return
            newDelay > currentDelay
                ? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48
                : currentDelay - newDelay;
    }

    ///
    /// Private setters
    ///

    /**
     * @dev Setter of the tuple for pending admin and its schedule.
     *
     * May emit a DefaultAdminTransferCanceled event.
     */
    function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private {
        (, uint48 oldSchedule) = pendingDefaultAdmin();

        _pendingDefaultAdmin = newAdmin;
        _pendingDefaultAdminSchedule = newSchedule;

        // An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted.
        if (_isScheduleSet(oldSchedule)) {
            // Emit for implicit cancellations when another default admin was scheduled.
            emit DefaultAdminTransferCanceled();
        }
    }

    /**
     * @dev Setter of the tuple for pending delay and its schedule.
     *
     * May emit a DefaultAdminDelayChangeCanceled event.
     */
    function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private {
        uint48 oldSchedule = _pendingDelaySchedule;

        if (_isScheduleSet(oldSchedule)) {
            if (_hasSchedulePassed(oldSchedule)) {
                // Materialize a virtual delay
                _currentDelay = _pendingDelay;
            } else {
                // Emit for implicit cancellations when another delay was scheduled.
                emit DefaultAdminDelayChangeCanceled();
            }
        }

        _pendingDelay = newDelay;
        _pendingDelaySchedule = newSchedule;
    }

    ///
    /// Private helpers
    ///

    /**
     * @dev Defines if an `schedule` is considered set. For consistency purposes.
     */
    function _isScheduleSet(uint48 schedule) private pure returns (bool) {
        return schedule != 0;
    }

    /**
     * @dev Defines if an `schedule` is considered passed. For consistency purposes.
     */
    function _hasSchedulePassed(uint48 schedule) private view returns (bool) {
        return schedule < block.timestamp;
    }
}

// File: src/tokens/UpxEth.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/presets/ERC1155PresetMinterPauser.sol)

pragma solidity 0.8.19;

import {ERC1155} from "solmate/tokens/ERC1155.sol";
import {AccessControlDefaultAdminRules} from "openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol";

/**
 * @title UpxEth
 * @notice Semi Fungible token contract used as an IOU by user 
 * @dev ERC1155 token contract with minting and burning capabilities, using AccessControl for role-based access.
 *
 * UpxEth contract includes:
 * - Total supply tracking for each token ID
 * - Token burning functionality for holders
 * - Minter role for token creation
 *
 * The contract deploys with the default admin role, allowing it to grant minter and burner roles to other accounts.
 * The contract uses AccessControl for role-based access control.
 *
 * Deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/).
 * @author redactedcartel.finance
 */
contract UpxEth is AccessControlDefaultAdminRules, ERC1155 {
    /**
     * @dev Bytes32 constant representing the role to mint new tokens.
     */
    bytes32 internal constant MINTER_ROLE = keccak256("MINTER_ROLE");

    /**
     * @dev Bytes32 constant representing the role to burn (destroy) tokens.
     */
    bytes32 internal constant BURNER_ROLE = keccak256("BURNER_ROLE");

    /**
     * @dev Constructor to initialize the UpxEth contract.
     * @param _initialDelay uint48 Initial delay for AccessControl's admin lock, set by the contract deployer.
     */
    constructor(
        uint48 _initialDelay
    ) AccessControlDefaultAdminRules(_initialDelay, msg.sender) {}

    /**
     * @notice Mints new tokens for a specific address.
     * @dev Restricted to accounts with the MINTER_ROLE.
     * @param to     address Address to receive the minted tokens.
     * @param id     uint256 Token ID to mint.
     * @param amount uint256 Amount of tokens to mint.
     * @param data   bytes   Additional data to include in the minting transaction.
     */
    function mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external onlyRole(MINTER_ROLE) {
        _mint(to, id, amount, data);
    }

    /**
     * @notice Mints a batch of new tokens for a specific address.
     * @dev Restricted to accounts with the MINTER_ROLE.
     * @param to      address   Address to receive the minted tokens.
     * @param ids     uint256[] Array of token IDs to mint.
     * @param amounts uint256[] Array of amounts of tokens to mint.
     * @param data    bytes     Additional data to include in the minting transaction.
     */
    function mintBatch(
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external onlyRole(MINTER_ROLE) {
        _batchMint(to, ids, amounts, data);
    }

    /**
     * @notice Burns a batch of tokens from a specific address.
     * @dev Restricted to accounts with the BURNER_ROLE.
     * @param from    address   Address from which to burn tokens.
     * @param ids     uint256[] Array of token IDs to burn.
     * @param amounts uint256[] Array of amounts of tokens to burn.
     */
    function burnBatch(
        address from,
        uint256[] calldata ids,
        uint256[] calldata amounts
    ) external onlyRole(BURNER_ROLE) {
        _batchBurn(from, ids, amounts);
    }

    /**
     * @notice Burns a specific amount of tokens from a specific address.
     * @dev Restricted to accounts with the BURNER_ROLE.
     * @param from   address Address from which to burn tokens.
     * @param id     uint256 Token ID to burn.
     * @param amount uint256 Amount of tokens to burn.
     */
    function burn(
        address from,
        uint256 id,
        uint256 amount
    ) external onlyRole(BURNER_ROLE) {
        _burn(from, id, amount);
    }

    /**
     * @inheritdoc ERC1155
     * @dev Not implemented due to semi-fungible only requirement
     */
    function uri(uint256 id) public view override returns (string memory) {}

    /**
     * @inheritdoc ERC1155
     */
    function supportsInterface(
        bytes4 interfaceId
    )
        public
        view
        override(AccessControlDefaultAdminRules, ERC1155)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

// File: src/libraries/ValidatorQueue.sol



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

import {SafeCast} from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol";
import {DataTypes} from "./DataTypes.sol";
import {Errors} from "./Errors.sol";

/**
 * @title ValidatorQueue
 * @notice Library for managing a FIFO queue of validators in the Pirex protocol.
 * @dev This library provides functions for adding, swapping, and removing validators in the validator queue.
 * It also includes functions for popping validators from the end of the queue, retrieving validator information, and clearing the entire queue.
 * @author redactedcartel.finance
 */
library ValidatorQueue {
    /**
     * @notice Emitted when a validator is added to the queue.
     * @dev This event is emitted when a validator is successfully added to the end of the queue.
     * @param pubKey               bytes Public key of the added validator.
     * @param withdrawalCredential bytes Withdrawal credentials associated with the added validator.
     */
    event ValidatorAdded(bytes pubKey, bytes withdrawalCredential);

    /**
     * @notice Emitted when the entire validator queue is cleared.
     * @dev This event is emitted when all validators are removed from the queue, clearing it completely.
     */
    event ValidatorQueueCleared();

    /**
     * @notice Emitted when a validator is removed from the queue.
     * @dev This event is emitted when a validator is successfully removed from the queue, either ordered or unordered.
     * @param pubKey      bytes   Public key of the removed validator.
     * @param removeIndex uint256 Index of the removed validator.
     * @param unordered   bool    Indicates whether the removal was unordered.
     */
    event ValidatorRemoved(bytes pubKey, uint256 removeIndex, bool unordered);

    /**
     * @notice Emitted when validators are popped from the front of the queue.
     * @dev This event is emitted when validators are successfully popped from the front of the queue.
     * @param times uint256 Number of pop operations performed.
     */
    event ValidatorsPopped(uint256 times);

    /**
     * @notice Emitted when two validators are swapped in the queue.
     * @dev This event is emitted when two validators are successfully swapped in the queue.
     * @param fromPubKey bytes   Public key of the first validator being swapped.
     * @param toPubKey   bytes   Public key of the second validator being swapped.
     * @param fromIndex  uint256 Index of the first validator.
     * @param toIndex    uint256 Index of the second validator.
     */
    event ValidatorsSwapped(
        bytes fromPubKey,
        bytes toPubKey,
        uint256 fromIndex,
        uint256 toIndex
    );

    /**
     * @notice Adds a synchronized validator to the FIFO queue, ready for staking.
     * @dev This function adds a validator to the end of the queue with the associated withdrawal credentials.
     * @param deque                 DataTypes.ValidatorDeque Storage reference to the validator deque.
     * @param validator             DataTypes.Validator      Validator information to be added.
     * @param withdrawalCredentials bytes                    Withdrawal credentials associated with the validator.
     */
    function add(
        DataTypes.ValidatorDeque storage deque,
        DataTypes.Validator memory validator,
        bytes memory withdrawalCredentials
    ) external {
        int128 backIndex = deque._end;
        deque._validators[backIndex] = validator;

        unchecked {
            deque._end = backIndex + 1;
        }

        emit ValidatorAdded(validator.pubKey, withdrawalCredentials);
    }

    /**
     * @notice Swaps the location of one validator with another.
     * @dev This function swaps the position of two validators in the queue.
     * @param deque     DataTypes.ValidatorDeque Storage reference to the validator deque.
     * @param fromIndex uint256                  Index of the validator to be swapped.
     * @param toIndex   uint256                  Index of the validator to swap with.
     */
    function swap(
        DataTypes.ValidatorDeque storage deque,
        uint256 fromIndex,
        uint256 toIndex
    ) public {
        if (fromIndex == toIndex) revert Errors.InvalidIndexRanges();
        if (empty(deque)) revert Errors.ValidatorQueueEmpty();

        int128 fromidx = SafeCast.toInt128(
            int256(deque._begin) + SafeCast.toInt256(fromIndex)
        );

        if (fromidx >= deque._end) revert Errors.OutOfBounds();

        int128 toidx = SafeCast.toInt128(
            int256(deque._begin) + SafeCast.toInt256(toIndex)
        );

        if (toidx >= deque._end) revert Errors.OutOfBounds();

        // Get the original values
        DataTypes.Validator memory fromVal = deque._validators[fromidx];
        DataTypes.Validator memory toVal = deque._validators[toidx];

        // Set the swapped values
        deque._validators[toidx] = fromVal;
        deque._validators[fromidx] = toVal;

        emit ValidatorsSwapped(
            fromVal.pubKey,
            toVal.pubKey,
            fromIndex,
            toIndex
        );
    }

    /**
     * @notice Removes validators from the end of the queue, in case they were added in error.
     * @dev This function removes validators from the end of the queue, specified by the number of times to pop.
     * @param  deque     DataTypes.ValidatorDeque Storage reference to the validator deque.
     * @param  times     uint256                  Number of pop operations to perform.
     * @return validator DataTypes.Validator      Removed and returned validator.
     */
    function pop(
        DataTypes.ValidatorDeque storage deque,
        uint256 times
    ) public returns (DataTypes.Validator memory validator) {
        // Loop through and remove validator entries at the end
        for (uint256 _i; _i < times; ) {
            if (empty(deque)) revert Errors.ValidatorQueueEmpty();

            int128 backIndex;

            unchecked {
                backIndex = deque._end - 1;
                ++_i;
            }

            validator = deque._validators[backIndex];
            delete deque._validators[backIndex];
            deque._end = backIndex;
        }

        emit ValidatorsPopped(times);
    }

    /**
     * @notice Check if the deque is empty
     * @dev Returns true if the validator deque is empty, otherwise false.
     * @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
     * @return      bool                     True if the deque is empty, otherwise false.
     */
    function empty(
        DataTypes.ValidatorDeque storage deque
    ) public view returns (bool) {
        return deque._end <= deque._begin;
    }

    /**
     * @notice Remove a validator from the array using a more gas-efficient loop.
     * @dev Removes a validator at the specified index and emits an event.
     * @param  deque         DataTypes.ValidatorDeque Storage reference to the validator deque.
     * @param  removeIndex   uint256                  Index of the validator to remove.
     * @return removedPubKey bytes                    Public key of the removed validator.
     */
    function removeOrdered(
        DataTypes.ValidatorDeque storage deque,
        uint256 removeIndex
    ) external returns (bytes memory removedPubKey) {
        int128 idx = SafeCast.toInt128(
            int256(deque._begin) + SafeCast.toInt256(removeIndex)
        );

        if (idx >= deque._end) revert Errors.OutOfBounds();

        // Get the pubkey for the validator to remove (for informational purposes)
        removedPubKey = deque._validators[idx].pubKey;

        for (int128 _i = idx; _i < deque._end - 1; ) {
            deque._validators[_i] = deque._validators[_i + 1];

            unchecked {
                ++_i;
            }
        }

        pop(deque, 1);

        emit ValidatorRemoved(removedPubKey, removeIndex, false);
    }

    /**
     * @notice Remove a validator from the array using swap and pop.
     * @dev Removes a validator at the specified index by swapping it with the last validator and then popping the last validator.
     * @param  deque         DataTypes.ValidatorDeque Storage reference to the validator deque.
     * @param  removeIndex   uint256                  Index of the validator to remove.
     * @return removedPubkey bytes                    Public key of the removed validator.
     */
    function removeUnordered(
        DataTypes.ValidatorDeque storage deque,
        uint256 removeIndex
    ) external returns (bytes memory removedPubkey) {
        int128 idx = SafeCast.toInt128(
            int256(deque._begin) + SafeCast.toInt256(removeIndex)
        );

        if (idx >= deque._end) revert Errors.OutOfBounds();

        // Get the pubkey for the validator to remove (for informational purposes)
        removedPubkey = deque._validators[idx].pubKey;

        // Swap the (validator to remove) with the last validator in the array if needed
        uint256 lastIndex = count(deque) - 1;
        if (removeIndex != lastIndex) {
            swap(deque, removeIndex, lastIndex);
        }

        // Pop off the validator to remove, which is now at the end of the array
        pop(deque, 1);

        emit ValidatorRemoved(removedPubkey, removeIndex, true);
    }

    /**
     * @notice Remove the last validator from the validators array and return its information
     * @dev Removes and returns information about the last validator in the queue.
     * @param  deque                   DataTypes.ValidatorDeque  Deque
     * @param  _withdrawalCredentials  bytes                     Credentials
     * @return pubKey                  bytes                     Key
     * @return withdrawalCredentials   bytes                     Credentials
     * @return signature               bytes                     Signature
     * @return depositDataRoot         bytes32                   Deposit data root
     * @return receiver                address                   account to receive pxEth
     */
    function getNext(
        DataTypes.ValidatorDeque storage deque,
        bytes memory _withdrawalCredentials
    )
        external
        returns (
            bytes memory pubKey,
            bytes memory withdrawalCredentials,
            bytes memory signature,
            bytes32 depositDataRoot,
            address receiver
        )
    {
        if (empty(deque)) revert Errors.ValidatorQueueEmpty();

        int128 frontIndex = deque._begin;
        DataTypes.Validator memory popped = deque._validators[frontIndex];
        delete deque._validators[frontIndex];

        unchecked {
            deque._begin = frontIndex + 1;
        }

        // Return the validator's information
        pubKey = popped.pubKey;
        withdrawalCredentials = _withdrawalCredentials;
        signature = popped.signature;
        depositDataRoot = popped.depositDataRoot;
        receiver = popped.receiver;
    }

    /**
     * @notice Return the information of the i'th validator in the registry
     * @dev Returns information about the validator at the specified index without removing it from the deque.
     * @param  deque                   DataTypes.ValidatorDeque  Deque
     * @param  _withdrawalCredentials  bytes                     Credentials
     * @param  _index                  uint256                   Index
     * @return pubKey                  bytes                     Key
     * @return withdrawalCredentials   bytes                     Credentials
     * @return signature               bytes                     Signature
     * @return depositDataRoot         bytes32                   Deposit data root
     * @return receiver                address                   account to receive pxEth
     */
    function get(
        DataTypes.ValidatorDeque storage deque,
        bytes memory _withdrawalCredentials,
        uint256 _index
    )
        external
        view
        returns (
            bytes memory pubKey,
            bytes memory withdrawalCredentials,
            bytes memory signature,
            bytes32 depositDataRoot,
            address receiver
        )
    {
        // int256(deque._begin) is a safe upcast
        int128 idx = SafeCast.toInt128(
            int256(deque._begin) + SafeCast.toInt256(_index)
        );

        if (idx >= deque._end) revert Errors.OutOfBounds();

        DataTypes.Validator memory _v = deque._validators[idx];

        // Return the validator's information
        pubKey = _v.pubKey;
        withdrawalCredentials = _withdrawalCredentials;
        signature = _v.signature;
        depositDataRoot = _v.depositDataRoot;
        receiver = _v.receiver;
    }

    /**
     * @notice Empties the validator queue.
     * @dev Clears the entire validator deque, setting both begin and end to 0.
     *      Emits an event to signal the clearing of the queue.
     * @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
     */
    function clear(DataTypes.ValidatorDeque storage deque) external {
        deque._begin = 0;
        deque._end = 0;

        emit ValidatorQueueCleared();
    }

    /**
     * @notice Returns the number of validators in the queue.
     * @dev Calculates and returns the number of validators in the deque.
     * @param deque DataTypes.ValidatorDeque Storage reference to the validator deque.
     * @return      uint256                  Number of validators in the deque.
     */
    function count(
        DataTypes.ValidatorDeque storage deque
    ) public view returns (uint256) {
        // The interface preserves the invariant that begin <= end so we assume this will not overflow.
        // We also assume there are at most int256.max items in the queue.
        unchecked {
            return uint256(int256(deque._end) - int256(deque._begin));
        }
    }
}

// File: src/interfaces/IOracleAdapter.sol



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

/**
 * @title IOracleAdapter
 * @notice Interface for Oracle Adapters
 * @dev This interface defines the methods for interacting with OracleAdapter.
 * @author redactedcartel.finance
 */

interface IOracleAdapter {
    /**
     * @notice Requests a voluntary exit for a specific public key
     * @dev This function is used to initiate a voluntary exit process.
     * @param _pubKey bytes The public key of the entity requesting the exit.
     */
    function requestVoluntaryExit(bytes calldata _pubKey) external;
}

// File: src/interfaces/IPirexEth.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {DataTypes} from "../libraries/DataTypes.sol";

/**
 * @title IPirexEth
 * @notice Interface for the PirexEth contract
 * @dev This interface defines the methods for interacting with PirexEth.
 * @author redactedcartel.finance
 */
interface IPirexEth {
    /**
     * @notice Initiate redemption by burning pxETH in return for upxETH
     * @dev This function allows the initiation of redemption by burning pxETH in exchange for upxETH.
     * @param _assets                     uint256 The amount of assets to burn. If the caller is AutoPxEth, then apxETH; pxETH otherwise.
     * @param _receiver                   address The address to receive upxETH.
     * @param _shouldTriggerValidatorExit bool    Whether the initiation should trigger voluntary exit.
     * @return postFeeAmount              uint256 The amount of pxETH burnt for the receiver.
     * @return feeAmount                  uint256 The amount of pxETH distributed as fees.
     */
    function initiateRedemption(
        uint256 _assets,
        address _receiver,
        bool _shouldTriggerValidatorExit
    ) external returns (uint256 postFeeAmount, uint256 feeAmount);

    /**
     * @notice Dissolve validator
     * @dev This function dissolves a validator.
     * @param _pubKey bytes The public key of the validator.
     */
    function dissolveValidator(bytes calldata _pubKey) external payable;

    /**
     * @notice Update validator state to be slashed
     * @dev This function updates the validator state to be slashed.
     * @param _pubKey         bytes                     The public key of the validator.
     * @param _removeIndex    uint256                   The index of the validator to be slashed.
     * @param _amount         uint256                   The ETH amount released from the Beacon chain.
     * @param _unordered      bool                      Whether to remove from the staking validator queue in order or not.
     * @param _useBuffer      bool                      Whether to use a buffer to compensate for the loss.
     * @param _burnerAccounts DataTypes.BurnerAccount[] Burner accounts.
     */
    function slashValidator(
        bytes calldata _pubKey,
        uint256 _removeIndex,
        uint256 _amount,
        bool _unordered,
        bool _useBuffer,
        DataTypes.BurnerAccount[] calldata _burnerAccounts
    ) external payable;

    /**
     * @notice Harvest and mint staking rewards when available
     * @dev This function harvests and mints staking rewards when available.
     * @param _endBlock uint256 The block until which ETH rewards are computed.
     */
    function harvest(uint256 _endBlock) external payable;
}

// File: src/interfaces/IDepositContract.sol



// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.19;

// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
    /// @notice Submit a Phase 0 DepositData object.
    /// @param pubkey A BLS12-381 public key.
    /// @param withdrawal_credentials Commitment to a public key for withdrawals.
    /// @param signature A BLS12-381 signature.
    /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
    /// Used as a protection against malformed input.
    function deposit(
        bytes calldata pubkey,
        bytes calldata withdrawal_credentials,
        bytes calldata signature,
        bytes32 deposit_data_root
    ) external payable;

    /// @notice Query the current deposit count.
    /// @return The deposit count encoded as a little endian 64-bit number.
    function get_deposit_count() external view returns (bytes memory);
}

// File: src/AutoPxEth.sol



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

import {Ownable2Step} from "openzeppelin-contracts/contracts/access/Ownable2Step.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {Errors} from "./libraries/Errors.sol";
import {IPirexEth} from "./interfaces/IPirexEth.sol";

/**
 * @title AutoPxEth
 * @notice Autocompounding vault for (staked) pxETH, adapted from pxCVX vault system
 * @dev This contract enables autocompounding for pxETH assets and includes various fee mechanisms.
 * @author redactedcartel.finance
 */
contract AutoPxEth is Ownable2Step, ERC4626 {
    /**
     * @dev Library: SafeTransferLib - Provides safe transfer functions for ERC20 tokens.
     */
    using SafeTransferLib for ERC20;

    /**
     * @dev Library: FixedPointMathLib - Provides fixed-point arithmetic for uint256.
     */
    using FixedPointMathLib for uint256;

    // Constants

    /**
     * @dev Maximum withdrawal penalty percentage.
     */
    uint256 private constant MAX_WITHDRAWAL_PENALTY = 50_000;
    

    /**
     * @dev Maximum platform fee percentage.
     */
    uint256 private constant MAX_PLATFORM_FEE = 200_000;

    /**
     * @dev Fee denominator for precise fee calculations.
     */
    uint256 private constant FEE_DENOMINATOR = 1_000_000;

    /**
     * @dev Duration of the rewards period.
     */
    uint256 private constant REWARDS_DURATION = 7 days;

    // State variables for tracking rewards and actively staked assets

    /**
     * @notice Reference to the PirexEth contract.
     */
    IPirexEth public pirexEth;
    
    /**
     * @notice Timestamp when the current rewards period will end.
     */
    uint256 public periodFinish;
    
    /**
     * @notice Rate at which rewards are distributed per second.
     */
    uint256 public rewardRate;
    
    /**
     * @notice Timestamp of the last update to the reward variables.
     */
    uint256 public lastUpdateTime;
    
    /**
     * @notice Accumulated reward per token stored.
     */
    uint256 public rewardPerTokenStored;
    
    /**
     * @notice Last calculated reward per token paid to stakers.
     */
    uint256 public rewardPerTokenPaid;
    
    /**
     * @notice Total rewards available for distribution.
     */
    uint256 public rewards;
    
    /**
     * @notice Total assets actively staked in the vault.
     */
    uint256 public totalStaked;

    // State variables related to fees
    /**
     * @notice Withdrawal penalty percentage.
     */
    uint256 public withdrawalPenalty = 30_000;
    
    /**
     * @notice Platform fee percentage.
     */
    uint256 public platformFee = 100_000;
    
    /**
     * @notice Address of the platform that receives fees.
     */
    address public platform;

    // Events

    /**
     * @notice Emitted when rewards are harvested and staked.
     * @dev This event is emitted when a user triggers the harvest function.
     * @param caller address indexed Address that triggered the harvest.
     * @param value  uint256         Amount of rewards harvested.
     */
    event Harvest(address indexed caller, uint256 value);

    /**
     * @notice Emitted when the withdrawal penalty is updated.
     * @dev This event is emitted when the withdrawal penalty is modified.
     * @param penalty uint256 New withdrawal penalty percentage.
     */
    event WithdrawalPenaltyUpdated(uint256 penalty);

    /**
     * @notice Emitted when the platform fee is updated.
     * @dev This event is emitted when the platform fee is modified.
     * @param fee uint256 New platform fee percentage.
     */
    event PlatformFeeUpdated(uint256 fee);
    
    /**
     * @notice Emitted when the platform address is updated.
     * @dev This event is emitted when the platform address is modified.
     * @param _platform address New platform address.
     */
    event PlatformUpdated(address _platform);

    /**
     * @notice Emitted when new rewards are added to the vault.
     * @dev This event is emitted when new rewards are added to the vault.
     * @param reward uint256 Amount of rewards added.
     */
    event RewardAdded(uint256 reward);

    /**
     * @notice Emitted when the PirexEth contract address is set.
     * @dev This event is emitted when the PirexEth contract address is set.
     * @param _pirexEth address New PirexEth contract address.
     */
    event SetPirexEth(address _pirexEth);

    // Modifiers
    /**
     * @dev Update reward states modifier
     * @param updateEarned bool Whether to update earned amount so far
     */
    modifier updateReward(bool updateEarned) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();

        if (updateEarned) {
            rewards = earned();
            rewardPerTokenPaid = rewardPerTokenStored;
        }
        _;
    }

    /**
     * @dev Contract constructor
     * @param _asset address Asset contract address
     * @param _platform address Platform address
     */
    constructor(
        address _asset,
        address _platform
    ) ERC4626(ERC20(_asset), "Autocompounding Pirex Ether", "apxETH") {
        if (_platform == address(0)) revert Errors.ZeroAddress();

        platform = _platform;
    }

    /*//////////////////////////////////////////////////////////////
                        RESTRICTED FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Set the PirexEth contract address
     * @dev Function access restricted to only owner
     * @param _pirexEth address PirexEth contract address
     */
    function setPirexEth(address _pirexEth) external onlyOwner {
        if (_pirexEth == address(0)) revert Errors.ZeroAddress();

        emit SetPirexEth(_pirexEth);

        pirexEth = IPirexEth(_pirexEth);
    }

    /**
     * @notice Set the withdrawal penalty
     * @dev Function access restricted to only owner
     * @param penalty uint256 Withdrawal penalty
     */
    function setWithdrawalPenalty(uint256 penalty) external onlyOwner {
        if (penalty > MAX_WITHDRAWAL_PENALTY) revert Errors.ExceedsMax();

        withdrawalPenalty = penalty;

        emit WithdrawalPenaltyUpdated(penalty);
    }

    /**
     * @notice Set the platform fee
     * @dev Function access restricted to only owner
     * @param fee uint256 Platform fee
     */
    function setPlatformFee(uint256 fee) external onlyOwner {
        if (fee > MAX_PLATFORM_FEE) revert Errors.ExceedsMax();

        platformFee = fee;

        emit PlatformFeeUpdated(fee);
    }

    /**
     * @notice Set the platform
     * @dev Function access restricted to only owner
     * @param _platform address Platform
     */
    function setPlatform(address _platform) external onlyOwner {
        if (_platform == address(0)) revert Errors.ZeroAddress();

        platform = _platform;

        emit PlatformUpdated(_platform);
    }

    /**
     * @notice Notify and sync the newly added rewards to be streamed over time
     * @dev Rewards are streamed following the duration set in REWARDS_DURATION
     */
    function notifyRewardAmount() external updateReward(false) {
        if (msg.sender != address(pirexEth)) revert Errors.NotPirexEth();

        // Rewards transferred directly to this contract are not added to totalStaked
        // To get the rewards w/o relying on a potentially incorrect passed in arg,
        // we can use the difference between the asset balance and totalStaked.
        // Additionally, to avoid re-distributing rewards, deduct the output of `earned`
        uint256 rewardBalance = asset.balanceOf(address(this)) -
            totalStaked -
            earned();

        rewardRate = rewardBalance / REWARDS_DURATION;

        if (rewardRate == 0) revert Errors.NoRewards();

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp + REWARDS_DURATION;

        emit RewardAdded(rewardBalance);
    }

    /*//////////////////////////////////////////////////////////////
                                VIEWS
    //////////////////////////////////////////////////////////////*/

    /**
     * @inheritdoc ERC4626
     * @notice Get the amount of available pxETH in the contract
     * @dev Rewards are streamed for the duration set in REWARDS_DURATION
     */
    function totalAssets() public view override returns (uint256) {
        // Based on the current totalStaked and available rewards
        uint256 _totalStaked = totalStaked;
        uint256 _rewards = ((_totalStaked *
            (rewardPerToken() - rewardPerTokenPaid)) / 1e18) + rewards;

        // Deduct the exact reward amount staked (after fees are deducted when calling `harvest`)
        return
            _totalStaked +
            (
                _rewards == 0
                    ? 0
                    : (_rewards - ((_rewards * platformFee) / FEE_DENOMINATOR))
            );
    }

    /**
     * @notice Returns the last effective timestamp of the current reward period
     * @return uint256 Timestamp
     */
    function lastTimeRewardApplicable() public view returns (uint256) {
        return block.timestamp < periodFinish ? block.timestamp : periodFinish;
    }

    /**
     * @notice Returns the amount of rewards per staked token/asset
     * @return uint256 Rewards amount
     */
    function rewardPerToken() public view returns (uint256) {
        if (totalStaked == 0) {
            return rewardPerTokenStored;
        }

        return
            rewardPerTokenStored +
            ((((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate) *
                1e18) / totalStaked);
    }

    /**
     * @notice Returns the earned rewards amount so far
     * @return uint256 Rewards amount
     */
    function earned() public view returns (uint256) {
        return
            ((totalStaked * (rewardPerToken() - rewardPerTokenPaid)) / 1e18) +
            rewards;
    }

    /**
     * @notice Return the amount of assets per 1 (1e18) share
     * @return uint256 Assets
     */
    function assetsPerShare() external view returns (uint256) {
        return previewRedeem(1e18);
    }

    /*//////////////////////////////////////////////////////////////
                            INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Internal method to keep track of the total amount of staked token/asset on deposit/mint
     */
    function _stake(uint256 amount) internal updateReward(true) {
        totalStaked += amount;
    }

    /**
     * @dev Internal method to keep track of the total amount of staked token/asset on withdrawal/redeem
     */
    function _withdraw(uint256 amount) internal updateReward(true) {
        totalStaked -= amount;
    }

    /*//////////////////////////////////////////////////////////////
                            ERC4626 OVERRIDES
    //////////////////////////////////////////////////////////////*/

    /**
     * @inheritdoc ERC4626
     * @dev Deduct the specified amount of assets from totalStaked to prepare for transfer to the user
     * @param assets uint256 Assets
     */
    function beforeWithdraw(uint256 assets, uint256) internal override {
        // Perform harvest to make sure that totalStaked is always equal or larger than assets to be withdrawn
        if (assets > totalStaked) harvest();

        _withdraw(assets);
    }

    /**
     * @inheritdoc ERC4626
     * @dev Include the new assets in totalStaked so that rewards can be properly distributed
     * @param assets uint256 Assets
     */
    function afterDeposit(uint256 assets, uint256) internal override {
        _stake(assets);
    }

    /**
     * @inheritdoc ERC4626
     * @dev Preview the amount of assets a user would receive from redeeming shares
     */
    function previewRedeem(
        uint256 shares
    ) public view override returns (uint256) {
        // Calculate assets based on a user's % ownership of vault shares
        uint256 assets = convertToAssets(shares);

        uint256 _totalSupply = totalSupply;

        // Calculate a penalty - zero if user is the last to withdraw.
        uint256 penalty = (_totalSupply == 0 || _totalSupply - shares == 0)
            ? 0
            : assets.mulDivUp(withdrawalPenalty, FEE_DENOMINATOR); // Round up the penalty in favour of the protocol.

        // Redeemable amount is the post-penalty amount
        return assets - penalty;
    }

    /**
     * @inheritdoc ERC4626
     * @notice Preview the amount of shares a user would need to redeem the specified asset amount
     * @dev This modified version takes into consideration the withdrawal fee
     */
    function previewWithdraw(
        uint256 assets
    ) public view override returns (uint256) {
        // Calculate shares based on the specified assets' proportion of the pool
        uint256 shares = convertToShares(assets);

        // Save 1 SLOAD
        uint256 _totalSupply = totalSupply;

        // Factor in additional shares to fulfill withdrawal if user is not the last to withdraw
        return
            (_totalSupply == 0 || _totalSupply - shares == 0)
                ? shares
                : (shares * FEE_DENOMINATOR) /
                    (FEE_DENOMINATOR - withdrawalPenalty);
    }

    /*//////////////////////////////////////////////////////////////
                            MUTATIVE FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Harvest and stake available rewards after distributing fees to the platform
     * @dev This function claims and stakes the available rewards, deducting a fee for the platform.
     */
    function harvest() public updateReward(true) {
        uint256 _rewards = rewards;

        if (_rewards != 0) {
            rewards = 0;

            // Fee for platform
            uint256 feeAmount = (_rewards * platformFee) / FEE_DENOMINATOR;

            // Deduct fee from reward balance
            _rewards -= feeAmount;

            // Claimed rewards should be in pxETH
            asset.safeTransfer(platform, feeAmount);

            // Stake rewards sans fee
            _stake(_rewards);

            emit Harvest(msg.sender, _rewards);
        }
    }

    /**
     * @notice Override transfer logic to trigger direct `initiateRedemption`.
     * @dev This function overrides the standard transfer logic to initiate redemption when transferring to the PirexEth contract.
     * @param to     address Transfer destination
     * @param amount uint256 Amount
     * @return       bool
     */
    function transfer(
        address to,
        uint256 amount
    ) public override returns (bool) {
        super.transfer(to, amount);

        if (to == address(pirexEth)) {
            pirexEth.initiateRedemption(amount, msg.sender, false);
        }

        return true;
    }

    /**
     * @notice Override transferFrom logic to trigger direct `initiateRedemption`.
     * @dev This function overrides the standard transferFrom logic to initiate redemption when transferring from the PirexEth contract.
     * @param from   Address of the transfer origin.
     * @param to     Address of the transfer destination.
     * @param amount Amount of tokens to transfer.
     * @return       A boolean indicating the success of the transfer.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public override returns (bool) {
        super.transferFrom(from, to, amount);

        if (to == address(pirexEth)) {
            pirexEth.initiateRedemption(amount, from, false);
        }

        return true;
    }
}

// File: src/PxEth.sol



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

import {DineroERC20} from "./DineroERC20.sol";
import {Errors} from "./libraries/Errors.sol";

/**
 * @title  PxEth
 * @notice The PxEth token, the main token for the PirexEth system used in the Dinero ecosystem.
 * @dev    Extends the DineroERC20 contract and includes additional functionality.
 * @author redactedcartel.finance
 */
contract PxEth is DineroERC20 {
    // Roles
    /**
     * @notice The OPERATOR_ROLE role assigned for operator functions in the PxEth token contract.
     * @dev    Used to control access to critical functions.
     */
    bytes32 private constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");

    /**
     * @notice Constructor to initialize the PxEth token.
     * @dev    Inherits from the DineroERC20 contract and sets the name, symbol, decimals, admin, and initial delay.
     * @param  _admin         address  Admin address.
     * @param  _initialDelay  uint48   Delay required to schedule the acceptance of an access control transfer started.
     */
    constructor(
        address _admin,
        uint48 _initialDelay
    ) DineroERC20("Pirex Ether", "pxETH", 18, _admin, _initialDelay) {}

    /**
     * @notice Operator function to approve allowances for specified accounts and amounts.
     * @dev    Only callable by the operator role.
     * @param  _from    address  Owner of the tokens.
     * @param  _to      address  Account to be approved.
     * @param  _amount  uint256  Amount to be approved.
     */
    function operatorApprove(
        address _from,
        address _to,
        uint256 _amount
    ) external onlyRole(OPERATOR_ROLE) {
        if (_from == address(0)) revert Errors.ZeroAddress();
        if (_to == address(0)) revert Errors.ZeroAddress();

        allowance[_from][_to] = _amount;

        emit Approval(_from, _to, _amount);
    }
}

// File: lib/openzeppelin-contracts/contracts/access/AccessControl.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";

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

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

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

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

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(account),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

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

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

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

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

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

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

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/access/IAccessControlDefaultAdminRules.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/IAccessControlDefaultAdminRules.sol)

pragma solidity ^0.8.0;

import "./IAccessControl.sol";

/**
 * @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection.
 *
 * _Available since v4.9._
 */
interface IAccessControlDefaultAdminRules is IAccessControl {
    /**
     * @dev Emitted when a {defaultAdmin} transfer is started, setting `newAdmin` as the next
     * address to become the {defaultAdmin} by calling {acceptDefaultAdminTransfer} only after `acceptSchedule`
     * passes.
     */
    event DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule);

    /**
     * @dev Emitted when a {pendingDefaultAdmin} is reset if it was never accepted, regardless of its schedule.
     */
    event DefaultAdminTransferCanceled();

    /**
     * @dev Emitted when a {defaultAdminDelay} change is started, setting `newDelay` as the next
     * delay to be applied between default admin transfer after `effectSchedule` has passed.
     */
    event DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule);

    /**
     * @dev Emitted when a {pendingDefaultAdminDelay} is reset if its schedule didn't pass.
     */
    event DefaultAdminDelayChangeCanceled();

    /**
     * @dev Returns the address of the current `DEFAULT_ADMIN_ROLE` holder.
     */
    function defaultAdmin() external view returns (address);

    /**
     * @dev Returns a tuple of a `newAdmin` and an accept schedule.
     *
     * After the `schedule` passes, the `newAdmin` will be able to accept the {defaultAdmin} role
     * by calling {acceptDefaultAdminTransfer}, completing the role transfer.
     *
     * A zero value only in `acceptSchedule` indicates no pending admin transfer.
     *
     * NOTE: A zero address `newAdmin` means that {defaultAdmin} is being renounced.
     */
    function pendingDefaultAdmin() external view returns (address newAdmin, uint48 acceptSchedule);

    /**
     * @dev Returns the delay required to schedule the acceptance of a {defaultAdmin} transfer started.
     *
     * This delay will be added to the current timestamp when calling {beginDefaultAdminTransfer} to set
     * the acceptance schedule.
     *
     * NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this
     * function returns the new delay. See {changeDefaultAdminDelay}.
     */
    function defaultAdminDelay() external view returns (uint48);

    /**
     * @dev Returns a tuple of `newDelay` and an effect schedule.
     *
     * After the `schedule` passes, the `newDelay` will get into effect immediately for every
     * new {defaultAdmin} transfer started with {beginDefaultAdminTransfer}.
     *
     * A zero value only in `effectSchedule` indicates no pending delay change.
     *
     * NOTE: A zero value only for `newDelay` means that the next {defaultAdminDelay}
     * will be zero after the effect schedule.
     */
    function pendingDefaultAdminDelay() external view returns (uint48 newDelay, uint48 effectSchedule);

    /**
     * @dev Starts a {defaultAdmin} transfer by setting a {pendingDefaultAdmin} scheduled for acceptance
     * after the current timestamp plus a {defaultAdminDelay}.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * Emits a DefaultAdminRoleChangeStarted event.
     */
    function beginDefaultAdminTransfer(address newAdmin) external;

    /**
     * @dev Cancels a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}.
     *
     * A {pendingDefaultAdmin} not yet accepted can also be cancelled with this function.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * May emit a DefaultAdminTransferCanceled event.
     */
    function cancelDefaultAdminTransfer() external;

    /**
     * @dev Completes a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}.
     *
     * After calling the function:
     *
     * - `DEFAULT_ADMIN_ROLE` should be granted to the caller.
     * - `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder.
     * - {pendingDefaultAdmin} should be reset to zero values.
     *
     * Requirements:
     *
     * - Only can be called by the {pendingDefaultAdmin}'s `newAdmin`.
     * - The {pendingDefaultAdmin}'s `acceptSchedule` should've passed.
     */
    function acceptDefaultAdminTransfer() external;

    /**
     * @dev Initiates a {defaultAdminDelay} update by setting a {pendingDefaultAdminDelay} scheduled for getting
     * into effect after the current timestamp plus a {defaultAdminDelay}.
     *
     * This function guarantees that any call to {beginDefaultAdminTransfer} done between the timestamp this
     * method is called and the {pendingDefaultAdminDelay} effect schedule will use the current {defaultAdminDelay}
     * set before calling.
     *
     * The {pendingDefaultAdminDelay}'s effect schedule is defined in a way that waiting until the schedule and then
     * calling {beginDefaultAdminTransfer} with the new delay will take at least the same as another {defaultAdmin}
     * complete transfer (including acceptance).
     *
     * The schedule is designed for two scenarios:
     *
     * - When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by
     * {defaultAdminDelayIncreaseWait}.
     * - When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`.
     *
     * A {pendingDefaultAdminDelay} that never got into effect will be canceled in favor of a new scheduled change.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event.
     */
    function changeDefaultAdminDelay(uint48 newDelay) external;

    /**
     * @dev Cancels a scheduled {defaultAdminDelay} change.
     *
     * Requirements:
     *
     * - Only can be called by the current {defaultAdmin}.
     *
     * May emit a DefaultAdminDelayChangeCanceled event.
     */
    function rollbackDefaultAdminDelay() external;

    /**
     * @dev Maximum time in seconds for an increase to {defaultAdminDelay} (that is scheduled using {changeDefaultAdminDelay})
     * to take effect. Default to 5 days.
     *
     * When the {defaultAdminDelay} is scheduled to be increased, it goes into effect after the new delay has passed with
     * the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds)
     * that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can
     * be overrode for a custom {defaultAdminDelay} increase scheduling.
     *
     * IMPORTANT: Make sure to add a reasonable amount of time while overriding this value, otherwise,
     * there's a risk of setting a high new delay that goes into effect almost immediately without the
     * possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds).
     */
    function defaultAdminDelayIncreaseWait() external view returns (uint48);
}

// File: lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCast {
    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     *
     * _Available since v4.7._
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     *
     * _Available since v4.7._
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     *
     * _Available since v4.7._
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     *
     * _Available since v4.2._
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     *
     * _Available since v4.7._
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     *
     * _Available since v4.7._
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     *
     * _Available since v4.7._
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     *
     * _Available since v4.7._
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     *
     * _Available since v4.7._
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     *
     * _Available since v4.7._
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     *
     * _Available since v4.7._
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     *
     * _Available since v4.7._
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     *
     * _Available since v4.7._
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     *
     * _Available since v4.7._
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     *
     * _Available since v4.7._
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v2.5._
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     *
     * _Available since v4.7._
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     *
     * _Available since v4.7._
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     *
     * _Available since v4.7._
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     *
     * _Available since v4.2._
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     *
     * _Available since v4.7._
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     *
     * _Available since v4.7._
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     *
     * _Available since v4.7._
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v2.5._
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     *
     * _Available since v4.7._
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     *
     * _Available since v4.7._
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     *
     * _Available since v4.7._
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v2.5._
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     *
     * _Available since v4.7._
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v2.5._
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     *
     * _Available since v2.5._
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     *
     * _Available since v3.0._
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     *
     * _Available since v4.7._
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     *
     * _Available since v4.7._
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     *
     * _Available since v4.7._
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     *
     * _Available since v4.7._
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     *
     * _Available since v4.7._
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     *
     * _Available since v4.7._
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     *
     * _Available since v4.7._
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     *
     * _Available since v4.7._
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     *
     * _Available since v4.7._
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     *
     * _Available since v4.7._
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     *
     * _Available since v4.7._
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     *
     * _Available since v4.7._
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     *
     * _Available since v4.7._
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     *
     * _Available since v4.7._
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     *
     * _Available since v4.7._
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     *
     * _Available since v4.7._
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     *
     * _Available since v4.7._
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     *
     * _Available since v4.7._
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     *
     * _Available since v4.7._
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     *
     * _Available since v4.7._
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     *
     * _Available since v4.7._
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     *
     * _Available since v4.7._
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     *
     * _Available since v4.7._
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     *
     * _Available since v4.7._
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     *
     * _Available since v4.7._
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     *
     * _Available since v4.7._
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     *
     * _Available since v3.0._
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

// File: lib/openzeppelin-contracts/contracts/interfaces/IERC5313.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5313.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface for the Light Contract Ownership Standard.
 *
 * A standardized minimal interface required to identify an account that controls a contract
 *
 * _Available since v4.9._
 */
interface IERC5313 {
    /**
     * @dev Gets the address of the owner.
     */
    function owner() external view returns (address);
}

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



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 amount
    );

    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] amounts
    );

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    event URI(string value, uint256 indexed id);

    /*//////////////////////////////////////////////////////////////
                             ERC1155 STORAGE
    //////////////////////////////////////////////////////////////*/

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

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                             METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id) public view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                              ERC1155 LOGIC
    //////////////////////////////////////////////////////////////*/

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) public virtual {
        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        balanceOf[from][id] -= amount;
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, from, to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) ==
                    ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) public virtual {
        require(ids.length == amounts.length, "LENGTH_MISMATCH");

        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        // Storing these outside the loop saves ~15 gas per iteration.
        uint256 id;
        uint256 amount;

        for (uint256 i = 0; i < ids.length; ) {
            id = ids[i];
            amount = amounts[i];

            balanceOf[from][id] -= amount;
            balanceOf[to][id] += amount;

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) ==
                    ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function balanceOfBatch(address[] calldata owners, uint256[] calldata ids)
        public
        view
        virtual
        returns (uint256[] memory balances)
    {
        require(owners.length == ids.length, "LENGTH_MISMATCH");

        balances = new uint256[](owners.length);

        // Unchecked because the only math done is incrementing
        // the array index counter which cannot possibly overflow.
        unchecked {
            for (uint256 i = 0; i < owners.length; ++i) {
                balances[i] = balanceOf[owners[i]][ids[i]];
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
            interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
    }

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

    function _mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, address(0), to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) ==
                    ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchMint(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[to][ids[i]] += amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, address(0), to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) ==
                    ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchBurn(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[from][ids[i]] -= amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, address(0), ids, amounts);
    }

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

        emit TransferSingle(msg.sender, from, address(0), id, amount);
    }
}

/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155TokenReceiver {
    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiver.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] calldata,
        uint256[] calldata,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiver.onERC1155BatchReceived.selector;
    }
}

// File: lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.0;

import "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
        _transferOwnership(sender);
    }
}

// File: lib/solmate/src/mixins/ERC4626.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";

/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
    using SafeTransferLib for ERC20;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);

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

    /*//////////////////////////////////////////////////////////////
                               IMMUTABLES
    //////////////////////////////////////////////////////////////*/

    ERC20 public immutable asset;

    constructor(
        ERC20 _asset,
        string memory _name,
        string memory _symbol
    ) ERC20(_name, _symbol, _asset.decimals()) {
        asset = _asset;
    }

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

    function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
        // Check for rounding error since we round down in previewDeposit.
        require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");

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

        _mint(receiver, shares);

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

        afterDeposit(assets, shares);
    }

    function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
        assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.

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

        _mint(receiver, shares);

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

        afterDeposit(assets, shares);
    }

    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public virtual returns (uint256 shares) {
        shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.

        if (msg.sender != owner) {
            uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
        }

        beforeWithdraw(assets, shares);

        _burn(owner, shares);

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

        asset.safeTransfer(receiver, assets);
    }

    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) public virtual returns (uint256 assets) {
        if (msg.sender != owner) {
            uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
        }

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

        beforeWithdraw(assets, shares);

        _burn(owner, shares);

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

        asset.safeTransfer(receiver, assets);
    }

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

    function totalAssets() public view virtual returns (uint256);

    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
    }

    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
    }

    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    function previewMint(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
    }

    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
    }

    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

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

    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return convertToAssets(balanceOf[owner]);
    }

    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HOOKS LOGIC
    //////////////////////////////////////////////////////////////*/

    function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}

    function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}

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



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// File: src/DineroERC20.sol



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

import {ERC20} from "solmate/tokens/ERC20.sol";
import {AccessControlDefaultAdminRules} from "openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol";
import {Errors} from "./libraries/Errors.sol";

/**
 * @title DineroERC20
 * @dev A Standard ERC20 token with minting and burning with access control.
 * @author redactedcartel.finance
 */
contract DineroERC20 is ERC20, AccessControlDefaultAdminRules {
    // Roles
    /**
     * @dev Bytes32 constant representing the role to mint new tokens.
     */
    bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE");

    /**
     * @dev Bytes32 constant representing the role to burn (destroy) tokens.
     */
    bytes32 private constant BURNER_ROLE = keccak256("BURNER_ROLE");

    /**
     * @notice Constructor to initialize ERC20 token with access control.
     * @param _name          string   Token name.
     * @param _symbol        string   Token symbol.
     * @param _decimals      uint8    Token decimals.
     * @param _admin         address  Admin address.
     * @param _initialDelay  uint48   Delay required to schedule the acceptance
     *                                of an access control transfer started.
     */
    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals,
        address _admin,
        uint48 _initialDelay
    )
        AccessControlDefaultAdminRules(_initialDelay, _admin)
        ERC20(_name, _symbol, _decimals)
    {
        if (bytes(_name).length == 0) revert Errors.EmptyString();
        if (bytes(_symbol).length == 0) revert Errors.EmptyString();
        if (_decimals == 0) revert Errors.ZeroAmount();
    }

    /**
     * @notice Mints tokens to an address.
     * @dev Only callable by minters.
     * @param _to      address  Address to mint tokens to.
     * @param _amount  uint256  Amount of tokens to mint.
     */
    function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
        if (_to == address(0)) revert Errors.ZeroAddress();
        if (_amount == 0) revert Errors.ZeroAmount();

        _mint(_to, _amount);
    }

    /**
     * @notice Burns tokens from an address.
     * @dev Only callable by burners.
     * @param _from    address  Address to burn tokens from.
     * @param _amount  uint256  Amount of tokens to burn.
     */
    function burn(
        address _from,
        uint256 _amount
    ) external onlyRole(BURNER_ROLE) {
        if (_from == address(0)) revert Errors.ZeroAddress();
        if (_amount == 0) revert Errors.ZeroAmount();

        _burn(_from, _amount);
    }
}

// File: lib/openzeppelin-contracts/contracts/access/IAccessControl.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

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

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

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

// File: lib/openzeppelin-contracts/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/Strings.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: lib/openzeppelin-contracts/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/math/Math.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @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;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

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

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}